<commit_before>(function () {
    var Ext = window.Ext4 || window.Ext;

    Ext.define('Rally.apps.roadmapplanningboard.PlanningBoard', {
        extend: 'Rally.ui.cardboard.CardBoard',
        alias: 'widget.roadmapplanningboard',

        inject: ['preliminaryEstimateStore'],

        requires: [
            'Rally.data.util.PortfolioItemHelper',
            'Rally.ui.cardboard.plugin.FixedHeader',
            'Rally.apps.roadmapplanningboard.PlanningBoardColumn',
            'Rally.apps.roadmapplanningboard.TimeframePlanningColumn',
            'Rally.apps.roadmapplanningboard.BacklogBoardColumn',
            'Rally.apps.roadmapplanningboard.util.TimeframePlanStoreWrapper',
            'Rally.apps.roadmapplanningboard.util.PlanGenerator',
            'Rally.ui.Button',
            'Rally.ui.grid.TreeGrid'
        ],

        cls: 'roadmap-board cardboard',

        config: {
            roadmap: null,
            timeline: null,
            isAdmin: false,
            storeConfig: {
                shallowFetch: true,
                enablePostGet: true
            },
            cardConfig: {
                editable: true,
                skipDefaultFields: true,
                customFieldConfig: {
                    // Note: These additional fields are added by ALM when using a standard fetch
                    // They need to be explicitly added to the fetch params when using a shallow fetch
                    // TODO: These additional fields should eventually be moved into some sort of shallow fetch builder/decorator in the SDK
                    PercentDoneByStoryPlanEstimate: {
                        fetch: [
                            'PercentDoneByStoryPlanEstimate',
                            'PercentDoneByStoryCount',
                            'LeafStoryCount',
                            'LeafStoryPlanEstimateTotal',
                            'ActualEndDate',
                            'PlannedStartDate',
                            'PlannedEndDate',
                            'AcceptedLeafStoryPlanEstimateTotal',
                            'AcceptedLeafStoryCount',
                            'UnEstimatedLeafStoryCount',
                            'Notes',
                            'PortfolioItemTypeOrdinal',
                            'LateChildCount',
                            'Release'
                        ]
                    },
                    PercentDoneByStoryCount: {
                        fetch: [
                            'PercentDoneByStoryPlanEstimate',
                            'PercentDoneByStoryCount',
                            'LeafStoryCount',
                            'LeafStoryPlanEstimateTotal',
                            'ActualEndDate',
                            'PlannedStartDate',
                            'PlannedEndDate',
                            'AcceptedLeafStoryPlanEstimateTotal',
                            'AcceptedLeafStoryCount',
                            'UnEstimatedLeafStoryCount',
                            'Notes',
                            'PortfolioItemTypeOrdinal',
                            'LateChildCount',
                            'Release'
                        ]
                    },
                    PreliminaryEstimate: {
                        fetch: [
                            { name: 'PreliminaryEstimate', properties: ['Value', 'Name'] }
//                            { name: 'PreliminaryEstimate', properties: ['Value', 'Name'] },
//                            'Name', 'Value'
                        ]
                    },
                    UserStories: {
                        fetch: ['LeafStoryPlanEstimateTotal', 'DirectChildrenCount'],
                        popoverConfig: {
                            placement: ['bottom', 'right', 'left', 'top'],
                            listViewConfig: {
                                addNewConfig: {
                                    showAddWithDetails: false
                                },
                                gridConfig: {
                                    columnCfgs: [
                                        'FormattedID',
                                        'Name',
                                        {
                                            dataIndex: 'ScheduleState', // 'dataIndex' is the actual field name
                                            text: 'State' // 'text' is the display name
                                        },
                                        {
                                            dataIndex: 'PlanEstimate',
                                            editor: {
                                                decimalPrecision: 0
                                            }
                                        },
                                        'Project'
                                    ]
                                }
                            }
                        }
                    }
                }
            },
            columnConfig: {
                additionalFetchFields: ['PreliminaryEstimate', 'RefinedEstimate']
            },
            ddGroup: 'planningBoard',
            dropAllowed: "planningBoard",
            dropNotAllowed: "planningBoard",

            /**
             * @cfg {Boolean}
             * Toggle whether the theme is expanded or collapsed
             */
            showTheme: true,

            /**
             * @cfg {Object} Object containing Names and TypePaths of the lowest level portfolio item (eg: 'Feature') and optionally its parent (eg: 'Initiative')
             */
            typeNames: {},

            /**
             * @cfg {Number} The duration of the theme slide animation in milliseconds
             */
            slideDuration: 250
        },

        clientMetrics: [
            {
                method: '_toggleThemes',
                descriptionProperty: '_getClickAction'
            }
        ],

        initComponent: function () {
            this.timeframePlanStoreWrapper = Ext.create('Rally.apps.roadmapplanningboard.util.TimeframePlanStoreWrapper', {
                requester: this,
                roadmap: this.roadmap,
                timeline: this.timeline
            });

            if(!this.typeNames.child || !this.typeNames.child.name) {
                throw 'typeNames must have a child property with a name';
            }

            this.mergeConfig(this.config);

            this.callParent(arguments);
        },

        refresh: function (options) {
            options = options || {};

            if (options.rebuildBoard) {
                this.showMask('Refreshing the board...');
                this.addCls('loading');

                return this._loadColumnData().then({
                    success: function () {
                        var firstTimeframeColumn = this.getColumns()[1]; // first visible timeframe column
                        var firstTimeframeRecord = firstTimeframeColumn && firstTimeframeColumn.timeframeRecord;

                        this.buildColumns({
                            firstTimeframe: firstTimeframeRecord,
                            render: true
                        });
                    },
                    scope: this
                }).then({
                    success: this._refreshBacklog,
                    scope: this
                }).always(function () {
                    this.hideMask();
                    this.removeCls('loading');
                }, this);
            } else {
                var deferred = new Deft.Deferred();
                this.on('load', function () { deferred.resolve(); }, this, {single: true});

                this.callParent(arguments);

                return deferred.promise;
            }
        },

        shouldRetrieveModels: function () {
            return !this.columns || this.columns.length === 0;
        },

        onModelsRetrieved: function (callback) {
            return this._loadColumnData().then({
                success: function () {
                    this.buildColumns();
                    callback.call(this);
                },
                scope: this
            });
        },

        _loadColumnData: function () {
            return Deft.Promise.all([this.timeframePlanStoreWrapper.load(), this._loadPreliminaryStore()]).then({
                failure: function (operation) {
                    var service = operation.storeServiceName || 'External';
                    Rally.ui.notify.Notifier.showError({message: 'Failed to load: ' + service + ' service data load issue'});
                },
                scope: this
            });
        },

        drawAddNewColumnButton: function () {
            var column = this.getRightmostColumn();
            if (column.rendered && this.isAdmin) {
                if (this.addNewColumnButton) {
                    this.addNewColumnButton.destroy();
                }
                this.addNewColumnButton = Ext.create('Rally.ui.Button', {
                    border: 1,
                    text: '<i class="icon-add"></i>',
                    elTooltip: 'Add Timeframe',
                    cls: 'scroll-button right',
                    height: 28,
                    frame: false,
                    handler: this._addNewColumn,
                    renderTo: column.getHeaderTitle().getEl(),
                    scope: this,
                    userAction: 'rpb add timeframe'
                });
            }
        },

        getRightmostColumn: function () {
            return _.last(this.getColumns());
        },

        _loadPreliminaryStore: function() {
            return this.preliminaryEstimateStore.load();
        },

        /**
         * @inheritDoc
         */
        renderColumns: function () {
            this.callParent(arguments);

            if(this.firstLoad) {
                var titleField = this.getColumns()[1].columnHeader.down('rallyclicktoeditfieldcontainer');
                if(titleField) {
                    titleField.goToEditMode();
                }

                this.firstLoad = false;
            }

            this.drawThemeToggle();
            this.drawAddNewColumnButton();
        },

        /**
         * This method will build an array of columns from timeframe and plan stores
         * @returns {Array} columns
         */
        buildColumns: function () {
            var planColumns = _.map(this.timeframePlanStoreWrapper.getTimeframeAndPlanRecords(), function (record) {
                return this._addColumnFromTimeframeAndPlan(record.timeframe, record.plan);
            }, this);

            this.columns = [this._getBacklogColumnConfig()].concat(planColumns);

            return this.columns;
        },

        _getBacklogColumnConfig: function () {
            return {
                xtype: 'backlogplanningcolumn',
                types: this.types,
                typeNames: this.typeNames,
                planStore: this.timeframePlanStoreWrapper.planStore,
                cls: 'column backlog',
                cardConfig: {
                    preliminaryEstimateStore: this.preliminaryEstimateStore
                }
            };
        },

        /**
         * Return the backlog column if it exists
         * @returns {Rally.apps.roadmapplanningboard.BacklogBoardColumn} column The backlog column of the cardboard
         */
        getBacklogColumn: function () {
            var columns = this.getColumns();

            if (!Ext.isEmpty(columns)) {
                return columns[0];
            } else {
                return null;
            }
        },

        /**
         * Get the first record of the cardboard
         * @returns {Rally.data.Record}
         */
        getFirstRecord: function () {
            var cards;
            var record = null;
            var column = this.getBacklogColumn();

            if (column) {
                cards = column.getCards();
                if (!Ext.isEmpty(cards)) {
                    record = cards[0].getRecord();
                }
            }
            return record;
        },

        /**
         * Draws the theme toggle buttons to show/hide the themes
         */
        drawThemeToggle: function () {
            this._destroyThemeButton();

            this.themeToggleButton = Ext.create('Rally.ui.Button', {
                cls: 'theme-button',
                listeners: {
                    click: this._toggleThemes,
                    scope: this
                }
            });

            _.last(this.getColumns()).getColumnHeader().insert(2, this.themeToggleButton);

            this._updateThemeButton();
        },

        _toggleThemes: function () {
            this.showTheme = !this.showTheme;
            this.themeToggleButton.hide();
            this._updateThemeButton();
            this._updateThemeContainers().then({
                success: function () {
                    this.fireEvent('headersizechanged');
                },
                scope: this
            });
        },

        _updateThemeButton: function () {
            this.themeToggleButton.removeCls(['theme-button-collapse', 'theme-button-expand']);

            if(this.showTheme) {
                this.themeToggleButton.setIconCls('icon-chevron-up');
                this.themeToggleButton.addCls('theme-button-collapse');
            } else {
                this.themeToggleButton.setIconCls('icon-chevron-down');
                this.themeToggleButton.addCls('theme-button-expand');
            }

            this.themeToggleButton.show();
        },

        _addNewColumn: function (options) {
            options = options || {};

            this.addNewColumnButton.setDisabled(true);

            var getRecordPromise;

            if (options.timeframeRecord && options.planRecord) {
                var deferred = new Deft.Deferred();
                deferred.resolve({
                    timeframeRecord: options.timeframeRecord,
                    planRecord: options.planRecord
                });
                getRecordPromise = deferred.promise;
            } else {
                getRecordPromise = this._addNewPlanRecord();
            }

            return getRecordPromise.then({
                success: function (records) {
                    var column = this.addNewColumn(this._addColumnFromTimeframeAndPlan(records.timeframeRecord, records.planRecord));
                    column.columnHeader.down('rallyclicktoeditfieldcontainer').goToEditMode();
                    return column;
                },
                failure: function (error) {
                    this.addNewColumnButton.setDisabled(false);
                    Rally.ui.notify.Notifier.showError({message: 'Failed to create new column: ' + error});
                },
                scope: this
            });
        },

        _addNewPlanRecord: function (options) {
            var generator = Ext.create('Rally.apps.roadmapplanningboard.util.PlanGenerator', {
                timeframePlanStoreWrapper: this.timeframePlanStoreWrapper,
                roadmap: this.roadmap
            });

            return generator.createPlanWithTimeframe(options);
        },

        addNewColumn: function (columnConfig) {
            var columnEls = this.createColumnElements('after', _.last(this.getColumns()));
            var column = this.addColumn(columnConfig, this.getColumns().length);
            this.renderColumn(column, columnEls);

            this.drawThemeToggle();
            this.drawAddNewColumnButton();

            return column;
        },

        _updateThemeContainers: function () {
            var themeContainers = _.map(this.getEl().query('.theme_container'), Ext.get);
            var promises = _.map(themeContainers, this._toggleThemeContainer, this);

            return Deft.Promise.all(promises);
        },

        _toggleThemeContainer: function (el) {
            var deferred = new Deft.Deferred();

            el.addCls('theme-transitioning');

            var slide = this.showTheme ? el.slideIn : el.slideOut;

            slide.call(el, 't', {
                duration: this.slideDuration,
                listeners: {
                    afteranimate: function () {
                        el.removeCls('theme-transitioning');

                        if(!this.showTheme) {
                            el.setStyle('display', 'none'); // OMG Ext. Y U SUCK?
                        }

                        deferred.resolve();
                    },
                    scope: this
                }
            });

            return deferred.promise;
        },

        destroy: function () {
            this._destroyThemeButton();
            this.callParent(arguments);
        },

        _destroyThemeButton: function () {
            if(this.themeToggleButton) {
                this.themeToggleButton.destroy();
            }
        },

        _addColumnFromTimeframeAndPlan: function (timeframe, plan) {
            return {
                xtype: 'timeframeplanningcolumn',
                timeframeRecord: timeframe,
                planRecord: plan,
                timeframePlanStoreWrapper: this.timeframePlanStoreWrapper,
                types: this.types,
                typeNames: this.typeNames,
                columnHeaderConfig: {
                    record: timeframe,
                    fieldToDisplay: 'name',
                    editable: this.isAdmin
                },
                cardConfig: {
                    preliminaryEstimateStore: this.preliminaryEstimateStore
                },
                editPermissions: {
                    capacityRanges: this.isAdmin,
                    theme: this.isAdmin,
                    timeframeDates: this.isAdmin,
                    deletePlan: this.isAdmin
                },
                dropControllerConfig: {
                    dragDropEnabled: this.isAdmin
                },
                isMatchingRecord: function (featureRecord) {
                    return plan && _.find(plan.get('features'), function (feature) {
                        return (feature.id === featureRecord.get('_refObjectUUID'));
                    });
                },
                listeners: {
                    deleteplan: this._deleteTimeframePlanningColumn,
                    daterangechange: this._onColumnDateRangeChange,
                    scope: this
                }
            };
        },

        _onColumnDateRangeChange: function (column) {
            // resorting of columns handled in RoadmapScrollable plugin
        },

        _deleteTimeframePlanningColumn: function (column) {
            this.pendingDeletions = this.pendingDeletions || [];
            this.pendingDeletions.push(column.planRecord);

            var deletingLastColumn = (this.timeframePlanStoreWrapper.planStore.count() - this.pendingDeletions.length) < 1;

            if (column.deletePlanButton) {
                column.deletePlanButton.hide();
            }

            if (deletingLastColumn) {
                return this._addNewPlanRecord({resetDates: true}).then({
                    success: function (options) {
                        this._deletePlan(column).then({
                            success: function () {
                                this._addNewColumn(options);
                            },
                            scope: this
                        });
                    },
                    scope: this
                });
            }

            return this._deletePlan(column);
        },

        _deletePlan: function (column) {
            var timeframeName = column.timeframeRecord.get('name');
            var planRecordToDelete = column.planRecord;
            var columnHadFeatures = column.planRecord.get('features').length;

            return this.timeframePlanStoreWrapper.deletePlan(column.planRecord).then({
                success: function () {
                    this.destroyColumn(column);
                    this.pendingDeletions = _.reject(this.pendingDeletions, function (record) {
                        return record.getId() === planRecordToDelete.getId();
                    });
                    if (columnHadFeatures) {
                        this._refreshBacklog();
                    }
                    Rally.ui.notify.Notifier.showConfirmation({message: 'Column "' + timeframeName + '" deleted.'});
                },
                failure: function (error) {
                    Rally.ui.notify.Notifier.showError({message: error});
                },
                scope: this
            });
        },

        _isColumnOutOfOrder: function (currentColumn) {
            var columns = this.getColumns();
            var currentColumnIndex = _.findIndex(columns, function (column) {
                return column.getId() === currentColumn.getId();
            }, this);

            var previousColumn = columns[currentColumnIndex - 1] || null;
            var nextColumn = columns[currentColumnIndex + 1] || null;

            var previousTimeframe = previousColumn && previousColumn.timeframeRecord;
            var nextTimeframe = nextColumn && nextColumn.timeframeRecord;

            var startsAfterPreviousTimeframe = !previousTimeframe || (currentColumn.timeframeRecord.get('endDate') > previousTimeframe.get('endDate'));
            var endsBeforeNextTimeframe = !nextTimeframe || (currentColumn.timeframeRecord.get('endDate') < nextTimeframe.get('endDate'));

            return !startsAfterPreviousTimeframe || !endsBeforeNextTimeframe;
        },

        _getClickAction: function () {
            return 'Themes toggled from [' + !this.showTheme + '] to [' + this.showTheme + ']';
        },

        _refreshBacklog: function () {
            this.getColumns()[0].refresh();
        }

    });

})();
<commit_msg>S62142: RPB: Fix issue with capacity display when shallowFetch is enabled<commit_after>(function () {
    var Ext = window.Ext4 || window.Ext;

    Ext.define('Rally.apps.roadmapplanningboard.PlanningBoard', {
        extend: 'Rally.ui.cardboard.CardBoard',
        alias: 'widget.roadmapplanningboard',

        inject: ['preliminaryEstimateStore'],

        requires: [
            'Rally.data.util.PortfolioItemHelper',
            'Rally.ui.cardboard.plugin.FixedHeader',
            'Rally.apps.roadmapplanningboard.PlanningBoardColumn',
            'Rally.apps.roadmapplanningboard.TimeframePlanningColumn',
            'Rally.apps.roadmapplanningboard.BacklogBoardColumn',
            'Rally.apps.roadmapplanningboard.util.TimeframePlanStoreWrapper',
            'Rally.apps.roadmapplanningboard.util.PlanGenerator',
            'Rally.ui.Button',
            'Rally.ui.grid.TreeGrid'
        ],

        cls: 'roadmap-board cardboard',

        config: {
            roadmap: null,
            timeline: null,
            isAdmin: false,
            storeConfig: {
                shallowFetch: true,
                enablePostGet: true
            },
            cardConfig: {
                editable: true,
                skipDefaultFields: true,
                customFieldConfig: {
                    // Note: These additional fields are added by ALM when using a standard fetch
                    // They need to be explicitly added to the fetch params when using a shallow fetch
                    // TODO: These additional fields should eventually be moved into some sort of shallow fetch builder/decorator in the SDK
                    PercentDoneByStoryPlanEstimate: {
                        fetch: [
                            'PercentDoneByStoryPlanEstimate',
                            'PercentDoneByStoryCount',
                            'LeafStoryCount',
                            'LeafStoryPlanEstimateTotal',
                            'ActualEndDate',
                            'PlannedStartDate',
                            'PlannedEndDate',
                            'AcceptedLeafStoryPlanEstimateTotal',
                            'AcceptedLeafStoryCount',
                            'UnEstimatedLeafStoryCount',
                            'Notes',
                            'PortfolioItemTypeOrdinal',
                            'LateChildCount',
                            'Release'
                        ]
                    },
                    PercentDoneByStoryCount: {
                        fetch: [
                            'PercentDoneByStoryPlanEstimate',
                            'PercentDoneByStoryCount',
                            'LeafStoryCount',
                            'LeafStoryPlanEstimateTotal',
                            'ActualEndDate',
                            'PlannedStartDate',
                            'PlannedEndDate',
                            'AcceptedLeafStoryPlanEstimateTotal',
                            'AcceptedLeafStoryCount',
                            'UnEstimatedLeafStoryCount',
                            'Notes',
                            'PortfolioItemTypeOrdinal',
                            'LateChildCount',
                            'Release'
                        ]
                    },
                    PreliminaryEstimate: {
                        fetch: [
                            { name: 'PreliminaryEstimate', properties: ['Value', 'Name'] },
                            'Name', 'Value'
                        ]
                    },
                    UserStories: {
                        fetch: ['LeafStoryPlanEstimateTotal', 'DirectChildrenCount'],
                        popoverConfig: {
                            placement: ['bottom', 'right', 'left', 'top'],
                            listViewConfig: {
                                addNewConfig: {
                                    showAddWithDetails: false
                                },
                                gridConfig: {
                                    columnCfgs: [
                                        'FormattedID',
                                        'Name',
                                        {
                                            dataIndex: 'ScheduleState', // 'dataIndex' is the actual field name
                                            text: 'State' // 'text' is the display name
                                        },
                                        {
                                            dataIndex: 'PlanEstimate',
                                            editor: {
                                                decimalPrecision: 0
                                            }
                                        },
                                        'Project'
                                    ]
                                }
                            }
                        }
                    }
                }
            },
            columnConfig: {
                additionalFetchFields: ['PreliminaryEstimate', 'RefinedEstimate']
            },
            ddGroup: 'planningBoard',
            dropAllowed: "planningBoard",
            dropNotAllowed: "planningBoard",

            /**
             * @cfg {Boolean}
             * Toggle whether the theme is expanded or collapsed
             */
            showTheme: true,

            /**
             * @cfg {Object} Object containing Names and TypePaths of the lowest level portfolio item (eg: 'Feature') and optionally its parent (eg: 'Initiative')
             */
            typeNames: {},

            /**
             * @cfg {Number} The duration of the theme slide animation in milliseconds
             */
            slideDuration: 250
        },

        clientMetrics: [
            {
                method: '_toggleThemes',
                descriptionProperty: '_getClickAction'
            }
        ],

        initComponent: function () {
            this.timeframePlanStoreWrapper = Ext.create('Rally.apps.roadmapplanningboard.util.TimeframePlanStoreWrapper', {
                requester: this,
                roadmap: this.roadmap,
                timeline: this.timeline
            });

            if(!this.typeNames.child || !this.typeNames.child.name) {
                throw 'typeNames must have a child property with a name';
            }

            this.mergeConfig(this.config);

            this.callParent(arguments);
        },

        refresh: function (options) {
            options = options || {};

            if (options.rebuildBoard) {
                this.showMask('Refreshing the board...');
                this.addCls('loading');

                return this._loadColumnData().then({
                    success: function () {
                        var firstTimeframeColumn = this.getColumns()[1]; // first visible timeframe column
                        var firstTimeframeRecord = firstTimeframeColumn && firstTimeframeColumn.timeframeRecord;

                        this.buildColumns({
                            firstTimeframe: firstTimeframeRecord,
                            render: true
                        });
                    },
                    scope: this
                }).then({
                    success: this._refreshBacklog,
                    scope: this
                }).always(function () {
                    this.hideMask();
                    this.removeCls('loading');
                }, this);
            } else {
                var deferred = new Deft.Deferred();
                this.on('load', function () { deferred.resolve(); }, this, {single: true});

                this.callParent(arguments);

                return deferred.promise;
            }
        },

        shouldRetrieveModels: function () {
            return !this.columns || this.columns.length === 0;
        },

        onModelsRetrieved: function (callback) {
            return this._loadColumnData().then({
                success: function () {
                    this.buildColumns();
                    callback.call(this);
                },
                scope: this
            });
        },

        _loadColumnData: function () {
            return Deft.Promise.all([this.timeframePlanStoreWrapper.load(), this._loadPreliminaryStore()]).then({
                failure: function (operation) {
                    var service = operation.storeServiceName || 'External';
                    Rally.ui.notify.Notifier.showError({message: 'Failed to load: ' + service + ' service data load issue'});
                },
                scope: this
            });
        },

        drawAddNewColumnButton: function () {
            var column = this.getRightmostColumn();
            if (column.rendered && this.isAdmin) {
                if (this.addNewColumnButton) {
                    this.addNewColumnButton.destroy();
                }
                this.addNewColumnButton = Ext.create('Rally.ui.Button', {
                    border: 1,
                    text: '<i class="icon-add"></i>',
                    elTooltip: 'Add Timeframe',
                    cls: 'scroll-button right',
                    height: 28,
                    frame: false,
                    handler: this._addNewColumn,
                    renderTo: column.getHeaderTitle().getEl(),
                    scope: this,
                    userAction: 'rpb add timeframe'
                });
            }
        },

        getRightmostColumn: function () {
            return _.last(this.getColumns());
        },

        _loadPreliminaryStore: function() {
            return this.preliminaryEstimateStore.load();
        },

        /**
         * @inheritDoc
         */
        renderColumns: function () {
            this.callParent(arguments);

            if(this.firstLoad) {
                var titleField = this.getColumns()[1].columnHeader.down('rallyclicktoeditfieldcontainer');
                if(titleField) {
                    titleField.goToEditMode();
                }

                this.firstLoad = false;
            }

            this.drawThemeToggle();
            this.drawAddNewColumnButton();
        },

        /**
         * This method will build an array of columns from timeframe and plan stores
         * @returns {Array} columns
         */
        buildColumns: function () {
            var planColumns = _.map(this.timeframePlanStoreWrapper.getTimeframeAndPlanRecords(), function (record) {
                return this._addColumnFromTimeframeAndPlan(record.timeframe, record.plan);
            }, this);

            this.columns = [this._getBacklogColumnConfig()].concat(planColumns);

            return this.columns;
        },

        _getBacklogColumnConfig: function () {
            return {
                xtype: 'backlogplanningcolumn',
                types: this.types,
                typeNames: this.typeNames,
                planStore: this.timeframePlanStoreWrapper.planStore,
                cls: 'column backlog',
                cardConfig: {
                    preliminaryEstimateStore: this.preliminaryEstimateStore
                }
            };
        },

        /**
         * Return the backlog column if it exists
         * @returns {Rally.apps.roadmapplanningboard.BacklogBoardColumn} column The backlog column of the cardboard
         */
        getBacklogColumn: function () {
            var columns = this.getColumns();

            if (!Ext.isEmpty(columns)) {
                return columns[0];
            } else {
                return null;
            }
        },

        /**
         * Get the first record of the cardboard
         * @returns {Rally.data.Record}
         */
        getFirstRecord: function () {
            var cards;
            var record = null;
            var column = this.getBacklogColumn();

            if (column) {
                cards = column.getCards();
                if (!Ext.isEmpty(cards)) {
                    record = cards[0].getRecord();
                }
            }
            return record;
        },

        /**
         * Draws the theme toggle buttons to show/hide the themes
         */
        drawThemeToggle: function () {
            this._destroyThemeButton();

            this.themeToggleButton = Ext.create('Rally.ui.Button', {
                cls: 'theme-button',
                listeners: {
                    click: this._toggleThemes,
                    scope: this
                }
            });

            _.last(this.getColumns()).getColumnHeader().insert(2, this.themeToggleButton);

            this._updateThemeButton();
        },

        _toggleThemes: function () {
            this.showTheme = !this.showTheme;
            this.themeToggleButton.hide();
            this._updateThemeButton();
            this._updateThemeContainers().then({
                success: function () {
                    this.fireEvent('headersizechanged');
                },
                scope: this
            });
        },

        _updateThemeButton: function () {
            this.themeToggleButton.removeCls(['theme-button-collapse', 'theme-button-expand']);

            if(this.showTheme) {
                this.themeToggleButton.setIconCls('icon-chevron-up');
                this.themeToggleButton.addCls('theme-button-collapse');
            } else {
                this.themeToggleButton.setIconCls('icon-chevron-down');
                this.themeToggleButton.addCls('theme-button-expand');
            }

            this.themeToggleButton.show();
        },

        _addNewColumn: function (options) {
            options = options || {};

            this.addNewColumnButton.setDisabled(true);

            var getRecordPromise;

            if (options.timeframeRecord && options.planRecord) {
                var deferred = new Deft.Deferred();
                deferred.resolve({
                    timeframeRecord: options.timeframeRecord,
                    planRecord: options.planRecord
                });
                getRecordPromise = deferred.promise;
            } else {
                getRecordPromise = this._addNewPlanRecord();
            }

            return getRecordPromise.then({
                success: function (records) {
                    var column = this.addNewColumn(this._addColumnFromTimeframeAndPlan(records.timeframeRecord, records.planRecord));
                    column.columnHeader.down('rallyclicktoeditfieldcontainer').goToEditMode();
                    return column;
                },
                failure: function (error) {
                    this.addNewColumnButton.setDisabled(false);
                    Rally.ui.notify.Notifier.showError({message: 'Failed to create new column: ' + error});
                },
                scope: this
            });
        },

        _addNewPlanRecord: function (options) {
            var generator = Ext.create('Rally.apps.roadmapplanningboard.util.PlanGenerator', {
                timeframePlanStoreWrapper: this.timeframePlanStoreWrapper,
                roadmap: this.roadmap
            });

            return generator.createPlanWithTimeframe(options);
        },

        addNewColumn: function (columnConfig) {
            var columnEls = this.createColumnElements('after', _.last(this.getColumns()));
            var column = this.addColumn(columnConfig, this.getColumns().length);
            this.renderColumn(column, columnEls);

            this.drawThemeToggle();
            this.drawAddNewColumnButton();

            return column;
        },

        _updateThemeContainers: function () {
            var themeContainers = _.map(this.getEl().query('.theme_container'), Ext.get);
            var promises = _.map(themeContainers, this._toggleThemeContainer, this);

            return Deft.Promise.all(promises);
        },

        _toggleThemeContainer: function (el) {
            var deferred = new Deft.Deferred();

            el.addCls('theme-transitioning');

            var slide = this.showTheme ? el.slideIn : el.slideOut;

            slide.call(el, 't', {
                duration: this.slideDuration,
                listeners: {
                    afteranimate: function () {
                        el.removeCls('theme-transitioning');

                        if(!this.showTheme) {
                            el.setStyle('display', 'none'); // OMG Ext. Y U SUCK?
                        }

                        deferred.resolve();
                    },
                    scope: this
                }
            });

            return deferred.promise;
        },

        destroy: function () {
            this._destroyThemeButton();
            this.callParent(arguments);
        },

        _destroyThemeButton: function () {
            if(this.themeToggleButton) {
                this.themeToggleButton.destroy();
            }
        },

        _addColumnFromTimeframeAndPlan: function (timeframe, plan) {
            return {
                xtype: 'timeframeplanningcolumn',
                timeframeRecord: timeframe,
                planRecord: plan,
                timeframePlanStoreWrapper: this.timeframePlanStoreWrapper,
                types: this.types,
                typeNames: this.typeNames,
                columnHeaderConfig: {
                    record: timeframe,
                    fieldToDisplay: 'name',
                    editable: this.isAdmin
                },
                cardConfig: {
                    preliminaryEstimateStore: this.preliminaryEstimateStore
                },
                editPermissions: {
                    capacityRanges: this.isAdmin,
                    theme: this.isAdmin,
                    timeframeDates: this.isAdmin,
                    deletePlan: this.isAdmin
                },
                dropControllerConfig: {
                    dragDropEnabled: this.isAdmin
                },
                isMatchingRecord: function (featureRecord) {
                    return plan && _.find(plan.get('features'), function (feature) {
                        return (feature.id === featureRecord.get('_refObjectUUID'));
                    });
                },
                listeners: {
                    deleteplan: this._deleteTimeframePlanningColumn,
                    daterangechange: this._onColumnDateRangeChange,
                    scope: this
                }
            };
        },

        _onColumnDateRangeChange: function (column) {
            // resorting of columns handled in RoadmapScrollable plugin
        },

        _deleteTimeframePlanningColumn: function (column) {
            this.pendingDeletions = this.pendingDeletions || [];
            this.pendingDeletions.push(column.planRecord);

            var deletingLastColumn = (this.timeframePlanStoreWrapper.planStore.count() - this.pendingDeletions.length) < 1;

            if (column.deletePlanButton) {
                column.deletePlanButton.hide();
            }

            if (deletingLastColumn) {
                return this._addNewPlanRecord({resetDates: true}).then({
                    success: function (options) {
                        this._deletePlan(column).then({
                            success: function () {
                                this._addNewColumn(options);
                            },
                            scope: this
                        });
                    },
                    scope: this
                });
            }

            return this._deletePlan(column);
        },

        _deletePlan: function (column) {
            var timeframeName = column.timeframeRecord.get('name');
            var planRecordToDelete = column.planRecord;
            var columnHadFeatures = column.planRecord.get('features').length;

            return this.timeframePlanStoreWrapper.deletePlan(column.planRecord).then({
                success: function () {
                    this.destroyColumn(column);
                    this.pendingDeletions = _.reject(this.pendingDeletions, function (record) {
                        return record.getId() === planRecordToDelete.getId();
                    });
                    if (columnHadFeatures) {
                        this._refreshBacklog();
                    }
                    Rally.ui.notify.Notifier.showConfirmation({message: 'Column "' + timeframeName + '" deleted.'});
                },
                failure: function (error) {
                    Rally.ui.notify.Notifier.showError({message: error});
                },
                scope: this
            });
        },

        _isColumnOutOfOrder: function (currentColumn) {
            var columns = this.getColumns();
            var currentColumnIndex = _.findIndex(columns, function (column) {
                return column.getId() === currentColumn.getId();
            }, this);

            var previousColumn = columns[currentColumnIndex - 1] || null;
            var nextColumn = columns[currentColumnIndex + 1] || null;

            var previousTimeframe = previousColumn && previousColumn.timeframeRecord;
            var nextTimeframe = nextColumn && nextColumn.timeframeRecord;

            var startsAfterPreviousTimeframe = !previousTimeframe || (currentColumn.timeframeRecord.get('endDate') > previousTimeframe.get('endDate'));
            var endsBeforeNextTimeframe = !nextTimeframe || (currentColumn.timeframeRecord.get('endDate') < nextTimeframe.get('endDate'));

            return !startsAfterPreviousTimeframe || !endsBeforeNextTimeframe;
        },

        _getClickAction: function () {
            return 'Themes toggled from [' + !this.showTheme + '] to [' + this.showTheme + ']';
        },

        _refreshBacklog: function () {
            this.getColumns()[0].refresh();
        }

    });

})();
<|endoftext|>


<commit_before>'use strict';

var log = log4javascript.getLogger("usageController-logger");

angular.module('uluwatuControllers').controller('usageController', ['$scope', '$rootScope', '$filter', 'UserUsages', 'AccountUsages',
    function ($scope, $rootScope, $filter, UserUsages, AccountUsages) {
        $scope.regions = [];

        $scope.clearFilter = function() {
            initFilter();
        }

        $scope.loadUsages = function () {
            initSums();
            var filterStartDate = $scope.usageFilter.startDate;
            var filterEndDate = new Date($scope.usageFilter.endDate.getTime());
            var param = createRequestParams(filterStartDate);
            var chartsData = { gcp: [], azure: [], aws: [], cloud: $scope.usageFilter.provider};

            populateChartsDataBySelectedPeriod(chartsData, filterStartDate, filterEndDate);

            if ($scope.$parent.user.admin != undefined && $scope.$parent.user.admin) {
              $scope.usages = AccountUsages.query({ param: param }, function(success) {
                processUsages(success, chartsData);
              });
            } else {
              $scope.usages = UserUsages.query({ param: param }, function(success) {
                processUsages(success, chartsData);
              });
            }
        }

        function createRequestParams(filterStartDate) {
          var param = "";
          var filterEndDate = new Date($scope.usageFilter.endDate.getTime());
          filterEndDate.setDate(filterEndDate.getDate() + 1);
          param =  param.concat(filterStartDate !== null ? "since=".concat(filterStartDate.getTime().toString().concat("&")) : "");
          param =  param.concat(filterEndDate !== null ? "filterenddate=".concat(filterEndDate.getTime().toString().concat("&")) : "");
          param =  param.concat($scope.usageFilter.user !== "all" ? "user=".concat($scope.usageFilter.user.concat("&")) : "");
          param = param.concat($scope.usageFilter.provider !== "all" ? "cloud=".concat($scope.usageFilter.provider.concat("&")) : "");
          param = param.concat($scope.usageFilter.region !== "all" ? "zone=".concat($scope.usageFilter.region.concat("&")) : "");
          if( param.substr(-1) === "&" ) {
            param = param.substring(0, param.length - 1);
          }
          return param;
        }

        function populateChartsDataBySelectedPeriod(chartsData, filterStartDate, filterEndDate){
          var usagesPerMonth;
          var startDay = new Date(filterStartDate.getFullYear(), filterStartDate.getMonth(), filterStartDate.getDate(), 0, 0, 0, 0);
          var endDay = new Date(filterEndDate.getFullYear(), filterEndDate.getMonth(), filterEndDate.getDate(), 0, 0, 0, 0);
          while(startDay <= endDay) {
            var shortDate = startDay.getFullYear()+'-'+(startDay.getMonth()+1)+'-'+startDay.getDate();
            chartsData.gcp.push({ 'date': startDay.getTime(), 'hours': parseInt(0)});
            chartsData.azure.push({ 'date': startDay.getTime(), 'hours': parseInt(0)});
            chartsData.aws.push({ 'date': startDay.getTime(), 'hours': parseInt(0)});
            startDay.setDate(startDay.getDate()+1);
          }
        }

        function processUsages(usages, chartsData) {
          angular.forEach(usages, function(item) {
            item.monthString = new Date(item.day).getFullYear() + "-" +  new Date(item.day).getMonth();
            item.monthDayString = new Date(item.day).toLocaleDateString();

            var calculatedCost;
            var usageByProvider;
            var chartsDataByProvider;
            if ($scope.gccFilterFunction(item)) {
              usageByProvider = $scope.gccSum;
              chartsDataByProvider = chartsData.gcp;
            } else if($scope.azureFilterFunction(item)) {
              usageByProvider = $scope.azureSum;
              chartsDataByProvider = chartsData.azure;
            } else if($scope.awsFilterFunction(item)) {
              usageByProvider = $scope.awsSum;
              chartsDataByProvider = chartsData.aws;
            }

            addUsageToChartsData(item, chartsDataByProvider);
            addUsageToSum(item, calculatedCost, usageByProvider)

          });
          $scope.orderUsagesBy('stackName', false);
          $scope.dataOfCharts = chartsData;
        }

        function addUsageToChartsData(item, chartsDataByProvider) {
          var actDay = new Date(item.day);
          var shortDate = new Date(actDay.getFullYear(), actDay.getMonth(), actDay.getDate(), 0, 0, 0, 0);
          var chartData = $filter('filter')(chartsDataByProvider, { 'date': shortDate.getTime()}, true);
          if (chartData.length > 0) {
            chartData[0].hours += item.instanceHours;
          }
        }

        function addUsageToSum(item, calculatedCost, usageByProvider) {
          usageByProvider.fullHours += parseFloat(item.instanceHours);
          var result = $filter('filter')(usageByProvider.items, {stackId: item.stackId}, true);
          if (result.length > 0) {
            result[0].instanceHours = result[0].instanceHours + item.instanceHours;
            result[0].instanceGroups.push({instanceType: item.instanceType, hours: item.instanceHours, name: item.hostGroup});
          } else {
            item.instanceGroups = [];
            item.instanceGroups.push({instanceType: item.instanceType, hours:  item.instanceHours, name: item.hostGroup});
            usageByProvider.items.push(item);
          }
        }

        $scope.gccFilterFunction = function(element) {
            try {
                return element.provider.match('GCC') ? true : false;
            } catch (err) {
                return false;
            }
        };

        $scope.awsFilterFunction = function(element) {
            try {
                return element.provider.match('AWS') ? true : false;
            }  catch (err) {
                return false;
            }
        };

        $scope.azureFilterFunction = function(element) {
            try {
                return element.provider.match('AZURE') ? true : false;
            } catch (err) {
                return false;
            }
        };

        $scope.selectRegionsByProvider = function() {
          $scope.regions = [];
          $scope.usageFilter.region = 'all';

          if ($scope.usageFilter.provider == 'AWS' || $scope.usageFilter.provider == 'all') {
            $rootScope.config.AWS.awsRegions.forEach(function(item) {$scope.regions.push(item);});
          }

          if ($scope.usageFilter.provider == 'AZURE' || $scope.usageFilter.provider == 'all') {
            $rootScope.config.AZURE.azureRegions.forEach(function(item) {$scope.regions.push(item);});
          }

          if ($scope.usageFilter.provider == 'GCC' || $scope.usageFilter.provider == 'all') {
            $rootScope.config.GCC.gccRegions.forEach(function(item) {$scope.regions.push(item);});
          }
        };

        $scope.selectProviderByRegion = function() {
            if($filter('filter')($rootScope.config.AWS.awsRegions, { key: $scope.usageFilter.region}).length === 1) {
                $scope.usageFilter.provider = 'AWS';
            } else if ($filter('filter')($rootScope.config.AZURE.azureRegions, { key: $scope.usageFilter.region}).length === 1) {
                $scope.usageFilter.provider = 'AZURE';
            } else if($filter('filter')($rootScope.config.GCC.gccRegions, { key: $scope.usageFilter.region}).length === 1) {
                $scope.usageFilter.provider = 'GCC';
            }
        }

        $scope.setStartDate = function(dateString) {
            $scope.usageFilter.startDate = floorToDay(dateString);
            $scope.$digest();
        }

        $scope.setEndDate = function(dateString) {
          $scope.usageFilter.endDate = floorToDay(dateString);
          $scope.$digest();
        }

        $scope.orderUsagesBy = function(predicate, reverse) {
          $scope.gccSum.items = $filter('orderBy')($scope.gccSum.items, predicate, reverse);
          $scope.azureSum.items = $filter('orderBy')($scope.azureSum.items, predicate, reverse);
          $scope.awsSum.items = $filter('orderBy')($scope.awsSum.items, predicate, reverse);
        }

        function initSums() {
            $scope.awsSum = {
                fullHours: 0,
                items: []
            };
            $scope.gccSum = {
                fullHours: 0,
                items: []
            };
            $scope.azureSum = {
                fullHours: 0,
                items: []
            };
        }

        function initFilter() {
          var endDate = new Date();
          var startDate = new Date();
          startDate.setMonth(startDate.getMonth()-1);
          $scope.usageFilter = {
              startDate: floorToDay(startDate),
              endDate: floorToDay(endDate),
              user: "all",
              provider: "all",
              region: "all"
          };
          $scope.selectRegionsByProvider();
        }

        function floorToDay(dateString) {
          var date = new Date(dateString);
          return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
        }

        initFilter();
        initSums();
    }
]);
<commit_msg>CLOUD-390 added null check for chart data<commit_after>'use strict';

var log = log4javascript.getLogger("usageController-logger");

angular.module('uluwatuControllers').controller('usageController', ['$scope', '$rootScope', '$filter', 'UserUsages', 'AccountUsages',
    function ($scope, $rootScope, $filter, UserUsages, AccountUsages) {
        $scope.regions = [];

        $scope.clearFilter = function() {
            initFilter();
        }

        $scope.loadUsages = function () {
            initSums();
            var filterStartDate = $scope.usageFilter.startDate;
            var filterEndDate = new Date($scope.usageFilter.endDate.getTime());
            var param = createRequestParams(filterStartDate);
            var chartsData = { gcp: [], azure: [], aws: [], cloud: $scope.usageFilter.provider};

            populateChartsDataBySelectedPeriod(chartsData, filterStartDate, filterEndDate);

            if ($scope.$parent.user.admin != undefined && $scope.$parent.user.admin) {
              $scope.usages = AccountUsages.query({ param: param }, function(success) {
                processUsages(success, chartsData);
              });
            } else {
              $scope.usages = UserUsages.query({ param: param }, function(success) {
                processUsages(success, chartsData);
              });
            }
        }

        function createRequestParams(filterStartDate) {
          var param = "";
          var filterEndDate = new Date($scope.usageFilter.endDate.getTime());
          filterEndDate.setDate(filterEndDate.getDate() + 1);
          param =  param.concat(filterStartDate !== null ? "since=".concat(filterStartDate.getTime().toString().concat("&")) : "");
          param =  param.concat(filterEndDate !== null ? "filterenddate=".concat(filterEndDate.getTime().toString().concat("&")) : "");
          param =  param.concat($scope.usageFilter.user !== "all" ? "user=".concat($scope.usageFilter.user.concat("&")) : "");
          param = param.concat($scope.usageFilter.provider !== "all" ? "cloud=".concat($scope.usageFilter.provider.concat("&")) : "");
          param = param.concat($scope.usageFilter.region !== "all" ? "zone=".concat($scope.usageFilter.region.concat("&")) : "");
          if( param.substr(-1) === "&" ) {
            param = param.substring(0, param.length - 1);
          }
          return param;
        }

        function populateChartsDataBySelectedPeriod(chartsData, filterStartDate, filterEndDate){
          var usagesPerMonth;
          var startDay = new Date(filterStartDate.getFullYear(), filterStartDate.getMonth(), filterStartDate.getDate(), 0, 0, 0, 0);
          var endDay = new Date(filterEndDate.getFullYear(), filterEndDate.getMonth(), filterEndDate.getDate(), 0, 0, 0, 0);
          while(startDay <= endDay) {
            var shortDate = startDay.getFullYear()+'-'+(startDay.getMonth()+1)+'-'+startDay.getDate();
            chartsData.gcp.push({ 'date': startDay.getTime(), 'hours': parseInt(0)});
            chartsData.azure.push({ 'date': startDay.getTime(), 'hours': parseInt(0)});
            chartsData.aws.push({ 'date': startDay.getTime(), 'hours': parseInt(0)});
            startDay.setDate(startDay.getDate()+1);
          }
        }

        function processUsages(usages, chartsData) {
          angular.forEach(usages, function(item) {
            item.monthString = new Date(item.day).getFullYear() + "-" +  new Date(item.day).getMonth();
            item.monthDayString = new Date(item.day).toLocaleDateString();

            var calculatedCost;
            var usageByProvider;
            var chartsDataByProvider;
            if ($scope.gccFilterFunction(item)) {
              usageByProvider = $scope.gccSum;
              chartsDataByProvider = chartsData.gcp;
            } else if($scope.azureFilterFunction(item)) {
              usageByProvider = $scope.azureSum;
              chartsDataByProvider = chartsData.azure;
            } else if($scope.awsFilterFunction(item)) {
              usageByProvider = $scope.awsSum;
              chartsDataByProvider = chartsData.aws;
            }

            addUsageToChartsData(item, chartsDataByProvider);
            addUsageToSum(item, calculatedCost, usageByProvider)

          });
          $scope.orderUsagesBy('stackName', false);
          $scope.dataOfCharts = chartsData;
        }

        function addUsageToChartsData(item, chartsDataByProvider) {
          var actDay = new Date(item.day);
          var shortDate = new Date(actDay.getFullYear(), actDay.getMonth(), actDay.getDate(), 0, 0, 0, 0);
          var chartData = $filter('filter')(chartsDataByProvider, { 'date': shortDate.getTime()}, true);
          if (chartData != undefined && chartData.length > 0) {
            chartData[0].hours += item.instanceHours;
          }
        }

        function addUsageToSum(item, calculatedCost, usageByProvider) {
          usageByProvider.fullHours += parseFloat(item.instanceHours);
          var result = $filter('filter')(usageByProvider.items, {stackId: item.stackId}, true);
          if (result.length > 0) {
            result[0].instanceHours = result[0].instanceHours + item.instanceHours;
            result[0].instanceGroups.push({instanceType: item.instanceType, hours: item.instanceHours, name: item.hostGroup});
          } else {
            item.instanceGroups = [];
            item.instanceGroups.push({instanceType: item.instanceType, hours:  item.instanceHours, name: item.hostGroup});
            usageByProvider.items.push(item);
          }
        }

        $scope.gccFilterFunction = function(element) {
            try {
                return element.provider.match('GCC') ? true : false;
            } catch (err) {
                return false;
            }
        };

        $scope.awsFilterFunction = function(element) {
            try {
                return element.provider.match('AWS') ? true : false;
            }  catch (err) {
                return false;
            }
        };

        $scope.azureFilterFunction = function(element) {
            try {
                return element.provider.match('AZURE') ? true : false;
            } catch (err) {
                return false;
            }
        };

        $scope.selectRegionsByProvider = function() {
          $scope.regions = [];
          $scope.usageFilter.region = 'all';

          if ($scope.usageFilter.provider == 'AWS' || $scope.usageFilter.provider == 'all') {
            $rootScope.config.AWS.awsRegions.forEach(function(item) {$scope.regions.push(item);});
          }

          if ($scope.usageFilter.provider == 'AZURE' || $scope.usageFilter.provider == 'all') {
            $rootScope.config.AZURE.azureRegions.forEach(function(item) {$scope.regions.push(item);});
          }

          if ($scope.usageFilter.provider == 'GCC' || $scope.usageFilter.provider == 'all') {
            $rootScope.config.GCC.gccRegions.forEach(function(item) {$scope.regions.push(item);});
          }
        };

        $scope.selectProviderByRegion = function() {
            if($filter('filter')($rootScope.config.AWS.awsRegions, { key: $scope.usageFilter.region}).length === 1) {
                $scope.usageFilter.provider = 'AWS';
            } else if ($filter('filter')($rootScope.config.AZURE.azureRegions, { key: $scope.usageFilter.region}).length === 1) {
                $scope.usageFilter.provider = 'AZURE';
            } else if($filter('filter')($rootScope.config.GCC.gccRegions, { key: $scope.usageFilter.region}).length === 1) {
                $scope.usageFilter.provider = 'GCC';
            }
        }

        $scope.setStartDate = function(dateString) {
            $scope.usageFilter.startDate = floorToDay(dateString);
            $scope.$digest();
        }

        $scope.setEndDate = function(dateString) {
          $scope.usageFilter.endDate = floorToDay(dateString);
          $scope.$digest();
        }

        $scope.orderUsagesBy = function(predicate, reverse) {
          $scope.gccSum.items = $filter('orderBy')($scope.gccSum.items, predicate, reverse);
          $scope.azureSum.items = $filter('orderBy')($scope.azureSum.items, predicate, reverse);
          $scope.awsSum.items = $filter('orderBy')($scope.awsSum.items, predicate, reverse);
        }

        function initSums() {
            $scope.awsSum = {
                fullHours: 0,
                items: []
            };
            $scope.gccSum = {
                fullHours: 0,
                items: []
            };
            $scope.azureSum = {
                fullHours: 0,
                items: []
            };
        }

        function initFilter() {
          var endDate = new Date();
          var startDate = new Date();
          startDate.setMonth(startDate.getMonth()-1);
          $scope.usageFilter = {
              startDate: floorToDay(startDate),
              endDate: floorToDay(endDate),
              user: "all",
              provider: "all",
              region: "all"
          };
          $scope.selectRegionsByProvider();
        }

        function floorToDay(dateString) {
          var date = new Date(dateString);
          return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
        }

        initFilter();
        initSums();
    }
]);
<|endoftext|>


<commit_before>/* eslint-env jest */

import cheerio from 'cheerio'
import { join } from 'path'
import fs from 'fs-extra'
import webdriver from 'next-webdriver'

import {
  File,
  fetchViaHTTP,
  findPort,
  killApp,
  launchApp,
  nextBuild as _nextBuild,
  nextStart as _nextStart,
  renderViaHTTP,
  check,
} from 'next-test-utils'

import css from './css'

const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')]
const appDir = join(__dirname, '../app')
const nativeModuleTestAppDir = join(__dirname, '../unsupported-native-module')
const distDir = join(__dirname, '../app/.next')
const documentPage = new File(join(appDir, 'pages/_document.jsx'))
const appPage = new File(join(appDir, 'pages/_app.js'))
const error500Page = new File(join(appDir, 'pages/500.js'))

const documentWithGip = `
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

Document.getInitialProps = (ctx) => {
  return ctx.defaultGetInitialProps(ctx)
}
`

const appWithGlobalCss = `
import '../styles.css'

function App({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default App
`

const page500 = `
export default function Page500() {
  return 'custom-500-page'
}
`

async function nextBuild(dir) {
  return await _nextBuild(dir, [], {
    stdout: true,
    stderr: true,
    nodeArgs,
  })
}

async function nextStart(dir, port) {
  return await _nextStart(dir, port, {
    stdout: true,
    stderr: true,
    nodeArgs,
  })
}

async function nextDev(dir, port) {
  return await launchApp(dir, port, {
    stdout: true,
    stderr: true,
    nodeArgs,
  })
}

async function resolveStreamResponse(response, onData) {
  let result = ''
  onData = onData || (() => {})
  await new Promise((resolve) => {
    response.body.on('data', (chunk) => {
      result += chunk.toString()
      onData(chunk.toString(), result)
    })

    response.body.on('end', resolve)
  })
  return result
}

describe('concurrentFeatures - basic', () => {
  it('should warn user for experimental risk with server components', async () => {
    const edgeRuntimeWarning =
      'You are using the experimental Edge Runtime with `concurrentFeatures`.'
    const rscWarning = `You have experimental React Server Components enabled. Continue at your own risk.`
    const { stderr } = await nextBuild(appDir)
    expect(stderr).toContain(edgeRuntimeWarning)
    expect(stderr).toContain(rscWarning)
  })
  it('should warn user that native node APIs are not supported', async () => {
    const fsImportedErrorMessage =
      'Native Node.js APIs are not supported in the Edge Runtime with `concurrentFeatures` enabled. Found `dns` imported.'
    const { stderr } = await nextBuild(nativeModuleTestAppDir)
    expect(stderr).toContain(fsImportedErrorMessage)
  })
})

describe('concurrentFeatures - prod', () => {
  const context = { appDir }

  beforeAll(async () => {
    error500Page.write(page500)
    context.appPort = await findPort()
    await nextBuild(context.appDir)
    context.server = await nextStart(context.appDir, context.appPort)
  })
  afterAll(async () => {
    error500Page.delete()
    await killApp(context.server)
  })

  it('should generate rsc middleware manifests', async () => {
    const distServerDir = join(distDir, 'server')
    const hasFile = (filename) => fs.existsSync(join(distServerDir, filename))

    const files = [
      'middleware-build-manifest.js',
      'middleware-flight-manifest.js',
      'middleware-ssr-runtime.js',
      'middleware-manifest.json',
    ]
    files.forEach((file) => {
      expect(hasFile(file)).toBe(true)
    })
  })

  it('should have clientInfo in middleware manifest', async () => {
    const middlewareManifestPath = join(
      distDir,
      'server',
      'middleware-manifest.json'
    )
    const content = JSON.parse(
      await fs.readFile(middlewareManifestPath, 'utf8')
    )
    for (const item of [
      ['/', true],
      ['/next-api/image', true],
      ['/next-api/link', true],
      ['/routes/[dynamic]', true],
    ]) {
      expect(content.clientInfo).toContainEqual(item)
    }
    expect(content.clientInfo).not.toContainEqual([['/404', true]])
  })

  it('should support React.lazy and dynamic imports', async () => {
    const html = await renderViaHTTP(context.appPort, '/dynamic-imports')
    expect(html).toContain('foo.client')
  })

  runBasicTests(context, 'prod')
})

describe('concurrentFeatures - dev', () => {
  const context = { appDir }

  beforeAll(async () => {
    error500Page.write(page500)
    context.appPort = await findPort()
    context.server = await nextDev(context.appDir, context.appPort)
  })
  afterAll(async () => {
    error500Page.delete()
    await killApp(context.server)
  })

  it('should support React.lazy and dynamic imports', async () => {
    const html = await renderViaHTTP(context.appPort, '/dynamic-imports')
    expect(html).toContain('loading...')

    const browser = await webdriver(context.appPort, '/dynamic-imports')
    const content = await browser.eval(`window.document.body.innerText`)
    expect(content).toMatchInlineSnapshot('"foo.client"')
  })

  it('should not bundle external imports into client builds for RSC', async () => {
    const html = await renderViaHTTP(context.appPort, '/external-imports')
    expect(html).toContain('date:')

    const distServerDir = join(distDir, 'static', 'chunks', 'pages')
    const bundle = fs
      .readFileSync(join(distServerDir, 'external-imports.js'))
      .toString()

    expect(bundle).not.toContain('moment')
  })

  runBasicTests(context, 'dev')
})

const cssSuite = {
  runTests: css,
  before: () => appPage.write(appWithGlobalCss),
  after: () => appPage.delete(),
}

runSuite('CSS', 'dev', cssSuite)
runSuite('CSS', 'prod', cssSuite)

const documentSuite = {
  runTests: (context) => {
    it('should error when custom _document has getInitialProps method', async () => {
      const res = await fetchViaHTTP(context.appPort, '/')
      const html = await res.text()

      expect(res.status).toBe(500)
      expect(html).toContain(
        'Error: `getInitialProps` in Document component is not supported with `concurrentFeatures` enabled.'
      )
    })
  },
  before: () => documentPage.write(documentWithGip),
  after: () => documentPage.delete(),
}

runSuite('document', 'dev', documentSuite)
runSuite('document', 'prod', documentSuite)

async function runBasicTests(context, env) {
  const isDev = env === 'dev'
  it('should render the correct html', async () => {
    const homeHTML = await renderViaHTTP(context.appPort, '/', null, {
      headers: {
        'x-next-test-client': 'test-util',
      },
    })

    // should have only 1 DOCTYPE
    expect(homeHTML).toMatch(/^<!DOCTYPE html><html/)

    // dynamic routes
    const dynamicRouteHTML1 = await renderViaHTTP(
      context.appPort,
      '/routes/dynamic1'
    )
    const dynamicRouteHTML2 = await renderViaHTTP(
      context.appPort,
      '/routes/dynamic2'
    )

    const path404HTML = await renderViaHTTP(context.appPort, '/404')
    const path500HTML = await renderViaHTTP(context.appPort, '/err')
    const pathNotFoundHTML = await renderViaHTTP(
      context.appPort,
      '/this-is-not-found'
    )

    expect(homeHTML).toContain('component:index.server')
    expect(homeHTML).toContain('env:env_var_test')
    expect(homeHTML).toContain('header:test-util')
    expect(homeHTML).toContain('path:/')
    expect(homeHTML).toContain('foo.client')

    expect(dynamicRouteHTML1).toContain('[pid]')
    expect(dynamicRouteHTML2).toContain('[pid]')

    expect(path404HTML).toContain('custom-404-page')
    // in dev mode: custom error page is still using default _error
    expect(path500HTML).toContain(
      isDev ? 'Internal Server Error' : 'custom-500-page'
    )
    expect(pathNotFoundHTML).toContain('custom-404-page')
  })

  it('should support next/link', async () => {
    const linkHTML = await renderViaHTTP(context.appPort, '/next-api/link')
    const $ = cheerio.load(linkHTML)
    const linkText = $('div[hidden] > a[href="/"]').text()

    expect(linkText).toContain('go home')

    const browser = await webdriver(context.appPort, '/next-api/link')
    await browser.eval('window.beforeNav = 1')
    await browser.elementByCss('#next_id').click()
    await browser.elementByCss('#next_id').click()
    await check(() => browser.waitForElementByCss('#query').text(), /query:2/)
    expect(await browser.eval('window.beforeNav')).toBe(1)
  })

  it('should suspense next/image on server side', async () => {
    const imageHTML = await renderViaHTTP(context.appPort, '/next-api/image')
    const $ = cheerio.load(imageHTML)
    const imageTag = $('div[hidden] > span > span > img')

    expect(imageTag.attr('src')).toContain('data:image')
  })

  it('should support multi-level server component imports', async () => {
    const html = await renderViaHTTP(context.appPort, '/multi')
    expect(html).toContain('bar.server.js:')
    expect(html).toContain('foo.client')
  })

  it('should support streaming', async () => {
    await fetchViaHTTP(context.appPort, '/streaming', null, {}).then(
      async (response) => {
        let gotFallback = false
        let gotData = false

        await resolveStreamResponse(response, (_, result) => {
          gotData = result.includes('next_streaming_data')
          if (!gotFallback) {
            gotFallback = result.includes('next_streaming_fallback')
            if (gotFallback) {
              expect(gotData).toBe(false)
            }
          }
        })

        expect(gotFallback).toBe(true)
        expect(gotData).toBe(true)
      }
    )

    // Should end up with "next_streaming_data".
    const browser = await webdriver(context.appPort, '/streaming')
    const content = await browser.eval(`window.document.body.innerText`)
    expect(content).toMatchInlineSnapshot('"next_streaming_data"')
  })

  it('should support streaming flight request', async () => {
    await fetchViaHTTP(context.appPort, '/?__flight__=1').then(
      async (response) => {
        const result = await resolveStreamResponse(response)
        expect(result).toContain('component:index.server')
      }
    )
  })

  it('should support api routes', async () => {
    const res = await renderViaHTTP(context.appPort, '/api/ping')
    expect(res).toContain('pong')
  })
}

function runSuite(suiteName, env, { runTests, before, after }) {
  const context = { appDir }
  describe(`${suiteName} ${env}`, () => {
    if (env === 'prod') {
      beforeAll(async () => {
        before?.()
        context.appPort = await findPort()
        context.server = await nextDev(context.appDir, context.appPort)
      })
    }
    if (env === 'dev') {
      beforeAll(async () => {
        before?.()
        context.appPort = await findPort()
        context.server = await nextDev(context.appDir, context.appPort)
      })
    }
    afterAll(async () => {
      after?.()
      await killApp(context.server)
    })

    runTests(context, env)
  })
}
<commit_msg>Fix flakey next/link react streaming test (#32351)<commit_after>/* eslint-env jest */

import cheerio from 'cheerio'
import { join } from 'path'
import fs from 'fs-extra'
import webdriver from 'next-webdriver'

import {
  File,
  fetchViaHTTP,
  findPort,
  killApp,
  launchApp,
  nextBuild as _nextBuild,
  nextStart as _nextStart,
  renderViaHTTP,
  check,
} from 'next-test-utils'

import css from './css'

const nodeArgs = ['-r', join(__dirname, '../../react-18/test/require-hook.js')]
const appDir = join(__dirname, '../app')
const nativeModuleTestAppDir = join(__dirname, '../unsupported-native-module')
const distDir = join(__dirname, '../app/.next')
const documentPage = new File(join(appDir, 'pages/_document.jsx'))
const appPage = new File(join(appDir, 'pages/_app.js'))
const error500Page = new File(join(appDir, 'pages/500.js'))

const documentWithGip = `
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

Document.getInitialProps = (ctx) => {
  return ctx.defaultGetInitialProps(ctx)
}
`

const appWithGlobalCss = `
import '../styles.css'

function App({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default App
`

const page500 = `
export default function Page500() {
  return 'custom-500-page'
}
`

async function nextBuild(dir) {
  return await _nextBuild(dir, [], {
    stdout: true,
    stderr: true,
    nodeArgs,
  })
}

async function nextStart(dir, port) {
  return await _nextStart(dir, port, {
    stdout: true,
    stderr: true,
    nodeArgs,
  })
}

async function nextDev(dir, port) {
  return await launchApp(dir, port, {
    stdout: true,
    stderr: true,
    nodeArgs,
  })
}

async function resolveStreamResponse(response, onData) {
  let result = ''
  onData = onData || (() => {})
  await new Promise((resolve) => {
    response.body.on('data', (chunk) => {
      result += chunk.toString()
      onData(chunk.toString(), result)
    })

    response.body.on('end', resolve)
  })
  return result
}

describe('concurrentFeatures - basic', () => {
  it('should warn user for experimental risk with server components', async () => {
    const edgeRuntimeWarning =
      'You are using the experimental Edge Runtime with `concurrentFeatures`.'
    const rscWarning = `You have experimental React Server Components enabled. Continue at your own risk.`
    const { stderr } = await nextBuild(appDir)
    expect(stderr).toContain(edgeRuntimeWarning)
    expect(stderr).toContain(rscWarning)
  })
  it('should warn user that native node APIs are not supported', async () => {
    const fsImportedErrorMessage =
      'Native Node.js APIs are not supported in the Edge Runtime with `concurrentFeatures` enabled. Found `dns` imported.'
    const { stderr } = await nextBuild(nativeModuleTestAppDir)
    expect(stderr).toContain(fsImportedErrorMessage)
  })
})

describe('concurrentFeatures - prod', () => {
  const context = { appDir }

  beforeAll(async () => {
    error500Page.write(page500)
    context.appPort = await findPort()
    await nextBuild(context.appDir)
    context.server = await nextStart(context.appDir, context.appPort)
  })
  afterAll(async () => {
    error500Page.delete()
    await killApp(context.server)
  })

  it('should generate rsc middleware manifests', async () => {
    const distServerDir = join(distDir, 'server')
    const hasFile = (filename) => fs.existsSync(join(distServerDir, filename))

    const files = [
      'middleware-build-manifest.js',
      'middleware-flight-manifest.js',
      'middleware-ssr-runtime.js',
      'middleware-manifest.json',
    ]
    files.forEach((file) => {
      expect(hasFile(file)).toBe(true)
    })
  })

  it('should have clientInfo in middleware manifest', async () => {
    const middlewareManifestPath = join(
      distDir,
      'server',
      'middleware-manifest.json'
    )
    const content = JSON.parse(
      await fs.readFile(middlewareManifestPath, 'utf8')
    )
    for (const item of [
      ['/', true],
      ['/next-api/image', true],
      ['/next-api/link', true],
      ['/routes/[dynamic]', true],
    ]) {
      expect(content.clientInfo).toContainEqual(item)
    }
    expect(content.clientInfo).not.toContainEqual([['/404', true]])
  })

  it('should support React.lazy and dynamic imports', async () => {
    const html = await renderViaHTTP(context.appPort, '/dynamic-imports')
    expect(html).toContain('foo.client')
  })

  runBasicTests(context, 'prod')
})

describe('concurrentFeatures - dev', () => {
  const context = { appDir }

  beforeAll(async () => {
    error500Page.write(page500)
    context.appPort = await findPort()
    context.server = await nextDev(context.appDir, context.appPort)
  })
  afterAll(async () => {
    error500Page.delete()
    await killApp(context.server)
  })

  it('should support React.lazy and dynamic imports', async () => {
    const html = await renderViaHTTP(context.appPort, '/dynamic-imports')
    expect(html).toContain('loading...')

    const browser = await webdriver(context.appPort, '/dynamic-imports')
    const content = await browser.eval(`window.document.body.innerText`)
    expect(content).toMatchInlineSnapshot('"foo.client"')
  })

  it('should not bundle external imports into client builds for RSC', async () => {
    const html = await renderViaHTTP(context.appPort, '/external-imports')
    expect(html).toContain('date:')

    const distServerDir = join(distDir, 'static', 'chunks', 'pages')
    const bundle = fs
      .readFileSync(join(distServerDir, 'external-imports.js'))
      .toString()

    expect(bundle).not.toContain('moment')
  })

  runBasicTests(context, 'dev')
})

const cssSuite = {
  runTests: css,
  before: () => appPage.write(appWithGlobalCss),
  after: () => appPage.delete(),
}

runSuite('CSS', 'dev', cssSuite)
runSuite('CSS', 'prod', cssSuite)

const documentSuite = {
  runTests: (context) => {
    it('should error when custom _document has getInitialProps method', async () => {
      const res = await fetchViaHTTP(context.appPort, '/')
      const html = await res.text()

      expect(res.status).toBe(500)
      expect(html).toContain(
        'Error: `getInitialProps` in Document component is not supported with `concurrentFeatures` enabled.'
      )
    })
  },
  before: () => documentPage.write(documentWithGip),
  after: () => documentPage.delete(),
}

runSuite('document', 'dev', documentSuite)
runSuite('document', 'prod', documentSuite)

async function runBasicTests(context, env) {
  const isDev = env === 'dev'
  it('should render the correct html', async () => {
    const homeHTML = await renderViaHTTP(context.appPort, '/', null, {
      headers: {
        'x-next-test-client': 'test-util',
      },
    })

    // should have only 1 DOCTYPE
    expect(homeHTML).toMatch(/^<!DOCTYPE html><html/)

    // dynamic routes
    const dynamicRouteHTML1 = await renderViaHTTP(
      context.appPort,
      '/routes/dynamic1'
    )
    const dynamicRouteHTML2 = await renderViaHTTP(
      context.appPort,
      '/routes/dynamic2'
    )

    const path404HTML = await renderViaHTTP(context.appPort, '/404')
    const path500HTML = await renderViaHTTP(context.appPort, '/err')
    const pathNotFoundHTML = await renderViaHTTP(
      context.appPort,
      '/this-is-not-found'
    )

    expect(homeHTML).toContain('component:index.server')
    expect(homeHTML).toContain('env:env_var_test')
    expect(homeHTML).toContain('header:test-util')
    expect(homeHTML).toContain('path:/')
    expect(homeHTML).toContain('foo.client')

    expect(dynamicRouteHTML1).toContain('[pid]')
    expect(dynamicRouteHTML2).toContain('[pid]')

    expect(path404HTML).toContain('custom-404-page')
    // in dev mode: custom error page is still using default _error
    expect(path500HTML).toContain(
      isDev ? 'Internal Server Error' : 'custom-500-page'
    )
    expect(pathNotFoundHTML).toContain('custom-404-page')
  })

  it('should support next/link', async () => {
    const linkHTML = await renderViaHTTP(context.appPort, '/next-api/link')
    const $ = cheerio.load(linkHTML)
    const linkText = $('div[hidden] > a[href="/"]').text()

    expect(linkText).toContain('go home')

    const browser = await webdriver(context.appPort, '/next-api/link')
    await browser.eval('window.beforeNav = 1')
    await browser.elementByCss('#next_id').click()
    await browser.elementByCss('#next_id').click()
    await check(() => browser.waitForElementByCss('#query').text(), /query:2/)

    if (!isDev) {
      // this might do a hard navigation in development
      // so only require this check in production
      expect(await browser.eval('window.beforeNav')).toBe(1)
    }
  })

  it('should suspense next/image on server side', async () => {
    const imageHTML = await renderViaHTTP(context.appPort, '/next-api/image')
    const $ = cheerio.load(imageHTML)
    const imageTag = $('div[hidden] > span > span > img')

    expect(imageTag.attr('src')).toContain('data:image')
  })

  it('should support multi-level server component imports', async () => {
    const html = await renderViaHTTP(context.appPort, '/multi')
    expect(html).toContain('bar.server.js:')
    expect(html).toContain('foo.client')
  })

  it('should support streaming', async () => {
    await fetchViaHTTP(context.appPort, '/streaming', null, {}).then(
      async (response) => {
        let gotFallback = false
        let gotData = false

        await resolveStreamResponse(response, (_, result) => {
          gotData = result.includes('next_streaming_data')
          if (!gotFallback) {
            gotFallback = result.includes('next_streaming_fallback')
            if (gotFallback) {
              expect(gotData).toBe(false)
            }
          }
        })

        expect(gotFallback).toBe(true)
        expect(gotData).toBe(true)
      }
    )

    // Should end up with "next_streaming_data".
    const browser = await webdriver(context.appPort, '/streaming')
    const content = await browser.eval(`window.document.body.innerText`)
    expect(content).toMatchInlineSnapshot('"next_streaming_data"')
  })

  it('should support streaming flight request', async () => {
    await fetchViaHTTP(context.appPort, '/?__flight__=1').then(
      async (response) => {
        const result = await resolveStreamResponse(response)
        expect(result).toContain('component:index.server')
      }
    )
  })

  it('should support api routes', async () => {
    const res = await renderViaHTTP(context.appPort, '/api/ping')
    expect(res).toContain('pong')
  })
}

function runSuite(suiteName, env, { runTests, before, after }) {
  const context = { appDir }
  describe(`${suiteName} ${env}`, () => {
    if (env === 'prod') {
      beforeAll(async () => {
        before?.()
        context.appPort = await findPort()
        context.server = await nextDev(context.appDir, context.appPort)
      })
    }
    if (env === 'dev') {
      beforeAll(async () => {
        before?.()
        context.appPort = await findPort()
        context.server = await nextDev(context.appDir, context.appPort)
      })
    }
    afterAll(async () => {
      after?.()
      await killApp(context.server)
    })

    runTests(context, env)
  })
}
<|endoftext|>


<commit_before>describe('Map Control Test', function () {

  var el;
  var testMap;
  var spy;
  var hasWebGL;

  before(function () {
    el = document.createElement('div');
    el.style.cssText = 'position: absolute; left: 0; top: 0; width: 100%; height: 100%;';
    document.body.appendChild(el);
    L.Mapzen.apiKey = 'mapzen-cstHyBQ';
    testMap = L.Mapzen.map(el);
    spy = sinon.spy();
    testMap.addEventListener('tangramloaded', spy);
    testMap.setView([51.505, -0.09], 13);
    hasWebGL = testMap._tangram._hasWebGL();

  });

  after(function () {
    el.parentNode.removeChild(el);
  })

  describe('Leaflet Versions', function () {
    it('check which Leaflet version it is', function () {
      expect(L.version).to.equal('1.2.0');
    });
  });

  describe('Tangram layer check', function () {
    it('checks default style is set.', function (done) {
      if (hasWebGL) {
        if (map.getTangramLayer()) done();
      } else {
        // skip test if webgl is not available
        done();
      }
    });

    it('checks Tangram Event', function (done) {
      var tangramEventCheck = function () {
        if(hasWebGL) {
          if(spy.called) done();
          else return setTimeout(tangramEventCheck.bind(this), 200);
        } else done();
      }
      tangramEventCheck();
    })
  })
});
<commit_msg>fixed test<commit_after>describe('Map Control Test', function () {

  var el;
  var testMap;
  var spy;
  var hasWebGL;

  before(function () {
    el = document.createElement('div');
    el.style.cssText = 'position: absolute; left: 0; top: 0; width: 100%; height: 100%;';
    document.body.appendChild(el);
    L.Mapzen.apiKey = 'mapzen-cstHyBQ';
    testMap = L.Mapzen.map(el);
    spy = sinon.spy();
    testMap.addEventListener('tangramloaded', spy);
    testMap.setView([51.505, -0.09], 13);
    hasWebGL = testMap._tangram._hasWebGL();

  });

  after(function () {
    el.parentNode.removeChild(el);
  })

  describe('Leaflet Versions', function () {
    it('check which Leaflet version it is', function () {
      expect(L.version).to.equal('1.2.0');
    });
  });

  describe('Tangram layer check', function () {
    it('checks default style is set.', function (done) {
      if (hasWebGL) {
        if (testMap.getTangramLayer()) done();
      } else {
        // skip test if webgl is not available
        done();
      }
    });
    // Tangram event is deprectaed; remove in v1.0
    it('checks Tangram Event', function (done) {
      var tangramEventCheck = function () {
        if(hasWebGL) {
          if(spy.called) done();
          else return setTimeout(tangramEventCheck.bind(this), 200);
        } else done();
      }
      tangramEventCheck();
    })
  })
});
<|endoftext|>


<commit_before>// Script for making our demo work
// Load in our dependencies
var sinonUtils = require('../../test/utils/sinon');
var xhrResponses = require('../../test/test-files/http-responses/xhr');

// Define Mocha hook mocks
global.before = function (fn) { fn(); };
global.after = function (fn) { /* Don't run after functions */ };

// Start a Sinon server that approves all images
sinonUtils.mockXHR([xhrResponses.UPDATE_IMAGE_SET_APPROVE]);

// Load in normal script
void require('./index.js');
<commit_msg>Fixed up general image acceptance<commit_after>// Script for making our demo work
// Load in our dependencies
var ImageSet = require('./image-set');
var sinonUtils = require('../../test/utils/sinon');
var xhrResponses = require('../../test/test-files/http-responses/xhr');

// Define Mocha hook mocks
global.before = function (fn) { fn(); };
global.after = function (fn) { /* Don't run after functions */ };

// Start a Sinon server that approves all images
sinonUtils.mockXHR([xhrResponses.UPDATE_IMAGE_SET_APPROVE]);

// Mock over cachebustImg to always swap images to `current` variant
sinonUtils.stub(ImageSet, 'cachebustImg', function cachebustImgstub (imgEl) {
  // images/ref%2Froot.large.png -> images/current%2Froot.large.png
  var originalSrc = imgEl.getAttribute('src');
  var newSrc = originalSrc.replace(/images\/(ref|diff)/, 'images/current');
  imgEl.setAttribute('src', newSrc);
});

// Load in normal script
void require('./index.js');
<|endoftext|>


<commit_before>'use strict';

// MODULES //

var tape = require( 'tape' );
var pow = require( '@stdlib/math/base/special/pow' );
var FLOAT64_SMALLEST_SUBNORMAL = require( './../lib' );

// TESTS //

tape( 'the main export is a number', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof FLOAT64_SMALLEST_SUBNORMAL, 'number', 'main export is a number' );
	t.end();
});

tape( 'the exported value equals the smallest double-precision floating-point denormalized value', function test( t ) {
	t.equal( FLOAT64_SMALLEST_SUBNORMAL, pow(2,-1022)*pow(2,-52), 'equals 2**-1022 * 2**-52' );
	t.end();
});
<commit_msg>Fix lint errors<commit_after>'use strict';

// MODULES //

var tape = require( 'tape' );
var pow = require( '@stdlib/math/base/special/pow' );
var FLOAT64_SMALLEST_SUBNORMAL = require( './../lib' );

// TESTS //

tape( 'the main export is a number', function test( t ) {
	t.ok( true, __filename );
	t.equal( typeof FLOAT64_SMALLEST_SUBNORMAL, 'number', 'main export is a number' );
	t.end();
});

tape( 'the exported value equals the smallest double-precision floating-point denormalized value', function test( t ) {
	t.equal( FLOAT64_SMALLEST_SUBNORMAL, pow(2, -1022)*pow(2, -52), 'equals 2**-1022 * 2**-52' );
	t.end();
});
<|endoftext|>


<commit_before>'use strict';

// MODULES //

var constantFunction = require( '@stdlib/utils/constant-function' );
var degenerate = require( '@stdlib/math/base/dists/degenerate/quantile' ).factory;
var erfinv = require( '@stdlib/math/base/special/erfinv' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var sqrt = require( '@stdlib/math/base/special/sqrt' );

// MAIN //

/**
* Returns a function for evaluating the quantile function of a normal distribution.
*
* @param {number} mu - mean
* @param {NonNegativeNumber} sigma - standard deviation
* @returns {Function} quantile function
*
* @example
* var quantile = factory( 10.0, 2.0 );
* var y = quantile( 0.5 );
* // returns 10.0
*
* y = quantile( 0.8 );
* // returns ~11.683
*/
function factory( mu, sigma ) {
	var A = mu;
	var B = sigma * sqrt( 2.0 );

	if ( isnan( mu ) || isnan( sigma ) || sigma < 0.0 ) {
		return constantFunction( NaN );
	}
	if ( sigma === 0.0 ) {
		degenerate( mu );
	}
	return quantile;

	/**
	* Evaluates the quantile function for a normal distribution.
	*
	* @private
	* @param {Probability} p - input value
	* @returns {number} evaluated quantile function
	*
	* @example
	* var y = quantile( 0.3 );
	* // returns <number>
	*/
	function quantile( p ) {
		if ( isnan( p ) || p < 0.0 || p > 1.0 ) {
			return NaN;
		}
		return A + ( B * erfinv( (2.0*p) - 1.0 ) );
	} // end FUNCTION quantile()
} // end FUNCTION factory()

// EXPORTS //

module.exports = factory;
<commit_msg>Move statements to avoid unnecessary computation for invalid parameters<commit_after>'use strict';

// MODULES //

var constantFunction = require( '@stdlib/utils/constant-function' );
var degenerate = require( '@stdlib/math/base/dists/degenerate/quantile' ).factory;
var erfinv = require( '@stdlib/math/base/special/erfinv' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var sqrt = require( '@stdlib/math/base/special/sqrt' );

// MAIN //

/**
* Returns a function for evaluating the quantile function of a normal distribution.
*
* @param {number} mu - mean
* @param {NonNegativeNumber} sigma - standard deviation
* @returns {Function} quantile function
*
* @example
* var quantile = factory( 10.0, 2.0 );
* var y = quantile( 0.5 );
* // returns 10.0
*
* y = quantile( 0.8 );
* // returns ~11.683
*/
function factory( mu, sigma ) {
	var A;
	var B;
	if ( isnan( mu ) || isnan( sigma ) || sigma < 0.0 ) {
		return constantFunction( NaN );
	}
	if ( sigma === 0.0 ) {
		degenerate( mu );
	}
	A = mu;
	B = sigma * sqrt( 2.0 );
	return quantile;

	/**
	* Evaluates the quantile function for a normal distribution.
	*
	* @private
	* @param {Probability} p - input value
	* @returns {number} evaluated quantile function
	*
	* @example
	* var y = quantile( 0.3 );
	* // returns <number>
	*/
	function quantile( p ) {
		if ( isnan( p ) || p < 0.0 || p > 1.0 ) {
			return NaN;
		}
		return A + ( B * erfinv( (2.0*p) - 1.0 ) );
	} // end FUNCTION quantile()
} // end FUNCTION factory()

// EXPORTS //

module.exports = factory;
<|endoftext|>