<commit_before>/* Marketplace Stats Line Charts
--------------------------------
 * Dependencies: d3.js
 *
 * Some definitions:
 * -----------------
 * series: individual chart line.
 * tooltip: tooltip wrapper div.
 * graphline: container of the SVG tooltip circles and path.
 *
 */
define('linechart', ['log'], function(log) {
    var console = log('linechart');

    function createLineChart(lbls, options) {
        var opts = {
            container: document.getElementById('chart'),
            forceZeroMin: true,
            height: 440,
            lineLabels: false, // Append line labels to the end of each line?
            tickPadding: 8, // Axes distance from their tick labels (in px).
            url: 'http://localhost:5000/api/v1/apps/bah/statistics/',
            width: 950
        };

        // Almost like $.extend() except worse and no recursion so don't nest!
        for (var prop in options) {
            opts[prop] = options[prop];
        }

        $(opts.container).empty();

        var margin = {top: 20, right: 30, bottom: 40, left: 50},
            width = opts.width - margin.left - margin.right,
            height = opts.height - margin.top - margin.bottom;

        var parseDate = d3.time.format('%Y-%m-%d').parse;
        var formatTime = d3.time.format('%a, %b %e, %Y');
        var xAxisTimeFormat = d3.time.format('%b %e');

        var x = d3.time.scale().range([0, width]);
        var y = d3.scale.linear().range([height, 0]);

        var color = d3.scale.category10();

        var xAxis = d3.svg.axis().scale(x).orient('bottom')
                      .tickFormat(xAxisTimeFormat)
                      .tickPadding(opts.tickPadding);
                      //.ticks(d3.time.weeks, 1);

        var yAxis = d3.svg.axis().scale(y).orient('left')
                      .tickPadding(opts.tickPadding);

        var line = d3.svg.line()
                     .interpolate('monotone')
                     .x(function(d) {return x(d.date);})
                     .y(function(d) {return y(d.count);})
                     // Drops null values from series.
                     .defined(function(d) {return d.count !== null;});

        var svg = d3.select(opts.container).append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                        .attr('transform',
                              'translate(' + margin.left + ',' + margin.top + ')');

        var tooltip = d3.select(opts.container).append('div')
                        .attr('class', 'tooltip');

        function isNullSeries(vals) {
            for (var i = 0; i < vals.length; i++) {
                if (vals[i].count !== null) return false;
            }
            return true;
        }

        function getSeriesName(s) {return s.name;}
        function getSeriesColor(s) {return color(s.name);}

        d3.json(opts.url, function(error, data) {
            var series = [],
                dates = [], // to store 'extent' for dates
                valAxis = [],
                legendSeries = [];
            var legend;

            color.domain(d3.keys(data));

            // `item` is the key of each line (series).
            for (item in data) {
                console.log('Reading graph: ', item);
                data[item].forEach(function(d) {
                    d.date = parseDate(d.date);
                    dates.push(d.date);
                });
                // Populate each series' name.
                series = color.domain().map(function(name) {
                    return {name: name};
                });
            }

            x.domain(d3.extent(dates, function(d) {
                return d;
            }));

            // Populate each series' values.
            for (var i = 0; i < series.length; i++) {
                series[i].values = data[series[i].name].map(function(d) {
                    return {date: d.date, count: d.count};
                });
                if (!series[i].values.length || isNullSeries(series[i].values)) {
                    console.log('Found empty series: ', series[i].name);
                } else {
                    legendSeries.push(series[i]);
                }
            }

            console.log('Series is: ', series);

            $('.chartcloak').toggle(legendSeries.length === 0);

            function getMinValue() {
                return d3.min(series, function(c) {
                    return d3.min(c.values, function(v) {return v.count;});
                });
            }
            function getMaxValue() {
                return d3.max(series, function(c) {
                    return d3.max(c.values, function(v) {return v.count;});
                });
            }

            console.log('Minumum value found is: ', getMinValue());
            console.log('Maximum value found is: ', getMaxValue());

            y.domain([opts.forceZeroMin ? 0 : getMinValue(), getMaxValue()]);

            svg.append('g')
               .attr('class', 'x axis')
               .attr('transform', 'translate(0,' + height + ')')
               .call(xAxis);

            valAxis = svg.append('g').attr('class', 'y axis').call(yAxis);
            valAxis.append('text')
                   .attr('transform', 'rotate(-90)')
                   .attr('y', 6)
                   .attr('dy', '5px')
                   .style('text-anchor', 'end')
                   .text(lbls.yAxis);

            var graphline = svg.selectAll('.graphline').data(series)
                               .enter()
                               .append('g')
                               .attr('class', function(d) {
                                    return 'graphline ' + d.name;
                               });

            // Inject tooltips while hiding `null` values.
            for (i = 0; i < series.length; i++) {
                d3.select(graphline[0][i]).selectAll('dot')
                          .data(series[i].values)
                          .enter()
                            .append('circle')
                            .attr('r', 3)
                            .attr('cx', function(d) {return x(d.date);})
                            .attr('cy', function(d) {return y(d.count);})
                            .style('display', function(d) {
                                if (d.count === null) return 'none';
                            })
                            .style('fill', function(d) {return color(series[i].name);})
                            .attr('class', function(d) {return series[i].name;})
                            .on('mouseover', function(d) {
                                tooltip.transition()
                                       .duration(200)
                                       .style('opacity', .9)
                                       .style('border', '2px solid ' + this.style.fill);
                                tooltip.html('
' +
                                                formatTime(d.date) +
                                                '
' + '' + lbls.tooltipValue + ': ' + d.count)
                                    .style('left', (d3.mouse(this)[0] + 92) + 'px')
                                    .style('top', (d3.mouse(this)[1] + 125) + 'px');
                            })
                            .on('mouseout', function(d) {
                                tooltip.transition()
                                   .duration(500)
                                   .style('opacity', 0);
                            });
            }

            graphline.append('path')
                     .attr('class', 'line')
                     .attr('d', function(d) {return line(d.values);})
                     .style('stroke', getSeriesColor);

            if (opts.lineLabels) {
                graphline.append('text')
                         .datum(function(d) {
                            return {name: d.name, value: d.values[d.values.length - 1]};
                         })
                         .attr('transform', function(d) {
                            return 'translate(' + x(d.value.date) + ',' + y(d.value.count) + ')';
                         })
                         .attr('x', 10)
                         .attr('dy', '3px')
                         .text(getSeriesName);
            }

            if (series.length > 1 && legendSeries.length) {
                legend = d3.select(opts.container).append('div').attr('class', 'legend')
                                .attr('top', height + 45 + 'px');

                legend.selectAll('a')
                      .data(legendSeries)
                      .enter()
                        .append('a')
                        .style('color', getSeriesColor)
                        .attr('href', '#')
                        .attr('id', getSeriesName)
                        .text(getSeriesName)
                        .on('click', function(d, i) {
                            d3.event.preventDefault();
                            $('.graphline.' + getSeriesName(d)).toggle();
                            $(this).toggleClass('hidden');
                        })
                        .on('mouseover', function(d, i) {
                            // Toggling a class fails here.
                            $('.graphline.' + getSeriesName(d) + ' .line').css('stroke-width', '2.5px');
                        })
                        .on('mouseleave', function(d, i) {
                            $('.graphline.' + getSeriesName(d) + ' .line').css('stroke-width', '1.5px');
                        });
            }
        });
    }

    return {'createLineChart': createLineChart};
});

<commit_msg>fix tooltip position based on previous parent resize<commit_after>/* Marketplace Stats Line Charts
--------------------------------
 * Dependencies: d3.js
 *
 * Some definitions:
 * -----------------
 * series: individual chart line.
 * tooltip: tooltip wrapper div.
 * graphline: container of the SVG tooltip circles and path.
 *
 */
define('linechart', ['log'], function(log) {
    var console = log('linechart');

    function createLineChart(lbls, options) {
        var opts = {
            container: document.getElementById('chart'),
            forceZeroMin: true,
            height: 440,
            lineLabels: false, // Append line labels to the end of each line?
            tickPadding: 8, // Axes distance from their tick labels (in px).
            url: 'http://localhost:5000/api/v1/apps/bah/statistics/',
            width: 950
        };

        // Almost like $.extend() except worse and no recursion so don't nest!
        for (var prop in options) {
            opts[prop] = options[prop];
        }

        $(opts.container).empty();

        var margin = {top: 20, right: 30, bottom: 40, left: 50},
            width = opts.width - margin.left - margin.right,
            height = opts.height - margin.top - margin.bottom;

        var parseDate = d3.time.format('%Y-%m-%d').parse;
        var formatTime = d3.time.format('%a, %b %e, %Y');
        var xAxisTimeFormat = d3.time.format('%b %e');

        var x = d3.time.scale().range([0, width]);
        var y = d3.scale.linear().range([height, 0]);

        var color = d3.scale.category10();

        var xAxis = d3.svg.axis().scale(x).orient('bottom')
                      .tickFormat(xAxisTimeFormat)
                      .tickPadding(opts.tickPadding);
                      //.ticks(d3.time.weeks, 1);

        var yAxis = d3.svg.axis().scale(y).orient('left')
                      .tickPadding(opts.tickPadding);

        var line = d3.svg.line()
                     .interpolate('monotone')
                     .x(function(d) {return x(d.date);})
                     .y(function(d) {return y(d.count);})
                     // Drops null values from series.
                     .defined(function(d) {return d.count !== null;});

        var svg = d3.select(opts.container).append('svg')
                    .attr('width', width + margin.left + margin.right)
                    .attr('height', height + margin.top + margin.bottom)
                    .append('g')
                        .attr('transform',
                              'translate(' + margin.left + ',' + margin.top + ')');

        var tooltip = d3.select(opts.container).append('div')
                        .attr('class', 'tooltip');

        function isNullSeries(vals) {
            for (var i = 0; i < vals.length; i++) {
                if (vals[i].count !== null) return false;
            }
            return true;
        }

        function getSeriesName(s) {return s.name;}
        function getSeriesColor(s) {return color(s.name);}

        d3.json(opts.url, function(error, data) {
            var series = [],
                dates = [], // to store 'extent' for dates
                valAxis = [],
                legendSeries = [];
            var legend;

            color.domain(d3.keys(data));

            // `item` is the key of each line (series).
            for (item in data) {
                console.log('Reading graph: ', item);
                data[item].forEach(function(d) {
                    d.date = parseDate(d.date);
                    dates.push(d.date);
                });
                // Populate each series' name.
                series = color.domain().map(function(name) {
                    return {name: name};
                });
            }

            x.domain(d3.extent(dates, function(d) {
                return d;
            }));

            // Populate each series' values.
            for (var i = 0; i < series.length; i++) {
                series[i].values = data[series[i].name].map(function(d) {
                    return {date: d.date, count: d.count};
                });
                if (!series[i].values.length || isNullSeries(series[i].values)) {
                    console.log('Found empty series: ', series[i].name);
                } else {
                    legendSeries.push(series[i]);
                }
            }

            console.log('Series is: ', series);

            $('.chartcloak').toggle(legendSeries.length === 0);

            function getMinValue() {
                return d3.min(series, function(c) {
                    return d3.min(c.values, function(v) {return v.count;});
                });
            }
            function getMaxValue() {
                return d3.max(series, function(c) {
                    return d3.max(c.values, function(v) {return v.count;});
                });
            }

            console.log('Minumum value found is: ', getMinValue());
            console.log('Maximum value found is: ', getMaxValue());

            y.domain([opts.forceZeroMin ? 0 : getMinValue(), getMaxValue()]);

            svg.append('g')
               .attr('class', 'x axis')
               .attr('transform', 'translate(0,' + height + ')')
               .call(xAxis);

            valAxis = svg.append('g').attr('class', 'y axis').call(yAxis);
            valAxis.append('text')
                   .attr('transform', 'rotate(-90)')
                   .attr('y', 6)
                   .attr('dy', '5px')
                   .style('text-anchor', 'end')
                   .text(lbls.yAxis);

            var graphline = svg.selectAll('.graphline').data(series)
                               .enter()
                               .append('g')
                               .attr('class', function(d) {
                                    return 'graphline ' + d.name;
                               });

            // Inject tooltips while hiding `null` values.
            for (i = 0; i < series.length; i++) {
                d3.select(graphline[0][i]).selectAll('dot')
                          .data(series[i].values)
                          .enter()
                            .append('circle')
                            .attr('r', 3)
                            .attr('cx', function(d) {return x(d.date);})
                            .attr('cy', function(d) {return y(d.count);})
                            .style('display', function(d) {
                                if (d.count === null) return 'none';
                            })
                            .style('fill', function(d) {return color(series[i].name);})
                            .attr('class', function(d) {return series[i].name;})
                            .on('mouseover', function(d) {
                                tooltip.transition()
                                       .duration(200)
                                       .style('opacity', .9)
                                       .style('border', '2px solid ' + this.style.fill);
                                tooltip.html('
' +
                                                formatTime(d.date) +
                                                '
' + '' + lbls.tooltipValue + ': ' + d.count)
                                    .style('left', (d3.mouse(this)[0] + 95) + 'px')
                                    .style('top', (d3.mouse(this)[1] + 130) + 'px');
                            })
                            .on('mouseout', function(d) {
                                tooltip.transition()
                                   .duration(500)
                                   .style('opacity', 0);
                            });
            }

            graphline.append('path')
                     .attr('class', 'line')
                     .attr('d', function(d) {return line(d.values);})
                     .style('stroke', getSeriesColor);

            if (opts.lineLabels) {
                graphline.append('text')
                         .datum(function(d) {
                            return {name: d.name, value: d.values[d.values.length - 1]};
                         })
                         .attr('transform', function(d) {
                            return 'translate(' + x(d.value.date) + ',' + y(d.value.count) + ')';
                         })
                         .attr('x', 10)
                         .attr('dy', '3px')
                         .text(getSeriesName);
            }

            if (series.length > 1 && legendSeries.length) {
                legend = d3.select(opts.container).append('div').attr('class', 'legend')
                                .attr('top', height + 45 + 'px');

                legend.selectAll('a')
                      .data(legendSeries)
                      .enter()
                        .append('a')
                        .style('color', getSeriesColor)
                        .attr('href', '#')
                        .attr('id', getSeriesName)
                        .text(getSeriesName)
                        .on('click', function(d, i) {
                            d3.event.preventDefault();
                            $('.graphline.' + getSeriesName(d)).toggle();
                            $(this).toggleClass('hidden');
                        })
                        .on('mouseover', function(d, i) {
                            // Toggling a class fails here.
                            $('.graphline.' + getSeriesName(d) + ' .line').css('stroke-width', '2.5px');
                        })
                        .on('mouseleave', function(d, i) {
                            $('.graphline.' + getSeriesName(d) + ' .line').css('stroke-width', '1.5px');
                        });
            }
        });
    }

    return {'createLineChart': createLineChart};
});

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Kagilum SAS.
 *
 * This file is part of iceScrum.
 *
 * iceScrum is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * iceScrum is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Nicolas Noullet (nnoullet@kagilum.com)
 *
 */

extensibleController('backlogDetailsCtrl', ['$scope', 'StoryService', 'BacklogService', 'backlog', function($scope, StoryService, BacklogService, backlog) {
    // Functions
    $scope.refreshBacklog = function(backlog) {
        $scope.stories = BacklogService.filterStories(backlog, $scope.project.stories);
    };
    // Init
    $scope.backlog = backlog;
    $scope.refreshBacklog(backlog);
    // Ensures that the stories list of displayed backlogs are up to date
    $scope.$on('is:backlogsUpdated', function(event, backlogCodes) {
        if (_.includes(backlogCodes, backlog.code)) {
            $scope.refreshBacklog(backlog);
        }
    });
}]);

extensibleController('backlogCtrl', ['$controller', '$scope', 'window', '$filter', '$timeout', '$state', 'StoryService', 'BacklogService', 'BacklogCodes', 'StoryStatesByName', 'project', 'backlogs', function($controller, $scope, window, $filter, $timeout, $state, StoryService, BacklogService, BacklogCodes, StoryStatesByName, project, backlogs) {
    $controller('windowCtrl', {$scope: $scope, window: window}); // inherit from windowCtrl
    // Functions
    $scope.authorizedStory = StoryService.authorizedStory;
    $scope.isSelected = function(selectable) {
        if ($state.params.storyId) {
            return $state.params.storyId === selectable.id;
        } else if ($state.params.storyListId) {
            return _.includes($state.params.storyListId.split(','), selectable.id.toString());
        } else {
            return false;
        }
    };
    $scope.hasSelected = function() {
        return $state.params.storyId !== undefined || $state.params.storyListId !== undefined;
    };
    $scope.toggleSelectableMultiple = function() {
        $scope.selectableOptions.selectingMultiple = !$scope.selectableOptions.selectingMultiple;
        if ($state.params.storyListId !== undefined) {
            var currentStateName = $state.current.name;
            var storyIndexInStateName = currentStateName.indexOf('story');
            $state.go(currentStateName.slice(0, storyIndexInStateName - 1));
        }
    };
    $scope.refreshSingleBacklog = function(backlogContainer) {
        var backlog = backlogContainer.backlog;
        var filteredStories = BacklogService.filterStories(backlog, $scope.project.stories);
        backlog.stories = $filter('orderBy')(filteredStories, 'rank'); // This will be the model for the sortable directive so it must be in sort order, regardless of current display order
        if (backlog.stories && backlog.stories.length > 0) {
            backlogContainer.storiesLoaded = true; // To render stories already there in the client
        }
        backlogContainer.sortable = StoryService.authorizedStory('rank') && (BacklogService.isBacklog(backlog) || BacklogService.isSandbox(backlog));
        $timeout(function() { // Timeout to wait for story rendering
            $scope.$emit('selectable-refresh');
        }, 0);
    };
    var getValueEffortRateForSorting = function(story) {
        var rate = -3; // Rate = -3 when no effort (null) & no value (0)
        if (story.value === 0) {
            if (story.effort !== null) {
                rate = -story.effort / 10000; // Rate spans from 0 to -1 (unless effort is > 1000, very unlikely), higher effort => lower rate
            }
        } else {
            if (story.effort === null) {
                rate = -1 / story.value - 1; // Rate spans from -2 to -1, higher value => higher rate
            } else {
                rate = story.value / story.effort; // Rate spans from 0 to Infinity, higher value compared to effort => higher rate
            }
        }
        return rate;
    };
    $scope.orderBacklogByRank = function(backlogContainer) {
        backlogContainer.orderBy.reverse = false;
        $scope.changeBacklogOrder(backlogContainer, _.find(backlogContainer.orderBy.values, {id: 'rank'}));
    };
    $scope.changeBacklogOrder = function(backlogContainer, order) {
        var newOrder = _.clone(order);
        newOrder.value = [order.value];
        if (order.id !== 'rank') {
            newOrder.value.push('id'); // Order by id is crucial to ensure stable order when duplicate value (e.g. same effort) regardless of storyService.list order which itself depends on navigation order
        }
        if (BacklogService.isAll(backlogContainer.backlog) && order.id === 'rank') { // Hack to ensure that rank sort in "All" backlog is consistent with individual backlog ranking
            var sortByStateGroupingByBacklogState = function(story) {
                var orderCriteria = story.state === StoryStatesByName.ESTIMATED ? StoryStatesByName.ACCEPTED : story.state; // Ignore the differences betweed accepted and estimated
                return -orderCriteria; // "minus" the state to make done stories more prioritary
            };
            newOrder.value.unshift(sortByStateGroupingByBacklogState);
        }
        backlogContainer.orderBy.current = newOrder;
    };
    $scope.reverseBacklogOrder = function(backlogContainer) {
        backlogContainer.orderBy.reverse = !backlogContainer.orderBy.reverse;
    };
    $scope.isSortingBacklog = function(backlogContainer) {
        return backlogContainer.sortable && backlogContainer.orderBy.current.id === 'rank' && !backlogContainer.orderBy.reverse && !$scope.hasContextOrSearch();
    };
    $scope.enableSortable = function(backlogContainer) {
        $scope.clearContextAndSearch();
        $scope.orderBacklogByRank(backlogContainer)
    };
    $scope.openStoryUrl = function(storyId) {
        return '#/' + $scope.viewName + '/' + $state.params.elementId + '/story/' + storyId;
    };

    $scope.closeBacklogUrl = function(backlog) {
        var stateParams;
        if (backlog.code === $state.params.pinnedElementId) {
            stateParams = {pinnedElementId: $state.params.elementId, elementId: null};
        } else {
            stateParams = {elementId: null};
        }
        return $state.href('.', stateParams);
    };
    $scope.getBacklogContainer = function(backlogCode) {
        return _.find($scope.backlogContainers, function(backlogContainer) {
            return backlogContainer.backlog.code === backlogCode;
        });
    };
    $scope.showBacklog = function(backlogCode) {
        var backlogContainer = $scope.getBacklogContainer(backlogCode);
        if (!backlogContainer) {
            backlogContainer = {
                backlog: _.find($scope.availableBacklogs, {code: backlogCode}),
                storiesLoaded: false,
                orderBy: {
                    values: _.sortBy($scope.sortOptions, 'name')
                }
            };
            $scope.orderBacklogByRank(backlogContainer);
            $scope.refreshSingleBacklog(backlogContainer); // Init the backlog from client data (storyService.list) + init sortable variable
            var setStoriesLoaded = function() {
                backlogContainer.storiesLoaded = true;
            };
            var retrieveServerStories = function() {
                StoryService.listByBacklog(backlogContainer.backlog).then(function(stories) {
                    //mask loading on empty backlog
                    if (stories.length === 0) {
                        setStoriesLoaded();
                    }
                });  // Retrieve server data, stories that were missing will be automatically added
            };
            if (backlogContainer.backlog.count > 500) {
                $scope.confirm({message: $scope.message('todo.is.ui.backlog.load.confirm'), callback: retrieveServerStories, closeCallback: setStoriesLoaded});
            } else {
                retrieveServerStories();
            }
            $scope.backlogContainers.push(backlogContainer);
            var savedBacklogsOrder = $scope.getWindowSetting('elementsListOrder');
            if (savedBacklogsOrder) {
                $scope.backlogContainers.sort(function(a, b) {
                    return savedBacklogsOrder.indexOf(a.backlog.code) - savedBacklogsOrder.indexOf(b.backlog.code)
                });
            } else {
                //keep old fashion way for all users that never reordered their backlog at least one time
                $scope.backlogContainers = _.sortBy($scope.backlogContainers, function(backlogContainer) {
                    return backlogContainer.backlog.id;
                });
            }
        }
    };
    $scope.$watchGroup([function() { return $state.$current.self.name; }, function() { return $state.params.pinnedElementId; }, function() { return $state.params.elementId; }], function(newValues) {
        var stateName = newValues[0];
        var pinnedElementId = newValues[1];
        var elementId = newValues[2];
        if (stateName === 'backlog') {
            $state.go('backlog.backlog', {elementId: _.head($scope.availableBacklogs).code}, {location: 'replace'});
        } else if (_.startsWith(stateName, 'backlog')) {
            if (pinnedElementId) {
                $scope.showBacklog(pinnedElementId);
            }
            if (elementId) {
                $scope.showBacklog(elementId);
            }
            _.remove($scope.backlogContainers, function(backlogContainer) {
                return !_.includes([pinnedElementId, elementId], backlogContainer.backlog.code);
            });
        }
    });
    $scope.openBacklogUrl = function(backlog) {
        var stateName = 'backlog.backlog';
        if ($state.current.name != 'backlog.backlog.details') {
            stateName += '.details';
        }
        return $state.href(stateName, {elementId: backlog.code});
    };
    // Init
    $scope.viewName = 'backlog';
    $scope.project = project;
    $scope.sortOptions = [
        {id: 'effort', value: 'effort', name: $scope.message('todo.is.ui.sort.effort')},
        {id: 'rank', value: 'rank', name: $scope.message('todo.is.ui.sort.rank')},
        {id: 'name', value: 'name', name: $scope.message('todo.is.ui.sort.name')},
        {id: 'tasks_count', value: 'tasks_count', name: $scope.message('todo.is.ui.sort.tasks')},
        {id: 'suggestedDate', value: 'suggestedDate', name: $scope.message('todo.is.ui.sort.date')},
        {id: 'feature.id', value: 'feature.id', name: $scope.message('todo.is.ui.sort.feature')},
        {id: 'value', value: 'value', name: $scope.message('todo.is.ui.sort.value')},
        {id: 'type', value: 'type', name: $scope.message('todo.is.ui.sort.type')},
        {id: 'state', value: 'state', name: $scope.message('todo.is.ui.sort.state')},
        {id: 'value/effort', value: getValueEffortRateForSorting, name: $scope.message('todo.is.ui.sort.value.effort.rate')}
    ];

    $scope.backlogSortableOptions = {
        itemMoved: function(event) {
            var story = event.source.itemScope.modelValue;
            var newRank = event.dest.index + 1;
            var sourceScope = event.source.sortableScope;
            var destScope = event.dest.sortableScope;
            var promise;
            if (BacklogService.isBacklog(sourceScope.backlogContainer.backlog) && BacklogService.isSandbox(destScope.backlogContainer.backlog)) {
                promise = StoryService.returnToSandbox(story, newRank);
            } else if (BacklogService.isSandbox(sourceScope.backlogContainer.backlog) && BacklogService.isBacklog(destScope.backlogContainer.backlog)) {
                promise = StoryService.acceptToBacklog(story, newRank);
            }
            if (promise) {
                promise.catch(function() {
                    $scope.revertSortable(event);
                });
            }
        },
        orderChanged: function(event) {
            var story = event.source.itemScope.modelValue;
            story.rank = event.dest.index + 1;
            StoryService.update(story).catch(function() {
                $scope.revertSortable(event);
            });
        },
        accept: function(sourceItemHandleScope, destSortableScope) {
            var sameSortable = sourceItemHandleScope.itemScope.sortableScope.sortableId === destSortableScope.sortableId;
            // We don't check more that the fact that the dest backlog is also sorting
            // because we know that the only backlogs that can be sorted (Sandbox & Backlog) can always be inter-sorted (accept <-> return to backlog)
            return sameSortable && destSortableScope.isSortingBacklog(destSortableScope.backlogContainer);
        }
    };
    $scope.sortableId = 'backlog';
    $scope.selectableOptions = {
        notSelectableSelector: '.action, button, a',
        allowMultiple: true,
        selectionUpdated: function(selectedIds) {
            var currentStateName = $state.current.name;
            var storyIndexInStateName = currentStateName.indexOf('story');
            if (selectedIds.length === 0) {
                if (storyIndexInStateName != -1) {
                    $state.go(currentStateName.slice(0, storyIndexInStateName - 1));
                }
            } else {
                var stateName;
                var stateParams;
                if (_.startsWith(currentStateName, 'backlog.backlog')) {
                    stateName = 'backlog.backlog.story'
                } else if (_.startsWith(currentStateName, 'backlog.multiple')) {
                    stateName = 'backlog.multiple.story'
                }
                if (selectedIds.length === 1) {
                    stateName += '.details' + ($state.params.storyTabId ? '.tab' : '');
                    stateParams = {storyId: selectedIds};
                } else {
                    stateName += '.multiple';
                    stateParams = {storyListId: selectedIds.join(",")};
                }
                $state.go(stateName, stateParams);
            }
        }
    };
    $scope.backlogContainers = [];
    $scope.availableBacklogs = backlogs;
    $scope.backlogCodes = BacklogCodes;
    var backlogsUpdatedTimeout;
    // Ensures that the stories list of displayed backlogs are up to date
    $scope.$on('is:backlogsUpdated', function(event, backlogCodes) {
        _.each(backlogCodes, function(backlogCode) {
            var backlogContainer = $scope.getBacklogContainer(backlogCode);
            if (backlogContainer) {
                if (backlogsUpdatedTimeout) {
                    $timeout.cancel(backlogsUpdatedTimeout);
                }
                backlogsUpdatedTimeout = $timeout(function() {
                    $scope.refreshSingleBacklog(backlogContainer);
                }, 500);
            }
        });
    });
}]);<commit_msg>T386-0 Improve backlog loading performances & fix loading of multiple backlogs on refresh<commit_after>/*
 * Copyright (c) 2014 Kagilum SAS.
 *
 * This file is part of iceScrum.
 *
 * iceScrum is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License.
 *
 * iceScrum is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iceScrum.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *
 * Vincent Barrier (vbarrier@kagilum.com)
 * Nicolas Noullet (nnoullet@kagilum.com)
 *
 */

extensibleController('backlogDetailsCtrl', ['$scope', 'StoryService', 'BacklogService', 'backlog', function($scope, StoryService, BacklogService, backlog) {
    // Functions
    $scope.refreshBacklog = function(backlog) {
        $scope.stories = BacklogService.filterStories(backlog, $scope.project.stories);
    };
    // Init
    $scope.backlog = backlog;
    $scope.refreshBacklog(backlog);
    // Ensures that the stories list of displayed backlogs are up to date
    $scope.$on('is:backlogsUpdated', function(event, backlogCodes) {
        if (_.includes(backlogCodes, backlog.code)) {
            $scope.refreshBacklog(backlog);
        }
    });
}]);

extensibleController('backlogCtrl', ['$controller', '$scope', 'window', '$filter', '$timeout', '$state', 'StoryService', 'BacklogService', 'BacklogCodes', 'StoryStatesByName', 'project', 'backlogs', function($controller, $scope, window, $filter, $timeout, $state, StoryService, BacklogService, BacklogCodes, StoryStatesByName, project, backlogs) {
    $controller('windowCtrl', {$scope: $scope, window: window}); // inherit from windowCtrl
    // Functions
    $scope.authorizedStory = StoryService.authorizedStory;
    $scope.isSelected = function(selectable) {
        if ($state.params.storyId) {
            return $state.params.storyId === selectable.id;
        } else if ($state.params.storyListId) {
            return _.includes($state.params.storyListId.split(','), selectable.id.toString());
        } else {
            return false;
        }
    };
    $scope.hasSelected = function() {
        return $state.params.storyId !== undefined || $state.params.storyListId !== undefined;
    };
    $scope.toggleSelectableMultiple = function() {
        $scope.selectableOptions.selectingMultiple = !$scope.selectableOptions.selectingMultiple;
        if ($state.params.storyListId !== undefined) {
            var currentStateName = $state.current.name;
            var storyIndexInStateName = currentStateName.indexOf('story');
            $state.go(currentStateName.slice(0, storyIndexInStateName - 1));
        }
    };
    $scope.refreshSingleBacklog = function(backlogContainer) {
        var backlog = backlogContainer.backlog;
        var filteredStories = BacklogService.filterStories(backlog, $scope.project.stories);
        backlog.stories = $filter('orderBy')(filteredStories, 'rank'); // This will be the model for the sortable directive so it must be in sort order, regardless of current display order
        if (backlog.stories && backlog.stories.length > 0) {
            backlogContainer.storiesLoaded = true; // Render stories already there in the client cache
        }
        backlogContainer.sortable = StoryService.authorizedStory('rank') && (BacklogService.isBacklog(backlog) || BacklogService.isSandbox(backlog));
        $timeout(function() { // Timeout to wait for story rendering
            $scope.$emit('selectable-refresh');
        }, 0);
    };
    var getValueEffortRateForSorting = function(story) {
        var rate = -3; // Rate = -3 when no effort (null) & no value (0)
        if (story.value === 0) {
            if (story.effort !== null) {
                rate = -story.effort / 10000; // Rate spans from 0 to -1 (unless effort is > 1000, very unlikely), higher effort => lower rate
            }
        } else {
            if (story.effort === null) {
                rate = -1 / story.value - 1; // Rate spans from -2 to -1, higher value => higher rate
            } else {
                rate = story.value / story.effort; // Rate spans from 0 to Infinity, higher value compared to effort => higher rate
            }
        }
        return rate;
    };
    $scope.orderBacklogByRank = function(backlogContainer) {
        backlogContainer.orderBy.reverse = false;
        $scope.changeBacklogOrder(backlogContainer, _.find(backlogContainer.orderBy.values, {id: 'rank'}));
    };
    $scope.changeBacklogOrder = function(backlogContainer, order) {
        var newOrder = _.clone(order);
        newOrder.value = [order.value];
        if (order.id !== 'rank') {
            newOrder.value.push('id'); // Order by id is crucial to ensure stable order when duplicate value (e.g. same effort) regardless of storyService.list order which itself depends on navigation order
        }
        if (BacklogService.isAll(backlogContainer.backlog) && order.id === 'rank') { // Hack to ensure that rank sort in "All" backlog is consistent with individual backlog ranking
            var sortByStateGroupingByBacklogState = function(story) {
                var orderCriteria = story.state === StoryStatesByName.ESTIMATED ? StoryStatesByName.ACCEPTED : story.state; // Ignore the differences betweed accepted and estimated
                return -orderCriteria; // "minus" the state to make done stories more prioritary
            };
            newOrder.value.unshift(sortByStateGroupingByBacklogState);
        }
        backlogContainer.orderBy.current = newOrder;
    };
    $scope.reverseBacklogOrder = function(backlogContainer) {
        backlogContainer.orderBy.reverse = !backlogContainer.orderBy.reverse;
    };
    $scope.isSortingBacklog = function(backlogContainer) {
        return backlogContainer.sortable && backlogContainer.orderBy.current.id === 'rank' && !backlogContainer.orderBy.reverse && !$scope.hasContextOrSearch();
    };
    $scope.enableSortable = function(backlogContainer) {
        $scope.clearContextAndSearch();
        $scope.orderBacklogByRank(backlogContainer)
    };
    $scope.openStoryUrl = function(storyId) {
        return '#/' + $scope.viewName + '/' + $state.params.elementId + '/story/' + storyId;
    };

    $scope.closeBacklogUrl = function(backlog) {
        var stateParams;
        if (backlog.code === $state.params.pinnedElementId) {
            stateParams = {pinnedElementId: $state.params.elementId, elementId: null};
        } else {
            stateParams = {elementId: null};
        }
        return $state.href('.', stateParams);
    };
    $scope.getBacklogContainer = function(backlogCode) {
        return _.find($scope.backlogContainers, function(backlogContainer) {
            return backlogContainer.backlog.code === backlogCode;
        });
    };
    $scope.showBacklog = function(backlogCode) {
        var backlogContainer = $scope.getBacklogContainer(backlogCode);
        if (!backlogContainer) {
            backlogContainer = {
                backlog: _.find($scope.availableBacklogs, {code: backlogCode}),
                storiesLoaded: false,
                orderBy: {
                    values: _.sortBy($scope.sortOptions, 'name')
                }
            };
            $scope.orderBacklogByRank(backlogContainer);
            $scope.refreshSingleBacklog(backlogContainer); // Init the backlog from client data (storyService.list) + init sortable variable
            var setStoriesLoaded = function() {
                backlogContainer.storiesLoaded = true;
            };
            var retrieveServerStories = function() { // Retrieve server data, stories that were missing will be automatically added
                StoryService.listByBacklog(backlogContainer.backlog).then(function(stories) {
                    if (stories.length === 0) {
                        setStoriesLoaded();
                    }
                });
            };
            if (backlogContainer.backlog.count > 500) {
                $scope.confirm({message: $scope.message('todo.is.ui.backlog.load.confirm'), callback: retrieveServerStories, closeCallback: setStoriesLoaded});
            } else {
                retrieveServerStories();
            }
            $scope.backlogContainers.push(backlogContainer);
            var savedBacklogsOrder = $scope.getWindowSetting('elementsListOrder');
            if (savedBacklogsOrder) {
                $scope.backlogContainers.sort(function(a, b) {
                    return savedBacklogsOrder.indexOf(a.backlog.code) - savedBacklogsOrder.indexOf(b.backlog.code)
                });
            } else {
                //keep old fashion way for all users that never reordered their backlog at least one time
                $scope.backlogContainers = _.sortBy($scope.backlogContainers, function(backlogContainer) {
                    return backlogContainer.backlog.id;
                });
            }
        }
    };
    $scope.$watchGroup([function() { return $state.$current.self.name; }, function() { return $state.params.pinnedElementId; }, function() { return $state.params.elementId; }], function(newValues) {
        var stateName = newValues[0];
        var pinnedElementId = newValues[1];
        var elementId = newValues[2];
        if (stateName === 'backlog') {
            $state.go('backlog.backlog', {elementId: _.head($scope.availableBacklogs).code}, {location: 'replace'});
        } else if (_.startsWith(stateName, 'backlog')) {
            if (pinnedElementId) {
                $scope.showBacklog(pinnedElementId);
            }
            if (elementId) {
                $scope.showBacklog(elementId);
            }
            _.remove($scope.backlogContainers, function(backlogContainer) {
                return !_.includes([pinnedElementId, elementId], backlogContainer.backlog.code);
            });
        }
    });
    $scope.openBacklogUrl = function(backlog) {
        var stateName = 'backlog.backlog';
        if ($state.current.name != 'backlog.backlog.details') {
            stateName += '.details';
        }
        return $state.href(stateName, {elementId: backlog.code});
    };
    // Init
    $scope.viewName = 'backlog';
    $scope.project = project;
    $scope.sortOptions = [
        {id: 'effort', value: 'effort', name: $scope.message('todo.is.ui.sort.effort')},
        {id: 'rank', value: 'rank', name: $scope.message('todo.is.ui.sort.rank')},
        {id: 'name', value: 'name', name: $scope.message('todo.is.ui.sort.name')},
        {id: 'tasks_count', value: 'tasks_count', name: $scope.message('todo.is.ui.sort.tasks')},
        {id: 'suggestedDate', value: 'suggestedDate', name: $scope.message('todo.is.ui.sort.date')},
        {id: 'feature.id', value: 'feature.id', name: $scope.message('todo.is.ui.sort.feature')},
        {id: 'value', value: 'value', name: $scope.message('todo.is.ui.sort.value')},
        {id: 'type', value: 'type', name: $scope.message('todo.is.ui.sort.type')},
        {id: 'state', value: 'state', name: $scope.message('todo.is.ui.sort.state')},
        {id: 'value/effort', value: getValueEffortRateForSorting, name: $scope.message('todo.is.ui.sort.value.effort.rate')}
    ];

    $scope.backlogSortableOptions = {
        itemMoved: function(event) {
            var story = event.source.itemScope.modelValue;
            var newRank = event.dest.index + 1;
            var sourceScope = event.source.sortableScope;
            var destScope = event.dest.sortableScope;
            var promise;
            if (BacklogService.isBacklog(sourceScope.backlogContainer.backlog) && BacklogService.isSandbox(destScope.backlogContainer.backlog)) {
                promise = StoryService.returnToSandbox(story, newRank);
            } else if (BacklogService.isSandbox(sourceScope.backlogContainer.backlog) && BacklogService.isBacklog(destScope.backlogContainer.backlog)) {
                promise = StoryService.acceptToBacklog(story, newRank);
            }
            if (promise) {
                promise.catch(function() {
                    $scope.revertSortable(event);
                });
            }
        },
        orderChanged: function(event) {
            var story = event.source.itemScope.modelValue;
            story.rank = event.dest.index + 1;
            StoryService.update(story).catch(function() {
                $scope.revertSortable(event);
            });
        },
        accept: function(sourceItemHandleScope, destSortableScope) {
            var sameSortable = sourceItemHandleScope.itemScope.sortableScope.sortableId === destSortableScope.sortableId;
            // We don't check more that the fact that the dest backlog is also sorting
            // because we know that the only backlogs that can be sorted (Sandbox & Backlog) can always be inter-sorted (accept <-> return to backlog)
            return sameSortable && destSortableScope.isSortingBacklog(destSortableScope.backlogContainer);
        }
    };
    $scope.sortableId = 'backlog';
    $scope.selectableOptions = {
        notSelectableSelector: '.action, button, a',
        allowMultiple: true,
        selectionUpdated: function(selectedIds) {
            var currentStateName = $state.current.name;
            var storyIndexInStateName = currentStateName.indexOf('story');
            if (selectedIds.length === 0) {
                if (storyIndexInStateName != -1) {
                    $state.go(currentStateName.slice(0, storyIndexInStateName - 1));
                }
            } else {
                var stateName;
                var stateParams;
                if (_.startsWith(currentStateName, 'backlog.backlog')) {
                    stateName = 'backlog.backlog.story'
                } else if (_.startsWith(currentStateName, 'backlog.multiple')) {
                    stateName = 'backlog.multiple.story'
                }
                if (selectedIds.length === 1) {
                    stateName += '.details' + ($state.params.storyTabId ? '.tab' : '');
                    stateParams = {storyId: selectedIds};
                } else {
                    stateName += '.multiple';
                    stateParams = {storyListId: selectedIds.join(",")};
                }
                $state.go(stateName, stateParams);
            }
        }
    };
    $scope.backlogContainers = [];
    $scope.availableBacklogs = backlogs;
    $scope.backlogCodes = BacklogCodes;
    var backlogsPendingUpdate = [];
    // Ensures that the stories list of displayed backlogs are up to date
    $scope.$on('is:backlogsUpdated', function(event, backlogCodes) {
        _.each(backlogCodes, function(backlogCode) {
            var backlogContainer = $scope.getBacklogContainer(backlogCode);
            if (backlogContainer && !_.includes(backlogsPendingUpdate, backlogCode)) {
                backlogsPendingUpdate.push(backlogCode);
                $timeout(function() {
                    _.pull(backlogsPendingUpdate, backlogCode);
                    $scope.refreshSingleBacklog(backlogContainer);
                }, 25);
            }
        });
    });
}]);<|endoftext|>


<commit_before>'use strict';

define(
  [
    'moment',
    'flight',
    'jquery',
    'query-string',
    '../component_data/dependency',
    '../component_ui/dependencyGraph',
    '../component_ui/serviceDataModal',
    '../component_ui/timeStamp',
    '../component_ui/goToDependency',
    '../../../templates/v2/dependency.mustache'
  ],

  function (moment,
            {component},
            $,
            queryString,
            DependencyData,
            DependencyGraphUI,
            ServiceDataModal,
            TimeStampUI,
            GoToDependencyUI,
            dependenciesTemplate
  ) {

    const DependencyPageComponent = component(function DependencyPage() {
      this.after('initialize', function() {
        window.document.title = 'Zipkin - Dependency';
        this.trigger(document, 'navigate', {route: 'dependency'});

        this.$node.html(dependenciesTemplate());

        const {startTs, endTs} = queryString.parse(location.search);
        $('#endTs').val(endTs || moment().valueOf());
        $('#startTs').val(startTs || moment().valueOf() - 7*24*60*60*1000); // set default startTs 7 days ago;

        DependencyData.attachTo('#dependency-container');
        DependencyGraphUI.attachTo('#dependency-container');
        ServiceDataModal.attachTo('#service-data-modal-container');
        TimeStampUI.attachTo('#end-ts');
        TimeStampUI.attachTo('#start-ts');
        GoToDependencyUI.attachTo('#dependency-query-form');
      });
    });

    return function initializeDependencies() {
      DependencyPageComponent.attachTo('.content');
    }
  }
);
<commit_msg>set the default for dependencies to 1 day of data<commit_after>'use strict';

define(
  [
    'moment',
    'flight',
    'jquery',
    'query-string',
    '../component_data/dependency',
    '../component_ui/dependencyGraph',
    '../component_ui/serviceDataModal',
    '../component_ui/timeStamp',
    '../component_ui/goToDependency',
    '../../../templates/v2/dependency.mustache'
  ],

  function (moment,
            {component},
            $,
            queryString,
            DependencyData,
            DependencyGraphUI,
            ServiceDataModal,
            TimeStampUI,
            GoToDependencyUI,
            dependenciesTemplate
  ) {

    const DependencyPageComponent = component(function DependencyPage() {
      this.after('initialize', function() {
        window.document.title = 'Zipkin - Dependency';
        this.trigger(document, 'navigate', {route: 'dependency'});

        this.$node.html(dependenciesTemplate());

        const {startTs, endTs} = queryString.parse(location.search);
        $('#endTs').val(endTs || moment().valueOf());
        $('#startTs').val(startTs || moment().valueOf() - 1*24*60*60*1000); // set default startTs 1 day ago;

        DependencyData.attachTo('#dependency-container');
        DependencyGraphUI.attachTo('#dependency-container');
        ServiceDataModal.attachTo('#service-data-modal-container');
        TimeStampUI.attachTo('#end-ts');
        TimeStampUI.attachTo('#start-ts');
        GoToDependencyUI.attachTo('#dependency-query-form');
      });
    });

    return function initializeDependencies() {
      DependencyPageComponent.attachTo('.content');
    }
  }
);
<|endoftext|>


<commit_before>import { Widget } from '@phosphor/widgets';
import { ABCWidgetFactory } from '@jupyterlab/docregistry';
import { ActivityMonitor } from '@jupyterlab/coreutils';
import { runMode } from '@jupyterlab/codemirror';

const CLASS_NAME = 'jp-DocWidget{{cookiecutter.mime_short_name}}';
const RENDER_TIMEOUT = 1000;

/**
 * A widget for rendering {{cookiecutter.extension_name}} files
 */
export class DocWidget extends Widget {
  constructor(context) {
    super();
    this._context = context;
    /* Re-render when the document content changes */
    context.model.contentChanged.connect(() => {
      this.update();
    });
    /* Re-render when the document path changes */
    context.pathChanged.connect(() => {
      this.update();
    });
    /* Throttle re-renders until changes have stopped */
    this._monitor = new ActivityMonitor({
      signal: context.model.contentChanged,
      timeout: RENDER_TIMEOUT
    });
    this._monitor.activityStopped.connect(this.update, this);
    /* Track widget width and height */
    this._width = this.node.offsetWidth;
    this._height = this.node.offsetHeight;
    this.addClass(CLASS_NAME);
  }

  /**
   * Dispose of the resources used by the widget
   */
  dispose() {
    if (!this.isDisposed) {
      this._context = null;
      this._monitor.dispose();
      super.dispose();
    }
  }

  /**
   * A message handler invoked on an `'after-attach'` message
   */
  onAfterAttach(msg) {
    /* Render initial data */
    this.update();
  }

  /**
   * A message handler invoked on a `'resize'` message
   */
  onResize(msg) {
    /* Update tracked widget and height values */
    this._width = msg.width;
    this._height = msg.height;
    /* Re-render on resize */
    this.update();
  }

  /**
   * A message handler invoked on an `'update-request'` message
   */
  onUpdateRequest(msg) {
    this.title.label = this._context.path.split('/').pop();
    if (this.isAttached) {
      const content = this._context.model.toString();
      try {
        const data = JSON.parse(content);
        const text = document.createTextNode(JSON.stringify(data));
        this.node.appendChild(text);
      } catch (error) {
        const container = document.createElement('div');
        container.setAttribute('class', 'jp-RenderedText jp-mod-error');
        container.style.cssText = `width: 100%; text-align: center; padding: 10px; box-sizing: border-box;`;
        const titleContainer = document.createElement('span');
        titleContainer.style.cssText = `font-size: 18px; font-weight: 500; padding-bottom: 10px;`;
        const titleText = document.createTextNode('Invalid JSON');
        titleContainer.appendChild(titleText);
        container.appendChild(titleContainer);
        const contentContainer = document.createElement('pre');
        contentContainer.className = 'CodeMirror cm-s-jupyter CodeMirror-wrap';
        contentContainer.style.cssText = `text-align: left; padding: 10px; overflow: hidden;`;
        const contentText = document.createTextNode(content);
        contentContainer.appendChild(contentText);
        container.appendChild(contentContainer);
        this.node.innerHTML = '';
        this.node.appendChild(container);
      }
    }
  }
}

/**
 * A widget factory for DocWidget
 */
export class DocWidgetFactory extends ABCWidgetFactory {
  constructor(options) {
    super(options);
  }

  /**
   * Create a new widget instance
   */
  createNewWidget(context, kernel) {
    const widget = new DocWidget(context);
    this.widgetCreated.emit(widget);
    return widget;
  }
}
<commit_msg>Use CodeMirror's runMode to render JSON in error message<commit_after>import { Widget } from '@phosphor/widgets';
import { ABCWidgetFactory } from '@jupyterlab/docregistry';
import { ActivityMonitor } from '@jupyterlab/coreutils';
import { runMode } from '@jupyterlab/codemirror';

const CLASS_NAME = 'jp-DocWidget{{cookiecutter.mime_short_name}}';
const RENDER_TIMEOUT = 1000;

/**
 * A widget for rendering {{cookiecutter.extension_name}} files
 */
export class DocWidget extends Widget {
  constructor(context) {
    super();
    this._context = context;
    /* Re-render when the document content changes */
    context.model.contentChanged.connect(() => {
      this.update();
    });
    /* Re-render when the document path changes */
    context.pathChanged.connect(() => {
      this.update();
    });
    /* Throttle re-renders until changes have stopped */
    this._monitor = new ActivityMonitor({
      signal: context.model.contentChanged,
      timeout: RENDER_TIMEOUT
    });
    this._monitor.activityStopped.connect(this.update, this);
    /* Track widget width and height */
    this._width = this.node.offsetWidth;
    this._height = this.node.offsetHeight;
    this.addClass(CLASS_NAME);
  }

  /**
   * Dispose of the resources used by the widget
   */
  dispose() {
    if (!this.isDisposed) {
      this._context = null;
      this._monitor.dispose();
      super.dispose();
    }
  }

  /**
   * A message handler invoked on an `'after-attach'` message
   */
  onAfterAttach(msg) {
    /* Render initial data */
    this.update();
  }

  /**
   * A message handler invoked on a `'resize'` message
   */
  onResize(msg) {
    /* Update tracked widget and height values */
    this._width = msg.width;
    this._height = msg.height;
    /* Re-render on resize */
    this.update();
  }

  /**
   * A message handler invoked on an `'update-request'` message
   */
  onUpdateRequest(msg) {
    this.title.label = this._context.path.split('/').pop();
    if (this.isAttached) {
      const content = this._context.model.toString();
      try {
        const data = JSON.parse(content);
        const text = document.createTextNode(JSON.stringify(data));
        this.node.appendChild(text);
      } catch (error) {
        const container = document.createElement('div');
        container.setAttribute('class', 'jp-RenderedText jp-mod-error');
        container.style.cssText = `width: 100%; text-align: center; padding: 10px; box-sizing: border-box;`;
        const titleContainer = document.createElement('span');
        titleContainer.style.cssText = `font-size: 18px; font-weight: 500; padding-bottom: 10px;`;
        const titleText = document.createTextNode('Invalid JSON');
        titleContainer.appendChild(titleText);
        container.appendChild(titleContainer);
        const contentContainer = document.createElement('pre');
        contentContainer.className = 'CodeMirror cm-s-jupyter CodeMirror-wrap';
        contentContainer.style.cssText = `text-align: left; padding: 10px; overflow: hidden;`;
        runMode(content, { name: 'javscript', json: true }, contentContainer);
        container.appendChild(contentContainer);
        this.node.innerHTML = '';
        this.node.appendChild(container);
      }
    }
  }
}

/**
 * A widget factory for DocWidget
 */
export class DocWidgetFactory extends ABCWidgetFactory {
  constructor(options) {
    super(options);
  }

  /**
   * Create a new widget instance
   */
  createNewWidget(context, kernel) {
    const widget = new DocWidget(context);
    this.widgetCreated.emit(widget);
    return widget;
  }
}
<|endoftext|>


<commit_before>// @flow

import styles from './AppRoute.css';
import React, { PureComponent, type Element, cloneElement } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import prepare from 'app/utils/prepare';
import Helmet from 'react-helmet';
import {
  loginAutomaticallyIfPossible,
  logoutWithRedirect,
  login
} from 'app/actions/UserActions';
import {
  fetchNotificationData,
  markAllNotifications
} from 'app/actions/NotificationsFeedActions';
import { fetchNotificationFeed } from 'app/actions/FeedActions';
import { fetchMeta } from 'app/actions/MetaActions';
import { selectFeedActivitesByFeedId } from 'app/reducers/feeds';
import { toggleSearch } from 'app/actions/SearchActions';
import ErrorBoundary from 'app/components/ErrorBoundary';
import SpecialDay from 'app/components/SpecialDay';
import Header from 'app/components/Header';
import Footer from 'app/components/Footer';
import ToastContainer from 'app/components/Toast/ToastContainer';
import PhotoUploadStatus from 'app/components/PhotoUploadStatus';
import { selectIsLoggedIn, selectCurrentUser } from 'app/reducers/auth';
import cx from 'classnames';
import HTTPError from '../errors/HTTPError';
import { setStatusCode } from 'app/actions/RoutingActions';
import config from 'app/config';
import coverPhoto from 'app/assets/cover.png';

type Props = {
  statusCode: number,
  location: any,
  children: Element<*>,
  currentUser: /*TODO: User*/ Object,
  setStatusCode: (code: ?number) => void,
  loggedIn: boolean
};

export const UserContext = React.createContext();

class AppChildren extends PureComponent<Props> {
  render() {
    const children = React.Children.map(this.props.children, child =>
      cloneElement(child, {
        passedProps: {
          currentUser: this.props.currentUser,
          loggedIn: this.props.loggedIn
        }
      })
    );

    const userValue = {
      currentUser: this.props.currentUser,
      loggedIn: this.props.loggedIn
    };

    return (

        <ErrorBoundary resetOnChange={this.props.location}>
          <ToastContainer />
          {this.props.statusCode ? (
            <HTTPError
              statusCode={this.props.statusCode}
              setStatusCode={this.props.setStatusCode}
              location={this.props.location}
            />
          ) : (
            <UserContext.Provider value={userValue}>
              {children}
            </UserContext.Provider>
          )}
        </ErrorBoundary>

    );
  }
}

// TODO: Type it
type AppProps = any;

class App extends PureComponent<AppProps> {
  render() {
    return (

        <Helmet defaultTitle="Abakus.no" titleTemplate="%s | Abakus.no">
          <meta property="og:image" content={coverPhoto} />
          <meta
            property="og:description"
            content="Abakus er linjeforeningen for studentene ved Datateknologi og Kommunikasjonsteknologi på NTNU, og drives av studenter ved disse studiene."
          />
        </Helmet>

        <SpecialDay>
          {config.environment !== 'production' && (

              This is a development version of lego-webapp.

          )}

          <Header
            searchOpen={this.props.searchOpen}
            toggleSearch={this.props.toggleSearch}
            currentUser={this.props.currentUser}
            loggedIn={this.props.loggedIn}
            logout={this.props.logout}
            login={this.props.login}
            notificationsData={this.props.notificationsData}
            fetchNotifications={this.props.fetchNotificationFeed}
            notifications={this.props.notifications}
            markAllNotifications={this.props.markAllNotifications}
            fetchNotificationData={this.props.fetchNotificationData}
          />
          <AppChildren
            currentUser={this.props.currentUser}
            loggedIn={this.props.loggedIn}
            statusCode={this.props.statusCode}
            setStatusCode={this.props.setStatusCode}
            location={this.props.location}
          >
            {this.props.children}
          </AppChildren>

          <PhotoUploadStatus />

        </SpecialDay>

    );
  }
}

function mapStateToProps(state) {
  return {
    searchOpen: state.search.open,
    currentUser: selectCurrentUser(state),
    loggedIn: selectIsLoggedIn(state),
    notificationsData: state.notificationsFeed,
    notifications: selectFeedActivitesByFeedId(state, {
      feedId: 'notifications'
    }),
    statusCode: state.router.statusCode
  };
}

const mapDispatchToProps = {
  toggleSearch,
  logout: logoutWithRedirect,
  login,
  fetchNotificationFeed,
  markAllNotifications,
  fetchNotificationData,
  setStatusCode,
  loginAutomaticallyIfPossible
};

async function fetchInitialOnServer(props) {
  await props.loginAutomaticallyIfPossible();
  await props.fetchNotificationData();
  await props.fetchNotificationFeed();
}

export default compose(
  prepare(fetchInitialOnServer, [], {
    componentDidMount: false,
    componentWillReceiveProps: false
  }),
  prepare((props, dispatch) => dispatch(fetchNotificationData())),
  connect(
    mapStateToProps,
    mapDispatchToProps
  )
)(App);
<commit_msg>WIP stuff to make it work<commit_after>// @flow

import styles from './AppRoute.css';
import React, { PureComponent, type Element, cloneElement } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import prepare from 'app/utils/prepare';
import Helmet from 'react-helmet';
import {
  loginAutomaticallyIfPossible,
  logoutWithRedirect,
  login
} from 'app/actions/UserActions';
import {
  fetchNotificationData,
  markAllNotifications
} from 'app/actions/NotificationsFeedActions';
import { fetchNotificationFeed } from 'app/actions/FeedActions';
import { fetchMeta } from 'app/actions/MetaActions';
import { selectFeedActivitesByFeedId } from 'app/reducers/feeds';
import { toggleSearch } from 'app/actions/SearchActions';
import ErrorBoundary from 'app/components/ErrorBoundary';
import SpecialDay from 'app/components/SpecialDay';
import Header from 'app/components/Header';
import Footer from 'app/components/Footer';
import ToastContainer from 'app/components/Toast/ToastContainer';
import PhotoUploadStatus from 'app/components/PhotoUploadStatus';
import { selectIsLoggedIn, selectCurrentUser } from 'app/reducers/auth';
import cx from 'classnames';
import HTTPError from '../errors/HTTPError';
import { setStatusCode } from 'app/actions/RoutingActions';
import config from 'app/config';
import coverPhoto from 'app/assets/cover.png';

type Props = {
  statusCode: number,
  location: any,
  children: Element<*>,
  currentUser: /*TODO: User*/ Object,
  setStatusCode: (code: ?number) => void,
  loggedIn: boolean
};

export const UserContext = React.createContext();

class AppChildren extends PureComponent<Props> {
  render() {
    const children = React.Children.map(this.props.children, child =>
      cloneElement(child, {
        passedProps: {
          currentUser: this.props.currentUser,
          loggedIn: this.props.loggedIn
        }
      })
    );

    const userValue = {
      currentUser: this.props.currentUser,
      loggedIn: this.props.loggedIn
    };

    return (

        <ErrorBoundary resetOnChange={this.props.location}>
          <ToastContainer />
          {this.props.statusCode ? (
            <HTTPError
              statusCode={this.props.statusCode}
              setStatusCode={this.props.setStatusCode}
              location={this.props.location}
            />
          ) : (
            <UserContext.Provider value={userValue}>
              {children}
            </UserContext.Provider>
          )}
        </ErrorBoundary>

    );
  }
}

// TODO: Type it
type AppProps = any;

class App extends PureComponent<AppProps> {
  render() {
    return (

        <Helmet defaultTitle="Abakus.no" titleTemplate="%s | Abakus.no">
          <meta property="og:image" content={coverPhoto} />
          <meta
            property="og:description"
            content="Abakus er linjeforeningen for studentene ved Datateknologi og Kommunikasjonsteknologi på NTNU, og drives av studenter ved disse studiene."
          />
        </Helmet>

        <SpecialDay>
          {config.environment !== 'production' && (

              This is a development version of lego-webapp.

          )}

          <Header
            searchOpen={this.props.searchOpen}
            toggleSearch={this.props.toggleSearch}
            currentUser={this.props.currentUser}
            loggedIn={this.props.loggedIn}
            logout={this.props.logout}
            login={this.props.login}
            notificationsData={this.props.notificationsData}
            fetchNotifications={this.props.fetchNotificationFeed}
            notifications={this.props.notifications}
            markAllNotifications={this.props.markAllNotifications}
            fetchNotificationData={this.props.fetchNotificationData}
          />
          <AppChildren
            currentUser={this.props.currentUser}
            loggedIn={this.props.loggedIn}
            statusCode={this.props.statusCode}
            setStatusCode={this.props.setStatusCode}
            location={this.props.location}
          >
            {this.props.children}
          </AppChildren>

          <PhotoUploadStatus />

        </SpecialDay>

    );
  }
}

function mapStateToProps(state) {
  return {
    searchOpen: state.search.open,
    currentUser: selectCurrentUser(state),
    loggedIn: selectIsLoggedIn(state),
    notificationsData: state.notificationsFeed,
    notifications: selectFeedActivitesByFeedId(state, {
      feedId: 'notifications'
    }),
    statusCode: state.router.statusCode
  };
}

const mapDispatchToProps = {
  toggleSearch,
  logout: logoutWithRedirect,
  login,
  fetchNotificationFeed,
  markAllNotifications,
  fetchNotificationData,
  setStatusCode,
  loginAutomaticallyIfPossible
};

function fetchInitialOnServer(props, dispatch) {
  return dispatch(loginAutomaticallyIfPossible()).then(() =>
    Promise.all([
      dispatch(fetchMeta())
      //dispatch(fetchNotificationData()),
      //dispatch(fetchNotificationFeed())
    ])
  );
}

export default compose(
  prepare(fetchInitialOnServer, [], {
    componentDidMount: false,
    componentWillReceiveProps: false
  }),
  prepare((props, dispatch) => dispatch(fetchNotificationData())),
  connect(
    mapStateToProps,
    mapDispatchToProps
  )
)(App);
<|endoftext|>


<commit_before>import { put, call, takeLatest, select, race } from 'redux-saga/effects'
import { delay } from 'redux-saga'
import OccuspaceService from '../services/occuspaceService'
import logger from '../util/logger'
import { OCCUSPACE_FETCH_TIMEOUT } from '../AppSettings'

const getOccuspaceData = state => (state.occuspace)
function* updateOccuspaceData() {
	// old data on device
	let { data }  = yield select(getOccuspaceData)
	try {
		yield put({ type: 'GET_OCCUSPACE_REQUEST' })
		// new data being fetched
		const { response, timeout } = yield race({
			response: call(OccuspaceService.FetchOccuspace),
			timeout: call(delay, OCCUSPACE_FETCH_TIMEOUT)
		})

		if (timeout) {
			const e = new Error('Request timed out.')
			e.name = 'occuspaceTimeout'
			throw e
		} else if (response.error) {
			if (response.error) {
				logger.log(response)
				throw response.error
			}
		} else {
			if (response) {
				const occuspaceData = JSON.parse(response)
				if (data) {
					data =  occuspaceData.sort(sortByOldOrder(data))
				} else {
					data = occuspaceData
				}
				yield put({ type: 'SET_OCCUSPACE_DATA', data })
				yield put({ type: 'SHOW_CARD', id: 'occuspace' })
			}
		}
	} catch (error) {
		yield put({ type: 'GET_OCCUSPACE_FAILED', error })
		logger.trackException(error)
	}
}

// comparator function to sort all the occuspace locations in the same order as the passed in occuspaceData
function sortByOldOrder(occuspaceData) {
	return function (a, b) {
		return occuspaceData.findIndex(x => x.locationName === a.locationName) - occuspaceData.findIndex(x => x.locationName === b.locationName)
	}
}

function* occuspaceSaga() {
	yield takeLatest('UPDATE_OCCUSPACE_DATA', updateOccuspaceData)
}

export default occuspaceSaga
<commit_msg>update occuspace saga to handle errors<commit_after>import { put, call, takeLatest, select, race } from 'redux-saga/effects'
import { delay } from 'redux-saga'
import OccuspaceService from '../services/occuspaceService'
import logger from '../util/logger'
import { OCCUSPACE_FETCH_TIMEOUT } from '../AppSettings'

const getOccuspaceData = state => (state.occuspace)
function* updateOccuspaceData() {
	// old data on device
	let { data }  = yield select(getOccuspaceData)
	try {
		yield put({ type: 'GET_OCCUSPACE_REQUEST' })
		// new data being fetched
		const { response, timeout } = yield race({
			response: call(OccuspaceService.FetchOccuspace),
			timeout: call(delay, OCCUSPACE_FETCH_TIMEOUT)
		})

		if (timeout) {
			const e = new Error('Request timed out.')
			e.name = 'occuspaceTimeout'
			throw e
		} else if (response.error) {
			logger.log(response)
			throw response.error
		} else {
			if (response) {
				const occuspaceData = JSON.parse(response)
				if (data) {
					data =  occuspaceData.sort(sortByOldOrder(data))
				} else {
					data = occuspaceData
				}
				yield put({ type: 'SET_OCCUSPACE_DATA', data })
				yield put({ type: 'GET_OCCUSPACE_SUCESS' })
				yield put({ type: 'SHOW_CARD', id: 'occuspace' })
			}
		}
	} catch (error) {
		yield put({ type: 'GET_OCCUSPACE_FAILURE', error })
		logger.trackException(error)
	}
}

// comparator function to sort all the occuspace locations in the same order as the passed in occuspaceData
function sortByOldOrder(occuspaceData) {
	return function (a, b) {
		return occuspaceData.findIndex(x => x.locationName === a.locationName) - occuspaceData.findIndex(x => x.locationName === b.locationName)
	}
}

function* occuspaceSaga() {
	yield takeLatest('UPDATE_OCCUSPACE_DATA', updateOccuspaceData)
}

export default occuspaceSaga
<|endoftext|>


<commit_before>import DS from "ember-data";

function bin2String(uintArray) {
  var encodedString = String.fromCharCode.apply(null, uintArray),
      decodedString = decodeURIComponent((encodedString));
  return decodedString;
}

export default DS.Serializer.extend({
  resource(id, body) {
    var resource = {};
    resource.id = id;
    resource.type = "article";
    resource.attributes = {};
    resource.attributes.body = bin2String(body);
    return resource;
  },

  normalizeResponse(store, type, payload, id, requestType) {
    var jsonapiPayload = {};

    if (requestType === "findAll") {
      jsonapiPayload.data = [];
      payload.forEach((element) => {
        jsonapiPayload.data.pushObject(this.resource(element.Key, element.Body));
      });
    } else if (requestType === "findRecord") {
      jsonapiPayload.data = this.resource(payload.Key, payload.Body);
    }

    return jsonapiPayload;
  }
});
<commit_msg>fix serializer for general case<commit_after>import DS from "ember-data";

function bin2String(uintArray) {
  var encodedString = String.fromCharCode.apply(null, uintArray),
      decodedString = decodeURIComponent((encodedString));
  return decodedString;
}

export default DS.Serializer.extend({
  resource(id, body) {
    var resource = {};
    resource.id = id;
    resource.type = "article";
    resource.attributes = {};
    resource.attributes.body = bin2String(body);
    return resource;
  },

  normalizeResponse(store, type, payload, id, requestType) {
    var jsonapiPayload = {};

    if (requestType === "findAll") {
      jsonapiPayload.data = [];
      payload.forEach((element) => {
        jsonapiPayload.data.pushObject(this.resource(element.Key, element.Body));
      });
    } else {
      jsonapiPayload.data = this.resource(payload.Key, payload.Body);
    }

    return jsonapiPayload;
  }
});
<|endoftext|>


<commit_before>import TagsInput from 'react-tagsinput'

let TagBar = ({
  className = 'tag-bar',
  inputProps = {
    className: 'tag-bar-input',
    placeholder: 'add tag'
  },
  tagProps = {
    className: 'tag',
    classNameRemove: 'remove'
  },
  readonly = false,
  tags = [],
  onChange = () => {},
  onlyUnique = true
}) =>
  <TagsInput
    className={className}
    value={tags}
    inputProps={inputProps} tagProps={tagProps}
    readonly={readonly}
    renderInput={props => !readonly && <input type="text" {...props} />}
    renderTag={props =>

        <a className="icon icon-tag" href={`/page/${props.tag}`}>{props.tag}
        { !readonly &&  props.onRemove(props.key)} /> }

    }
    onChange={onChange}
    onlyUnique={onlyUnique}
  />

export default TagBar
<commit_msg>Simplifying binding for Article to tags<commit_after>import * as React from 'react'
import TagsInput from 'react-tagsinput'
import { difference, isEqual, orderBy } from 'lodash'

export default class TagBar extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      tags: this.clean(props.tags, props),
    }
  }
  componentWillReceiveProps(nextProps) {
    this.setState({
      tags: this.clean(nextProps.tags, nextProps),
    })
  }

  clean(list, props = this.props) {
    let tags = difference(list, props.banned)
    if (props.unique) tags = orderBy(tags)
    return tags
  }

  render() {
    const {
      className, tags, readonly, unique,
      inputSettings, tagSettings, onChange,
    } = this.props

    return (
      <TagsInput
        className={className} value={tags} readonly={readonly}
        inputProps={inputSettings} tagProps={tagSettings}
        renderInput={props => !readonly && <input type="text" {...props} />}
        renderTag={props =>

            <a className="icon icon-tag" href={`/page/${props.tag}`}>{props.tag}
            { !readonly &&  props.onRemove(props.key)} /> }

        }
        onChange={updated => {
          let clean = this.clean(updated)
          if (!isEqual(clean, this.state.tags))
            this.props.onChange(clean)
        }}
        onlyUnique={unique}
      />
    )
  }
}

TagBar.propTypes = {
  banned: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
  className: React.PropTypes.string,
  onChange: React.PropTypes.func.isRequired,
  readonly: React.PropTypes.bool.isRequired,
  tags: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
  unique: React.PropTypes.bool.isRequired,

  input: React.PropTypes.shape({
    className: React.PropTypes.string,
    placeholder: React.PropTypes.string,
  }),
  tagSettings: React.PropTypes.shape({
    className: React.PropTypes.string,
    classNameRemove: React.PropTypes.string,
  }),
}
TagBar.defaultProps = {
  banned: [],
  className: 'tag-bar',
  onChange: () => {},
  readonly: false,
  tags: [],
  unique: true,

  inputSettings: {
    className: 'tag-bar-input',
    placeholder: 'add tag'
  },
  tagSettings: {
    className: 'tag',
    classNameRemove: 'remove'
  },
}
<|endoftext|>


<commit_before>import Ember from "ember";
import config from '../config/environment';

export default Ember.Service.extend({
  isMobileApp: config.cordova.enabled,
  cordova: Ember.inject.service(),
  i18n: Ember.inject.service(),

  promptReviewModal() {
    var _this = this;
    var i18n = _this.get("i18n");
    if(this.get("isMobileApp")) {
      AppRate.preferences = {
        displayAppName: config.APP.REVIEW_APP_NAME,
        usesUntilPrompt: 1,
        promptAgainForEachNewVersion: true,
        inAppReview: true,
        storeAppURL: {
          ios: config.APP.APPLE_APP_ID,
          android: config.APP.ANDROID_APP_URL,
        },
        customLocale: {
          title: i18n.t("app_review.title"),
          message: i18n.t("app_review.message"),
          cancelButtonLabel: i18n.t("app_review.cancel_button_label"),
          laterButtonLabel: i18n.t("app_review.later_button_label"),
          rateButtonLabel: i18n.t("app_review.rate_button_label"),
          yesButtonLabel: i18n.t("app_review.yes_button_label"),
          noButtonLabel: i18n.t("app_review.no_button_label"),
          appRatePromptTitle: i18n.t("app_review.app_rate_prompt_title"),
          feedbackPromptTitle: i18n.t("app_review.feedback_prompt_title"),
        },
        callbacks: {
          handleNegativeFeedback: function(){
            console.log("negative feedback registered");
          },
          onRateDialogShow: function(){
            console.log("Rate dilogue shown");
          },
          onButtonClicked: function(buttonIndex){
            console.log("onButtonClicked -> " + buttonIndex + "clicked");
          }
        }
      };
      AppRate.promptForRating(true);
    }
  }
});
<commit_msg>GCW-1920 prompt for every new version removed<commit_after>import Ember from "ember";
import config from '../config/environment';

export default Ember.Service.extend({
  isMobileApp: config.cordova.enabled,
  cordova: Ember.inject.service(),
  i18n: Ember.inject.service(),

  promptReviewModal() {
    var _this = this;
    var i18n = _this.get("i18n");
    if(this.get("isMobileApp")) {
      AppRate.preferences = {
        displayAppName: config.APP.REVIEW_APP_NAME,
        usesUntilPrompt: 1,
        promptAgainForEachNewVersion: false,
        inAppReview: true,
        storeAppURL: {
          ios: config.APP.APPLE_APP_ID,
          android: config.APP.ANDROID_APP_URL,
        },
        customLocale: {
          title: i18n.t("app_review.title"),
          message: i18n.t("app_review.message"),
          cancelButtonLabel: i18n.t("app_review.cancel_button_label"),
          laterButtonLabel: i18n.t("app_review.later_button_label"),
          rateButtonLabel: i18n.t("app_review.rate_button_label"),
          yesButtonLabel: i18n.t("app_review.yes_button_label"),
          noButtonLabel: i18n.t("app_review.no_button_label"),
          appRatePromptTitle: i18n.t("app_review.app_rate_prompt_title"),
          feedbackPromptTitle: i18n.t("app_review.feedback_prompt_title"),
        },
        callbacks: {
          handleNegativeFeedback: function(){
            console.log("negative feedback registered");
          },
          onRateDialogShow: function(){
            console.log("Rate dilogue shown");
          },
          onButtonClicked: function(buttonIndex){
            console.log("onButtonClicked -> " + buttonIndex + "clicked");
          }
        }
      };
      AppRate.promptForRating(true);
    }
  }
});
<|endoftext|>