<commit_before>/**
 * author: Sheng JIANG
 * date: 2017-03-13
 */
$(function () {

    var calendarData = {
        monthInfo: null,
        calendarInfo: null
    };
    var calendarTab;
    var monthArray = [];
    var fourMonthCalendar;

    $.ajax({
        url: "../json/calendar-data-month.json",
        dataType: "json"
    }).done(function (jsonData) {

        calendarData.monthInfo = jsonData.monthInfo;
        calendarData.calendarInfo = jsonData.calendarInfo;

        var date = new Date();
        date = nova.calendar.dateToDay(date);
        date.setDate(1);

        for (var i = 0; i < 12; i++) {
            var tempDate = lv.calendar.monthOffset(date, i);
            var yeahMonth = lv.calendar.dateFormat(tempDate, "yyyy-MM");
            if (calendarData.monthInfo[yeahMonth] && calendarData.monthInfo[yeahMonth].price) {
                monthArray.push(tempDate);
            }
        }

        if (monthArray.length === 0) {
            monthArray = [date];
        }

        initCalendar();
        initCalendarTab();

    }).fail(function () {
        console.error("接口错误");
    });

    function initCalendar() {

        var tabStr = '
' +
            '' +
            '

' +
            '

' +
            '' +
            '
';
        fourMonthCalendar = lv.calendar({
            autoRender: false,
            trigger: ".fourMonthInput",
            triggerEvent: "click",
            sourceFn: fillData,
            completeCallback: function () {

            },
            bimonthly: false,
            template: {
                wrap: '

',
                calWrap: '

' +
                '

' +
                tabStr +
                tabStr +
                tabStr +
                tabStr +
                '

' +
                '

' + '
{{content}}
',
                calTitle: '',
                day: '' +
                '' +
                '
' +
                '
{{day}}
' +
                '

' +
                '

' +
                '

' +
                '

' +
                '
' +
                '	'
            }
        });

    }

    function initCalendarTab() {

        calendarTab = {
            init: function () {
                this.size = monthArray.length;
                this.index = 0;
                this.page = 0;
                this.maxPage = Math.ceil(this.size / 4);
            },
            bindEvent: function () {
                var $wrap = fourMonthCalendar.wrap;
                var $tab = $wrap.find(".nova-calendar-tab");
                var self = this;

                function renderCal(dateStr) {
                    fourMonthCalendar.now = nova.calendar.getDateFromFormattedString(dateStr, "yyyy-MM");
                    fourMonthCalendar.render();
                    self.render();
                    self.bindEvent();
                }

                $wrap.find(".nova-calendar-tabs-prev").on("click", function () {
                    self.switchTab(-1);
                    var dateStr = $tab.first().attr("data-date");
                    renderCal(dateStr);
                    return false;
                });
                $wrap.find(".nova-calendar-tabs-next").on("click", function () {
                    self.switchTab(1);
                    var dateStr = $tab.first().attr("data-date");
                    renderCal(dateStr);
                    return false;
                });

                $wrap.find(".nova-calendar-tab").on("click", function () {
                    var $this = $(this);
                    if ($this.hasClass("disabled")) {
                        return false;
                    }
                    var dateStr = $this.attr("data-date");
                    renderCal(dateStr);
                    return false;
                })
            },
            switchTab: function (offset) {
                var page = this.page + offset;
                if (page < 0) {
                    page = 0
                }
                if (page > this.maxPage) {
                    page = this.maxPage;
                }
                this.page = page;
                this.render();
                this.bindEvent();
            },
            render: function () {
                var $wrap = fourMonthCalendar.wrap;
                var $tab = $wrap.find(".nova-calendar-tab");
                var calDate = fourMonthCalendar.now;

                var $prev = $wrap.find(".nova-calendar-tabs-prev");
                var $next = $wrap.find(".nova-calendar-tabs-next");

                $prev.removeClass("disabled");
                $next.removeClass("disabled");

                var page = this.page;
                if (page == 0) {
                    //console.log(0)

                    $prev.addClass("disabled");

                }

                if (page == this.maxPage - 1) {
                    //console.log("max")
                    $next.addClass("disabled");
                }

                for (var j = 0; j < 4; j++) {
                    var $thisTab = $tab.eq(j);
                    var $tabMonth = $thisTab.find(".nova-calendar-tab-month");
                    var tabDate = monthArray[this.page * 4 + j];
                    $thisTab.removeClass("active");
                    if (calDate && tabDate && nova.calendar.isSameMonth(calDate, tabDate)) {
                        $thisTab.addClass("active");
                    }
                    if (tabDate) {
                        var tabDateStr = nova.calendar.dateFormat(tabDate, "yyyy-MM");
                        $tabMonth.text(tabDateStr);
                        $thisTab.attr("data-date", tabDateStr);

                        $thisTab.removeClass("disabled");
                    } else {
                        $tabMonth.text("");
                        $thisTab.attr("data-date", "");
                        $thisTab.addClass("disabled");
                    }
                }
            },
            changeTab: function (date) {
                date = nova.calendar.dateToDay(date);
                date.setDate(1);
                var index = 0;
                for (var i = 0; i < this.size; i++) {
                    if (-date === -monthArray[i]) {
                        index = i;
                    }
                }
                this.index = index;
                this.page = ~~(index / 4);
                this.render();
            }
        };
        calendarTab.init();
    }

    function fillData() {
        var self = this;

        /**
         * 获取剩余HTML
         * @param inventory 剩余数量
         * @returns {string} 生成的HTML
         */
        function getStoreHTML(inventory) {
            var html = "";
            if (isNaN(inventory) || inventory <= 0) {
                html = "售罄";
            } else if (inventory < 10) {
                html = "余" + inventory
            }
            return html;
        }

        /**
         * 创建浮动框
         * @returns {*|jQuery|HTMLElement}  被创建的浮动框jQuery对象
         */
        function createHover() {
            var $hover = $(".calhover");
            if ($hover.length <= 0) {
                $hover = $('

');
            } else {
                $hover.html('

');
            }
            $("body").append($hover);
            $hover.removeClass("calhover-right");
            return $hover;
        }

        /**
         * 设置显示
         * @param data Ajax返回值
         * @returns {boolean}
         */
        function setDate(data) {

            /**
             * 退化的情况，无任何返回值
             */
            if (!data) {
                return false;
            }

            var $allTd = self.wrap.find('td[data-date-map]');  //所有的日历单元格
            $allTd.children().addClass("caldisabled");  //先禁用所有的日历日期

            //对json对象进行迭代处理
            data.forEach(function (row) {
                //row 每个json对象中的元素单元，格式如下所示
                // {
                //     "child": 3688,
                //     "date": "2016-06-11",
                //     "detail": "",
                //     "endDate": "201612",
                //     "lineRouteId": 0,
                //     "lineRouteName": "A",
                //     "next": true,
                //     "prev": false,
                //     "price": 3988,
                //     "surplus": 4,
                //     "type": 1,
                //     "inventory": -1,
                //     "oversold": false,
                //     "sale": "下单满3000减200元，成人儿童可享受，不与其他优惠同享；\n下单满2000减150元，成人儿童可享受，不与其他优惠同享；\n下单满1000减100元，成人儿童可享受，不与其他优惠同享；"
                // }

                var jsonDateStr = row.date;  //json单元-日期

                //将json单元日期字符串转化为JS日历对象(new Date())
                var date = lv.calendar.getDateFromFormattedString(jsonDateStr, self.options.dateFormat);

                //将日历对象转换为字符串(只保留参数dateFormat设定的数据，默认值为年月日)
                var dateStr = lv.calendar.dateFormat(date, self.options.dateFormat);

                //json单元-价格
                var price = row.price;

                //价格-浮点数
                var inventory = parseFloat(row.inventory);

                //json单月-是否促销
                var sale = row.sale;

                //json单元-日期对应文档中的td单元格
                var $td = self.wrap.find('td[data-date-map=' + dateStr + ']');

                var lineRouteName = row.lineRouteName;

                //如果json中的数据有td单元格相对应，则显示数据信息
                if ($td) {

                    //促销等元素的显示位置
                    var $calActive = $td.find(".calactive");

                    //显示价格
                    $td.find(".calprice").html('¥' + price + '起');

                    //显示库存
                    $td.find(".calinfo").html(getStoreHTML(inventory));

                    //是否售罄
                    if (isNaN(inventory) || inventory <= 0) {
                        $td.find(".calinfo").addClass("sellout");
                        $td.children().removeClass("caldate").addClass("nodate");
                    } else {
                        $td.children().removeClass("caldisabled")
                    }

                    //是否促销
                    if (sale) {
                        var $sale = $('
促
');
                        $calActive.find(".calsale").remove();
                        $calActive.append($sale);
                    }

                    // if (lineRouteName) {
                    //     var $lineRouteName = $('
' + lineRouteName + '
');
                    //     $calActive.find(".calroute").remove();
                    //     $calActive.append($lineRouteName);
                    // }

                }
            });

            //显示促销/线路/休假浮动框
            (function () {

                var festival;  //节日
                var route;  //线路
                var sale;  //促销

                //鼠标移开，隐藏浮动框
                self.wrap.on("mouseleave", "[data-date-map]", function () {
                    var $hover = $(".calhover");
                    $hover.hide();
                    $hover.css({
                        left: 0,
                        top: 0
                    });
                });

                //鼠标移入，显示浮动框
                self.wrap.on("mouseenter", "[data-date-map]", function () {
                    var hasOnce = false;

                    //td单元格
                    var $this = $(this);

                    if ($this.find(".notThisMonth").length !== 0) {
                        return false;
                    }

                    //sting 当前单元格日期字符串
                    var date = $this.attr("data-date-map");

                    //创建浮动框jQuery DOM对象
                    var $hover = createHover();

                    //休假
                    var $calfestival = $('
休
');
                    var $calfestivalContent = $calfestival.find("span");

                    //线路
                    var $calroute = $('

');
                    var $calrouteTitle = $calroute.find("i");
                    var $calrouteContent = $calroute.find("span");

                    //促销
                    var $calsale = $('
促
');
                    var $calsaleContent = $calsale.find("span");

                    //显示坐标
                    var left = $this.offset().left;
                    var top = $this.offset().top + $this.outerHeight();

                    //节日
                    var thatFestival = self.options.festival[date];
                    festival = thatFestival;
                    if (thatFestival) {
                        hasOnce = true;
                        $calfestivalContent.html(thatFestival.vacationName);
                        $hover.append($calfestival);
                    }

                    //获取json数据填充到页面中
                    data.forEach(function (row) {
                        if (row.date == date) {
                            var route = row.lineRouteName;
                            if (row.sale) {
                                var sale = row.sale.replace(/\n/g, '
');
                            }

                            if (route) {
                                hasOnce = true;
                                $calrouteTitle.html(route);
                                $calrouteContent.html("线路");
                                $hover.append($calroute);
                            }
                            if (sale) {
                                hasOnce = true;
                                $calsaleContent.html(sale);
                                $hover.append($calsale);
                            }
                        }
                    });

                    //页面右侧处理，如果屏幕过小，则显示在左侧
                    var width = $hover.outerWidth();

                    var $table = $this.parents(".caltable");
                    var tableLeft = $table.offset().left;
                    var tableWidth = $table.outerWidth();
                    if (width + left - tableLeft > tableWidth) {
                        left = tableLeft + (tableWidth - width);
                        $hover.addClass("calhover-right");
                    }

                    //显示
                    if (hasOnce) {

                        if ($this.children().is(".notThisMonth") && self.options.bimonthly) {
                            //hide
                        } else if (!self.wrap.is(".ui-calendar-mini")) {
                            $hover.show();
                        }

                        $hover.css({
                            left: ~~left,
                            top: ~~top
                        });

                        var triangleLeft = ~~($this.offset().left - left + $this.width() / 2);
                        $hover.find(".triangle").css({
                            left: triangleLeft
                        })

                        if (self.options.zIndex) {
                            $hover.css("zIndex", self.options.zIndex + 1);
                        }
                    }

                });

            })();
        }

        //this.loading();
        setDate(calendarData.calendarInfo);

        calendarTab.changeTab(this.now);
        calendarTab.bindEvent();
    }

});
<commit_msg>修复BUG<commit_after>/**
 * author: Sheng JIANG
 * date: 2017-03-13
 */
$(function () {

    var calendarData = {
        monthInfo: null,
        calendarInfo: null
    };
    var calendarTab;
    var monthArray = [];
    var fourMonthCalendar;

    $.ajax({
        url: "../json/calendar-data-month.json",
        dataType: "json"
    }).done(function (jsonData) {

        calendarData.monthInfo = jsonData.monthInfo;
        calendarData.calendarInfo = jsonData.calendarInfo;

        var date = new Date();
        date = nova.calendar.dateToDay(date);
        date.setDate(1);

        for (var i = 0; i < 12; i++) {
            var tempDate = lv.calendar.monthOffset(date, i);
            var yeahMonth = lv.calendar.dateFormat(tempDate, "yyyy-MM");
            if (calendarData.monthInfo[yeahMonth] && calendarData.monthInfo[yeahMonth].price) {
                monthArray.push(tempDate);
            }
        }

        if (monthArray.length === 0) {
            monthArray = [date];
        }

        initCalendar();
        initCalendarTab();

    }).fail(function () {
        console.error("接口错误");
    });

    function initCalendar() {

        var tabStr = '
' +
            '' +
            '

' +
            '

' +
            '' +
            '
';
        fourMonthCalendar = lv.calendar({
            autoRender: false,
            trigger: ".fourMonthInput",
            triggerEvent: "click",
            sourceFn: fillData,
            completeCallback: function () {

            },
            bimonthly: false,
            template: {
                wrap: '

',
                calWrap: '

' +
                '

' +
                tabStr +
                tabStr +
                tabStr +
                tabStr +
                '

' +
                '

' + '
{{content}}
',
                calTitle: '',
                day: '' +
                '' +
                '
' +
                '
{{day}}
' +
                '

' +
                '

' +
                '

' +
                '

' +
                '
' +
                '	'
            }
        });

    }

    function initCalendarTab() {

        calendarTab = {
            init: function () {
                this.size = monthArray.length;
                this.index = 0;
                this.page = 0;
                this.maxPage = Math.ceil(this.size / 4);
            },
            bindEvent: function () {
                var $wrap = fourMonthCalendar.wrap;
                var $tab = $wrap.find(".nova-calendar-tab");
                var self = this;

                function renderCal(dateStr) {
                    fourMonthCalendar.now = nova.calendar.getDateFromFormattedString(dateStr, "yyyy-MM");
                    fourMonthCalendar.render();
                    self.render();
                    self.bindEvent();
                }

                $wrap.find(".nova-calendar-tabs-prev").on("click", function () {
                    var $this = $(this);
                    if ($this.hasClass("disabled")) {
                        return false;
                    }
                    self.switchTab(-1);
                    var dateStr = $tab.first().attr("data-date");
                    renderCal(dateStr);
                    return false;
                });
                $wrap.find(".nova-calendar-tabs-next").on("click", function () {
                    var $this = $(this);
                    if ($this.hasClass("disabled")) {
                        return false;
                    }
                    self.switchTab(1);
                    var dateStr = $tab.first().attr("data-date");
                    renderCal(dateStr);
                    return false;
                });

                $wrap.find(".nova-calendar-tab").on("click", function () {
                    var $this = $(this);
                    if ($this.hasClass("disabled")) {
                        return false;
                    }
                    var dateStr = $this.attr("data-date");
                    renderCal(dateStr);
                    return false;
                })
            },
            switchTab: function (offset) {
                var page = this.page + offset;
                if (page < 0) {
                    page = 0
                }
                if (page > this.maxPage) {
                    page = this.maxPage;
                }
                this.page = page;
                this.render();
                this.bindEvent();
            },
            render: function () {
                var $wrap = fourMonthCalendar.wrap;
                var $tab = $wrap.find(".nova-calendar-tab");
                var calDate = fourMonthCalendar.now;

                var $prev = $wrap.find(".nova-calendar-tabs-prev");
                var $next = $wrap.find(".nova-calendar-tabs-next");

                $prev.removeClass("disabled");
                $next.removeClass("disabled");

                var page = this.page;
                if (page == 0) {
                    //console.log(0)

                    $prev.addClass("disabled");

                }

                if (page == this.maxPage - 1) {
                    //console.log("max")
                    $next.addClass("disabled");
                }

                for (var j = 0; j < 4; j++) {
                    var $thisTab = $tab.eq(j);
                    var $tabMonth = $thisTab.find(".nova-calendar-tab-month");
                    var tabDate = monthArray[this.page * 4 + j];
                    $thisTab.removeClass("active");
                    if (calDate && tabDate && nova.calendar.isSameMonth(calDate, tabDate)) {
                        $thisTab.addClass("active");
                    }
                    if (tabDate) {
                        var tabDateStr = nova.calendar.dateFormat(tabDate, "yyyy-MM");
                        $tabMonth.text(tabDateStr);
                        $thisTab.attr("data-date", tabDateStr);

                        $thisTab.removeClass("disabled");
                    } else {
                        $tabMonth.text("");
                        $thisTab.attr("data-date", "");
                        $thisTab.addClass("disabled");
                    }
                }
            },
            changeTab: function (date) {
                date = nova.calendar.dateToDay(date);
                date.setDate(1);
                var index = 0;
                for (var i = 0; i < this.size; i++) {
                    if (-date === -monthArray[i]) {
                        index = i;
                    }
                }
                this.index = index;
                this.page = ~~(index / 4);
                this.render();
            }
        };
        calendarTab.init();
    }

    function fillData() {
        var self = this;

        /**
         * 获取剩余HTML
         * @param inventory 剩余数量
         * @returns {string} 生成的HTML
         */
        function getStoreHTML(inventory) {
            var html = "";
            if (isNaN(inventory) || inventory <= 0) {
                html = "售罄";
            } else if (inventory < 10) {
                html = "余" + inventory
            }
            return html;
        }

        /**
         * 创建浮动框
         * @returns {*|jQuery|HTMLElement}  被创建的浮动框jQuery对象
         */
        function createHover() {
            var $hover = $(".calhover");
            if ($hover.length <= 0) {
                $hover = $('

');
            } else {
                $hover.html('

');
            }
            $("body").append($hover);
            $hover.removeClass("calhover-right");
            return $hover;
        }

        /**
         * 设置显示
         * @param data Ajax返回值
         * @returns {boolean}
         */
        function setDate(data) {

            /**
             * 退化的情况，无任何返回值
             */
            if (!data) {
                return false;
            }

            var $allTd = self.wrap.find('td[data-date-map]');  //所有的日历单元格
            $allTd.children().addClass("caldisabled");  //先禁用所有的日历日期

            //对json对象进行迭代处理
            data.forEach(function (row) {
                //row 每个json对象中的元素单元，格式如下所示
                // {
                //     "child": 3688,
                //     "date": "2016-06-11",
                //     "detail": "",
                //     "endDate": "201612",
                //     "lineRouteId": 0,
                //     "lineRouteName": "A",
                //     "next": true,
                //     "prev": false,
                //     "price": 3988,
                //     "surplus": 4,
                //     "type": 1,
                //     "inventory": -1,
                //     "oversold": false,
                //     "sale": "下单满3000减200元，成人儿童可享受，不与其他优惠同享；\n下单满2000减150元，成人儿童可享受，不与其他优惠同享；\n下单满1000减100元，成人儿童可享受，不与其他优惠同享；"
                // }

                var jsonDateStr = row.date;  //json单元-日期

                //将json单元日期字符串转化为JS日历对象(new Date())
                var date = lv.calendar.getDateFromFormattedString(jsonDateStr, self.options.dateFormat);

                //将日历对象转换为字符串(只保留参数dateFormat设定的数据，默认值为年月日)
                var dateStr = lv.calendar.dateFormat(date, self.options.dateFormat);

                //json单元-价格
                var price = row.price;

                //价格-浮点数
                var inventory = parseFloat(row.inventory);

                //json单月-是否促销
                var sale = row.sale;

                //json单元-日期对应文档中的td单元格
                var $td = self.wrap.find('td[data-date-map=' + dateStr + ']');

                var lineRouteName = row.lineRouteName;

                //如果json中的数据有td单元格相对应，则显示数据信息
                if ($td) {

                    //促销等元素的显示位置
                    var $calActive = $td.find(".calactive");

                    //显示价格
                    $td.find(".calprice").html('¥' + price + '起');

                    //显示库存
                    $td.find(".calinfo").html(getStoreHTML(inventory));

                    //是否售罄
                    if (isNaN(inventory) || inventory <= 0) {
                        $td.find(".calinfo").addClass("sellout");
                        $td.children().removeClass("caldate").addClass("nodate");
                    } else {
                        $td.children().removeClass("caldisabled")
                    }

                    //是否促销
                    if (sale) {
                        var $sale = $('
促
');
                        $calActive.find(".calsale").remove();
                        $calActive.append($sale);
                    }

                    // if (lineRouteName) {
                    //     var $lineRouteName = $('
' + lineRouteName + '
');
                    //     $calActive.find(".calroute").remove();
                    //     $calActive.append($lineRouteName);
                    // }

                }
            });

            //显示促销/线路/休假浮动框
            (function () {

                var festival;  //节日
                var route;  //线路
                var sale;  //促销

                //鼠标移开，隐藏浮动框
                self.wrap.on("mouseleave", "[data-date-map]", function () {
                    var $hover = $(".calhover");
                    $hover.hide();
                    $hover.css({
                        left: 0,
                        top: 0
                    });
                });

                //鼠标移入，显示浮动框
                self.wrap.on("mouseenter", "[data-date-map]", function () {
                    var hasOnce = false;

                    //td单元格
                    var $this = $(this);

                    if ($this.find(".notThisMonth").length !== 0) {
                        return false;
                    }

                    //sting 当前单元格日期字符串
                    var date = $this.attr("data-date-map");

                    //创建浮动框jQuery DOM对象
                    var $hover = createHover();

                    //休假
                    var $calfestival = $('
休
');
                    var $calfestivalContent = $calfestival.find("span");

                    //线路
                    var $calroute = $('

');
                    var $calrouteTitle = $calroute.find("i");
                    var $calrouteContent = $calroute.find("span");

                    //促销
                    var $calsale = $('
促
');
                    var $calsaleContent = $calsale.find("span");

                    //显示坐标
                    var left = $this.offset().left;
                    var top = $this.offset().top + $this.outerHeight();

                    //节日
                    var thatFestival = self.options.festival[date];
                    festival = thatFestival;
                    if (thatFestival) {
                        hasOnce = true;
                        $calfestivalContent.html(thatFestival.vacationName);
                        $hover.append($calfestival);
                    }

                    //获取json数据填充到页面中
                    data.forEach(function (row) {
                        if (row.date == date) {
                            var route = row.lineRouteName;
                            if (row.sale) {
                                var sale = row.sale.replace(/\n/g, '
');
                            }

                            if (route) {
                                hasOnce = true;
                                $calrouteTitle.html(route);
                                $calrouteContent.html("线路");
                                $hover.append($calroute);
                            }
                            if (sale) {
                                hasOnce = true;
                                $calsaleContent.html(sale);
                                $hover.append($calsale);
                            }
                        }
                    });

                    //页面右侧处理，如果屏幕过小，则显示在左侧
                    var width = $hover.outerWidth();

                    var $table = $this.parents(".caltable");
                    var tableLeft = $table.offset().left;
                    var tableWidth = $table.outerWidth();
                    if (width + left - tableLeft > tableWidth) {
                        left = tableLeft + (tableWidth - width);
                        $hover.addClass("calhover-right");
                    }

                    //显示
                    if (hasOnce) {

                        if ($this.children().is(".notThisMonth") && self.options.bimonthly) {
                            //hide
                        } else if (!self.wrap.is(".ui-calendar-mini")) {
                            $hover.show();
                        }

                        $hover.css({
                            left: ~~left,
                            top: ~~top
                        });

                        var triangleLeft = ~~($this.offset().left - left + $this.width() / 2);
                        $hover.find(".triangle").css({
                            left: triangleLeft
                        })

                        if (self.options.zIndex) {
                            $hover.css("zIndex", self.options.zIndex + 1);
                        }
                    }

                });

            })();
        }

        //this.loading();
        setDate(calendarData.calendarInfo);

        calendarTab.changeTab(this.now);
        calendarTab.bindEvent();
    }

});
<|endoftext|>


<commit_before>// Business process dedicated list manager
// (used to handle business processes table in official roles)

'use strict';

var d               = require('d')
  , ListManager     = require('../objects-table/manager')
  , resolveList     = require('../objects-table/resolve-list')
  , BusinessProcess = require('mano').db.BusinessProcess;

var BusinessProcessesManager = module.exports = function () {};

BusinessProcessesManager.prototype = Object.create(ListManager.prototype, {
	constructor: d(BusinessProcessesManager),
	_type: d(BusinessProcess),

	// Characterics that needs to be provided per system/user:
	_fullItems: d(null),
	_statusViews: d(null),
	_statusMap: d(null),
	_getItemOrderIndex: d(null),
	_getSearchFilter: d(null),

	_isExternalQuery: d(function (query) {
		// When we have all the items, we don't need to query server
		if (this._statusViews[query.status].totalSize <= 50) return false;
		// If it's not about first page, it's only server that knows
		if (query.page > 1) return true;
		// If it's purely a first page of a status, we know
		return this._modifiers.some(function (mod) {
			return (mod.name !== 'status') && query[mod.name];
		});
	}),
	_isItemApplicable: d(function (item, query) {
		if (!this._statusMap[query.status].data.has(item)) return false;
		if (!query.search) return true;
		return this._getSearchFilter(query.search)(item);
	}),
	_modifiers: d([
		{
			name: 'status',
			process: function (ignore, query) {
				var view = this._statusViews[query.status]
				  , list = this._resolveList({ view: view.get(1), size: view.totalSize }, query);
				return {
					list: list,
					size: (view.totalSize < this.itemsPerPage) ? list.length : view.totalSize
				};
			}
		},
		{
			name: 'search',
			process: function (data, query) {
				var list = data.list.filter(this._getSearchFilter(query.search));
				console.log("SEARCH", data.list.length, list.length);
				return { list: list, size: list.length };
			}
		}
	]),
	_resolveList: d(resolveList)
});
<commit_msg>Update after changes in query handler<commit_after>// Business process dedicated list manager
// (used to handle business processes table in official roles)

'use strict';

var d               = require('d')
  , ListManager     = require('../objects-table/manager')
  , resolveList     = require('../objects-table/resolve-list')
  , BusinessProcess = require('mano').db.BusinessProcess;

var BusinessProcessesManager = module.exports = function () {};

BusinessProcessesManager.prototype = Object.create(ListManager.prototype, {
	constructor: d(BusinessProcessesManager),
	_type: d(BusinessProcess),

	// Characterics that needs to be provided per system/user:
	_fullItems: d(null),
	_statusViews: d(null),
	_statusMap: d(null),
	_getItemOrderIndex: d(null),
	_getSearchFilter: d(null),

	_isExternalQuery: d(function (query) {
		// When we have all the items, we don't need to query server
		if (this._statusViews[query.status || 'all'].totalSize <= 50) return false;
		// If it's not about first page, it's only server that knows
		if (query.page > 1) return true;
		// If it's purely a first page of a status, we know
		return this._modifiers.some(function (mod) {
			return (mod.name !== 'status') && query[mod.name];
		});
	}),
	_isItemApplicable: d(function (item, query) {
		if (!this._statusMap[query.status].data.has(item)) return false;
		if (!query.search) return true;
		return this._getSearchFilter(query.search)(item);
	}),
	_modifiers: d([
		{
			name: 'status',
			process: function (ignore, query) {
				var view = this._statusViews[query.status]
				  , list = this._resolveList({ view: view.get(1), size: view.totalSize }, query);
				return {
					list: list,
					size: (view.totalSize < this.itemsPerPage) ? list.length : view.totalSize
				};
			}
		},
		{
			name: 'search',
			process: function (data, query) {
				var list = data.list.filter(this._getSearchFilter(query.search));
				console.log("SEARCH", data.list.length, list.length);
				return { list: list, size: list.length };
			}
		}
	]),
	_resolveList: d(resolveList)
});
<|endoftext|>


<commit_before>var domify = require('domify');
var dom = require('ampersand-dom');
var matches = require('matches-selector');

//Replaceable with anything with label, message-container, message-text data-hooks and a <select>
var defaultTemplate = [
    '',
        '',
        '<select></select>',
        '',
            '

',
        '',
    ''
].join('\n');

/* opts:
 *  - name:       name of the field
 *  - parent:     parent form reference
 *  - options:    array/collection of options to render into the select box
 *  - [unselectedText]: text to display if unselected
 *  - [value]:    initial value for the field
 *  - [el]:       dom node to use for the view
 *  - [required]: is field required
 *
 *  - [validClass]: class to apply to root element if valid
 *  - [invalidClass]: class to apply to root element if invalid
 *  - [requiredMessage]: message to display if invalid and required
 *
 *  Additional opts, if options is a collection:
 *  - [idAttribute]: model attribute to use as the id for the option node
 *  - [textAttribute]: model attribute to use as the text of the option node in the select box
 *  - [disabledAttribute]: model attribute to disable the option node
 *  - [yieldModel]: (defaults true) if options is a collection, yields the full model rather than just it's id to .value
 */

function SelectView (opts) {
    opts = opts || {};

    if (typeof opts.name !== 'string') throw new Error('SelectView requires a name property.');
    this.name = opts.name;

    if (!Array.isArray(opts.options) && !opts.options.isCollection) {
        throw new Error('SelectView requires select options.');
    }
    this.options = opts.options;

    if (this.options.isCollection) {
        this.idAttribute = opts.idAttribute || this.options.mainIndex || 'id';
        this.textAttribute = opts.textAttribute || 'text';
        this.disabledAttribute = opts.disabledAttribute || 'disabled';
    }

    this.el = opts.el;
    this.value = null;
    this.label = opts.label || this.name;
    this.parent = opts.parent;
    this.template = opts.template || defaultTemplate;
    this.unselectedText = opts.unselectedText;
    this.yieldModel = (opts.yieldModel === false) ? false : true;

    this.required = opts.required || false;
    this.validClass = opts.validClass || 'input-valid';
    this.invalidClass = opts.invalidClass || 'input-invalid';
    this.requiredMessage = opts.requiredMessage || 'Selection required';

    this.onChange = this.onChange.bind(this);

    this.render();

    this.setValue(opts.value);
}

SelectView.prototype.render = function () {
    if (this.rendered) return;

    if (!this.el) this.el = domify(this.template);

    var label = this.el.querySelector('[data-hook~=label]');
    if (label) {
        label.textContent = this.label;
    }

    this.select = this.el.querySelector('select');
    this.select.name = this.name;
    if (matches(this.el, 'select')) this.select = this.el;

    this.bindDOMEvents();
    this.renderOptions();
    this.updateSelectedOption();

    if (this.options.isCollection) {
        this.options.on('add', function () {
            this.renderOptions();
            this.updateSelectedOption();
        }.bind(this));
    }

    this.rendered = true;
};

SelectView.prototype.onChange = function () {
    var value = this.select.options[this.select.selectedIndex].value;

    if (this.options.isCollection && this.yieldModel) {
        value = this.findModelForId(value);
    }

    this.setValue(value);
};

SelectView.prototype.findModelForId = function (id) {
    return this.options.filter(function (model) {
        if (!model[this.idAttribute]) return false;

        //intentionally coerce for '1' == 1
        return model[this.idAttribute] == id;
    }.bind(this))[0];
};

SelectView.prototype.bindDOMEvents = function () {
    this.el.addEventListener('change', this.onChange, false);
};

SelectView.prototype.renderOptions = function () {
    if (!this.select) return;

    this.select.innerHTML = '';
    if (this.unselectedText) {
        this.select.appendChild(
            createOption(null, this.unselectedText)
        );
    }

    this.options.forEach(function (option) {
        this.select.appendChild(
            createOption(this.getOptionValue(option), this.getOptionText(option), this.getOptionDisabled(option))
        );
    }.bind(this));
};

SelectView.prototype.updateSelectedOption = function () {
    var lookupValue = this.value;

    if (!this.select) return;

    if (!lookupValue) {
        this.select.selectedIndex = 0;
        return;
    }

    //Pull out the id if it's a model
    if (this.options.isCollection && this.yieldModel) {
        lookupValue = lookupValue && lookupValue[this.idAttribute];
    }

    if (lookupValue) {
        for (var i = this.select.options.length; i--; i) {
            if (this.select.options[i].value === lookupValue.toString()) {
                this.select.selectedIndex = i;
                return;
            }
        }
    }

    //If failed to match any
    this.select.selectedIndex = 0;
};

SelectView.prototype.remove = function () {
    if (this.el) this.el.parentNode.removeChild(this.el);
    this.el.removeEventListener('change', this.onChange, false);
};

SelectView.prototype.clear = function() {
    this.shouldValidate = false;
    this.setValue(null);
};

SelectView.prototype.setValue = function (value) {
    if (value === this.value) return;

    //Coerce and find the right value based on yieldModel
    if (this.options.isCollection) {
        var model;

        if (this.options.indexOf(value) === -1) {
            model = this.findModelForId(value);
        } else {
            model = value;
        }

        if (this.yieldModel) {
            value = model;
        } else {
            if (model) {
                value = model[this.idAttribute];
            } else {
                value = void 0;
            }
        }
    }

    this.value = value;
    this.validate();
    this.updateSelectedOption();
    if (this.parent) this.parent.update(this);
};

SelectView.prototype.validate = function () {
    if(!this.value && !this.required) {
        this.valid = true;
    } else {
        this.valid = this.options.some(function (element) {

            //If it's a collection, ensure it's in the collection
            if (this.options.isCollection) {
                if (this.yieldModel) {
                    return this.options.indexOf(this.value) > -1;
                } else {
                    return !!this.findModelForId(this.value);
                }
            }

            //[ ['foo', 'Foo Text'], ['bar', 'Bar Text'] ]
            if (Array.isArray(element) && element.length === 2) {
                return element[0] === this.value;
            }

            //[ 'foo', 'bar', 'baz' ]
            return element === this.value;
        }.bind(this));
    }

    if (!this.valid && this.required) {
        this.setMessage(this.requiredMessage);
    } else {
        this.setMessage();
    }

    return this.valid;
};

SelectView.prototype.getOptionValue = function (option) {
    if (Array.isArray(option)) return option[0];

    if (this.options.isCollection) {
        if (this.idAttribute && option[this.idAttribute]) {
            return option[this.idAttribute];
        }
    }

    return option;
};

SelectView.prototype.getOptionDisabled = function (option) {
    if (Array.isArray(option)) return;

    if (this.options.isCollection) {
        if (this.disabledAttribute && option[this.disabledAttribute]) {
            return option[this.disabledAttribute];
        } else {
            return false;
        }
    }

    return option;
};

SelectView.prototype.setMessage = function (message) {
    var mContainer = this.el.querySelector('[data-hook~=message-container]');
    var mText = this.el.querySelector('[data-hook~=message-text]');

    if (!mContainer || !mText) return;

    if (message) {
        dom.show(mContainer);
        mText.textContent = message;
        dom.addClass(this.el, this.invalidClass);
        dom.removeClass(this.el, this.validClass);
    } else {
        dom.hide(mContainer);
        mText.textContent = '';
        dom.addClass(this.el, this.validClass);
        dom.removeClass(this.el, this.invalidClass);
    }
};

SelectView.prototype.getOptionText = function (option) {
    if (Array.isArray(option)) return option[1];

    if (this.options.isCollection) {
        if (this.textAttribute && option[this.textAttribute]) {
            return option[this.textAttribute];
        }
    }

    return option;
};

function createOption (value, text, disabled) {
    var node = document.createElement('option');

    //Set to empty-string if undefined or null, but not if 0, false, etc
    if (value === null || value === undefined) { value = ''; }

    if(disabled) {
        node.disabled = true;
    }

    node.textContent = text;
    node.value = value;

    return node;
}

module.exports = SelectView;
<commit_msg>Update comments, only set name on select if name not set via markup<commit_after>var domify = require('domify');
var dom = require('ampersand-dom');
var matches = require('matches-selector');

//Replaceable with anything with label, message-container, message-text data-hooks and a <select>
var defaultTemplate = [
    '',
        '',
        '<select></select>',
        '',
            '

',
        '',
    ''
].join('\n');

/* opts:
 *  - name:       name of the field
 *  - parent:     parent form reference
 *  - options:    array/collection of options to render into the select box
 *  - [unselectedText]: text to display if unselected
 *  - [value]:    initial value for the field
 *  - [el]:       dom node to use for the view
 *  - [required]: is field required
 *
 *  - [validClass]: class to apply to root element if valid
 *  - [invalidClass]: class to apply to root element if invalid
 *  - [requiredMessage]: message to display if invalid and required
 *
 *  Additional opts, if options is a collection:
 *  - [idAttribute]: model attribute to use as the id for the option node
 *  - [textAttribute]: model attribute to use as the text of the option node in the select box
 *  - [disabledAttribute]: boolean model attribute to flag disabling of the option node
 *  - [yieldModel]: (defaults true) if options is a collection, yields the full model rather than just it's id to .value
 */

function SelectView (opts) {
    opts = opts || {};

    if (typeof opts.name !== 'string') throw new Error('SelectView requires a name property.');
    this.name = opts.name;

    if (!Array.isArray(opts.options) && !opts.options.isCollection) {
        throw new Error('SelectView requires select options.');
    }
    this.options = opts.options;

    if (this.options.isCollection) {
        this.idAttribute = opts.idAttribute || this.options.mainIndex || 'id';
        this.textAttribute = opts.textAttribute || 'text';
        this.disabledAttribute = opts.disabledAttribute || 'disabled';
    }

    this.el = opts.el;
    this.value = null;
    this.label = opts.label || this.name;
    this.parent = opts.parent;
    this.template = opts.template || defaultTemplate;
    this.unselectedText = opts.unselectedText;
    this.yieldModel = (opts.yieldModel === false) ? false : true;

    this.required = opts.required || false;
    this.validClass = opts.validClass || 'input-valid';
    this.invalidClass = opts.invalidClass || 'input-invalid';
    this.requiredMessage = opts.requiredMessage || 'Selection required';

    this.onChange = this.onChange.bind(this);

    this.render();

    this.setValue(opts.value);
}

SelectView.prototype.render = function () {
    if (this.rendered) return;

    if (!this.el) this.el = domify(this.template);

    var label = this.el.querySelector('[data-hook~=label]');
    if (label) {
        label.textContent = this.label;
    }

    this.select = this.el.querySelector('select');
    if (matches(this.el, 'select')) this.select = this.el;

    if(!this.select.name) {
      this.select.name = this.name;
    }

    this.bindDOMEvents();
    this.renderOptions();
    this.updateSelectedOption();

    if (this.options.isCollection) {
        this.options.on('add', function () {
            this.renderOptions();
            this.updateSelectedOption();
        }.bind(this));
    }

    this.rendered = true;
};

SelectView.prototype.onChange = function () {
    var value = this.select.options[this.select.selectedIndex].value;

    if (this.options.isCollection && this.yieldModel) {
        value = this.findModelForId(value);
    }

    this.setValue(value);
};

SelectView.prototype.findModelForId = function (id) {
    return this.options.filter(function (model) {
        if (!model[this.idAttribute]) return false;

        //intentionally coerce for '1' == 1
        return model[this.idAttribute] == id;
    }.bind(this))[0];
};

SelectView.prototype.bindDOMEvents = function () {
    this.el.addEventListener('change', this.onChange, false);
};

SelectView.prototype.renderOptions = function () {
    if (!this.select) return;

    this.select.innerHTML = '';
    if (this.unselectedText) {
        this.select.appendChild(
            createOption(null, this.unselectedText)
        );
    }

    this.options.forEach(function (option) {
        this.select.appendChild(
            createOption(this.getOptionValue(option), this.getOptionText(option), this.getOptionDisabled(option))
        );
    }.bind(this));
};

SelectView.prototype.updateSelectedOption = function () {
    var lookupValue = this.value;

    if (!this.select) return;

    if (!lookupValue) {
        this.select.selectedIndex = 0;
        return;
    }

    //Pull out the id if it's a model
    if (this.options.isCollection && this.yieldModel) {
        lookupValue = lookupValue && lookupValue[this.idAttribute];
    }

    if (lookupValue) {
        for (var i = this.select.options.length; i--; i) {
            if (this.select.options[i].value === lookupValue.toString()) {
                this.select.selectedIndex = i;
                return;
            }
        }
    }

    //If failed to match any
    this.select.selectedIndex = 0;
};

SelectView.prototype.remove = function () {
    if (this.el) this.el.parentNode.removeChild(this.el);
    this.el.removeEventListener('change', this.onChange, false);
};

SelectView.prototype.clear = function() {
    this.shouldValidate = false;
    this.setValue(null);
};

SelectView.prototype.setValue = function (value) {
    if (value === this.value) return;

    //Coerce and find the right value based on yieldModel
    if (this.options.isCollection) {
        var model;

        if (this.options.indexOf(value) === -1) {
            model = this.findModelForId(value);
        } else {
            model = value;
        }

        if (this.yieldModel) {
            value = model;
        } else {
            if (model) {
                value = model[this.idAttribute];
            } else {
                value = void 0;
            }
        }
    }

    this.value = value;
    this.validate();
    this.updateSelectedOption();
    if (this.parent) this.parent.update(this);
};

SelectView.prototype.validate = function () {
    if(!this.value && !this.required) {
        this.valid = true;
    } else {
        this.valid = this.options.some(function (element) {

            //If it's a collection, ensure it's in the collection
            if (this.options.isCollection) {
                if (this.yieldModel) {
                    return this.options.indexOf(this.value) > -1;
                } else {
                    return !!this.findModelForId(this.value);
                }
            }

            //[ ['foo', 'Foo Text'], ['bar', 'Bar Text'] ]
            if (Array.isArray(element) && element.length === 2) {
                return element[0] === this.value;
            }

            //[ 'foo', 'bar', 'baz' ]
            return element === this.value;
        }.bind(this));
    }

    if (!this.valid && this.required) {
        this.setMessage(this.requiredMessage);
    } else {
        this.setMessage();
    }

    return this.valid;
};

SelectView.prototype.getOptionValue = function (option) {
    if (Array.isArray(option)) return option[0];

    if (this.options.isCollection) {
        if (this.idAttribute && option[this.idAttribute]) {
            return option[this.idAttribute];
        }
    }

    return option;
};

SelectView.prototype.getOptionDisabled = function (option) {
    if (Array.isArray(option)) return;

    if (this.options.isCollection) {
        if (this.disabledAttribute && option[this.disabledAttribute]) {
            return option[this.disabledAttribute];
        } else {
            return false;
        }
    }

    return option;
};

SelectView.prototype.setMessage = function (message) {
    var mContainer = this.el.querySelector('[data-hook~=message-container]');
    var mText = this.el.querySelector('[data-hook~=message-text]');

    if (!mContainer || !mText) return;

    if (message) {
        dom.show(mContainer);
        mText.textContent = message;
        dom.addClass(this.el, this.invalidClass);
        dom.removeClass(this.el, this.validClass);
    } else {
        dom.hide(mContainer);
        mText.textContent = '';
        dom.addClass(this.el, this.validClass);
        dom.removeClass(this.el, this.invalidClass);
    }
};

SelectView.prototype.getOptionText = function (option) {
    if (Array.isArray(option)) return option[1];

    if (this.options.isCollection) {
        if (this.textAttribute && option[this.textAttribute]) {
            return option[this.textAttribute];
        }
    }

    return option;
};

function createOption (value, text, disabled) {
    var node = document.createElement('option');

    //Set to empty-string if undefined or null, but not if 0, false, etc
    if (value === null || value === undefined) { value = ''; }

    if(disabled) {
        node.disabled = true;
    }

    node.textContent = text;
    node.value = value;

    return node;
}

module.exports = SelectView;
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import Link from 'next/link'
import { translate } from 'react-i18next'

import Section from './section'

const Events = ({ t }) => (

    <Link href='/events'>
      {t('eventsLink')}
    </Link>

)

Events.propTypes = {
  t: PropTypes.func.isRequired
}

export default translate('home')(Events)
<commit_msg>Fix link to event page<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { translate } from 'react-i18next'

import Link from '../link'
import Section from './section'

const Events = ({ t }) => (

    <Link href='/events'>
      {t('eventsLink')}
    </Link>

)

Events.propTypes = {
  t: PropTypes.func.isRequired
}

export default translate('home')(Events)
<|endoftext|>


<commit_before>define([
  'angular',
  'lodash',
  'kbn',
  'moment',
  './queryCtrl'
],
function (angular, _, kbn) {
  'use strict';

  var module = angular.module('grafana.services');

  module.factory('WarpDatasource', function($q, $http, templateSrv) {

    function WarpDatasource(datasource) {
      this.type = 'einstein';
      this.editorSrc = 'app/features/warp/partials/query.editor.html';
      this.name = datasource.name;
      this.supportMetrics = true;

      var url = datasource.url;
      if (url[url.length-1] === '/') {
        // remove trailing slash
        url = url.substr(0, url.length - 1);
      }
      this.url = url;
      this.lastErrors = {};
    }

    // Called once per panel (graph)
    WarpDatasource.prototype.query = function(options) {

      console.log("Query begin");

      var end = convertToWarpTime(options.range.to);
      var start = convertToWarpTime(options.range.from);

      console.log("From: "+start+ " To: "+end);

      var queries = [];

      console.log("Before foreach");

      _.each(options.targets, _.bind(function(target) {

        console.debug("Expre", target.expr);
        console.debug("Backend URL", target.backend);

        queries.push(target);
      }, this));

      console.log("After foreach");

      // No valid targets, return the empty result to save a round trip.
      if (_.isEmpty(queries)) {

        console.log("Empty query");

        var d = $q.defer();
        d.resolve({ data: [] });
        return d.promise;
      }

      var allQueryPromise = _.map(queries, _.bind(function(query) {
        return this.performTimeSeriesQuery(query, start, end);
      }, this));

      var self = this;
      return $q.all(allQueryPromise)
        .then(function(allResponse) {
          var result = [];

          _.each(allResponse, function(response, index) {

            console.log("Response", response);
            if (response.data.type === 'error') {
              self.lastErrors.query = response.data.value;
              throw response.data.value;
            }
            delete self.lastErrors.query;

            if (!isArray(response.data) || (response.data.length != 1)) {
              console.debug("Response isn't an Array or it has more than 1 element", response.data);
              return {};
            }

            var einsteinJsonResponse = response.data[0];

            console.log("Response data", einsteinJsonResponse)

            _.each(einsteinJsonResponse, function(metricData) {
              console.log("Metric data",metricData);

              result.push(transformMetricData(metricData, options.targets[index]));
            });
          });

          return { data: result };
        });
    };

    /***********************************************************************************
    *Puts into the Einstein script a header to place start and end ont the stack
    ***********************************************************************************/
    WarpDatasource.prototype.prepareEinsteinQuery = function(query, start, end) {

      var endISO = convertToISO(end);
      var startISO = convertToISO(start);
      var interval = end - start;

      var einsteinScript =
            "" + start + " 'start' STORE " + end + " 'end' STORE " +
            "'" + startISO + "' 'startISO' STORE '" + endISO + "' 'endISO' STORE " +
            interval + " 'interval' STORE " + query.expr;
      return einsteinScript;
    }

    /***********************************************************************************
    * Generate @query Einstein http query to the Einstein API entry point
    ***********************************************************************************/
    WarpDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {

      var einsteinScript = this.prepareEinsteinQuery(query, start, end);

      var backend = this.url;
      // If we have defined a backend in the query editor, it takes preecedence
      // over the datasource
      if ((query.backend !== undefined) && (query.backend.length >0)) {
        backend = query.backend;
      }

      var url = backend + '/api/v0/exec/einstein'

      var options = {
        method: 'POST',
        url: url,
        data: einsteinScript,
        headers: {
            'Accept': undefined,
            'Content-Type': undefined,
            'X-CityzenData-Token': 'token'
        }
      };

      return $http(options);
    };

    /***********************************************************************************
    * Transform from Einstein JSON to Grafana dps
    ***********************************************************************************/
    function transformMetricData(gts, options) {

      if (!isGts(gts)) {
        console.debug("Response item isn't a gts",gts);
        return;
      }

      var className = gts.c + JSON.stringify(gts.l);

      var dps = [];

      _.each(gts.v, function(value) {
        // Datapoint format: [ value, label]
        dps.push([value[value.length -1], Math.floor(value[0]/1000)]);
      });

      // Metric format {target: "Label text", datapoints: [ datapoints objects] }

      console.debug({ target: className, datapoints: dps });
      return { target: className, datapoints: dps };
    }

    /***********************************************************************************
    * Returns true if @value is an Array
    ***********************************************************************************/
    function isArray(value) {
      return value && typeof value === 'object' && value instanceof Array && typeof value.length === 'number'
        && typeof value.splice === 'function' && !(value.propertyIsEnumerable('length'));
    };

    /***********************************************************************************
    * Returns true if @gts is a JSON object version of a GTS
    ***********************************************************************************/
    function isGts(gts) {
      if ((gts.c == null) || (gts.l == null) || (gts.a == null) || (gts.v == null)) {
        return false;
      }
      return true;
    };

    /***********************************************************************************
    * Converts @date into µs since Epoch time (Einstein tick format)
    ***********************************************************************************/
    function convertToWarpTime(date) {
      date = kbn.parseDate(date);
      return date.getTime() * 1000;
    }

    /***********************************************************************************
    * Converts @timestamp into ISO 8601 format
    ***********************************************************************************/
    function convertToISO(timestamp) {
      var date = new Date(Math.floor(timestamp/1000));
      return date.toISOString();
    }

    return WarpDatasource;
  });

});
<commit_msg>Better metric names<commit_after>define([
  'angular',
  'lodash',
  'kbn',
  'moment',
  './queryCtrl'
],
function (angular, _, kbn) {
  'use strict';

  var module = angular.module('grafana.services');

  module.factory('WarpDatasource', function($q, $http, templateSrv) {

    function WarpDatasource(datasource) {
      this.type = 'einstein';
      this.editorSrc = 'app/features/warp/partials/query.editor.html';
      this.name = datasource.name;
      this.supportMetrics = true;

      var url = datasource.url;
      if (url[url.length-1] === '/') {
        // remove trailing slash
        url = url.substr(0, url.length - 1);
      }
      this.url = url;
      this.lastErrors = {};
    }

    // Called once per panel (graph)
    WarpDatasource.prototype.query = function(options) {

      console.log("Query begin");

      var end = convertToWarpTime(options.range.to);
      var start = convertToWarpTime(options.range.from);

      console.log("From: "+start+ " To: "+end);

      var queries = [];

      console.log("Before foreach");

      _.each(options.targets, _.bind(function(target) {

        console.debug("Expre", target.expr);
        console.debug("Backend URL", target.backend);

        queries.push(target);
      }, this));

      console.log("After foreach");

      // No valid targets, return the empty result to save a round trip.
      if (_.isEmpty(queries)) {

        console.log("Empty query");

        var d = $q.defer();
        d.resolve({ data: [] });
        return d.promise;
      }

      var allQueryPromise = _.map(queries, _.bind(function(query) {
        return this.performTimeSeriesQuery(query, start, end);
      }, this));

      var self = this;
      return $q.all(allQueryPromise)
        .then(function(allResponse) {
          var result = [];

          _.each(allResponse, function(response, index) {

            console.log("Response", response);
            if (response.data.type === 'error') {
              self.lastErrors.query = response.data.value;
              throw response.data.value;
            }
            delete self.lastErrors.query;

            if (!isArray(response.data) || (response.data.length != 1)) {
              console.debug("Response isn't an Array or it has more than 1 element", response.data);
              return {};
            }

            var einsteinJsonResponse = response.data[0];

            console.log("Response data", einsteinJsonResponse)

            _.each(einsteinJsonResponse, function(metricData) {
              console.log("Metric data",metricData);

              result.push(transformMetricData(metricData, options.targets[index]));
            });
          });

          return { data: result };
        });
    };

    /***********************************************************************************
    *Puts into the Einstein script a header to place start and end ont the stack
    ***********************************************************************************/
    WarpDatasource.prototype.prepareEinsteinQuery = function(query, start, end) {

      var endISO = convertToISO(end);
      var startISO = convertToISO(start);
      var interval = end - start;

      var einsteinScript =
            "" + start + " 'start' STORE " + end + " 'end' STORE " +
            "'" + startISO + "' 'startISO' STORE '" + endISO + "' 'endISO' STORE " +
            interval + " 'interval' STORE " + query.expr;
      return einsteinScript;
    }

    /***********************************************************************************
    * Generate @query Einstein http query to the Einstein API entry point
    ***********************************************************************************/
    WarpDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {

      var einsteinScript = this.prepareEinsteinQuery(query, start, end);

      var backend = this.url;
      // If we have defined a backend in the query editor, it takes preecedence
      // over the datasource
      if ((query.backend !== undefined) && (query.backend.length >0)) {
        backend = query.backend;
      }

      var url = backend + '/api/v0/exec/einstein'

      var options = {
        method: 'POST',
        url: url,
        data: einsteinScript,
        headers: {
            'Accept': undefined,
            'Content-Type': undefined,
            'X-CityzenData-Token': 'token'
        }
      };

      return $http(options);
    };

    /***********************************************************************************
    * Transform from Einstein JSON to Grafana dps
    ***********************************************************************************/
    function transformMetricData(gts, options) {

      if (!isGts(gts)) {
        console.debug("Response item isn't a gts",gts);
        return;
      }

      var className = gts.c + JSON.stringify(gts.l);

      var labels =
        _.map(gts.l, function(value, key){
          return key+":"+value
        }).join(",");

      var metricName = className+"{"+labels+"}"
      var dps = [];

      _.each(gts.v, function(value) {
        // Datapoint format: [ value, label]
        dps.push([value[value.length -1], Math.floor(value[0]/1000)]);
      });

      // Metric format {target: "Label text", datapoints: [ datapoints objects] }

      console.debug({ target: metricName , datapoints: dps });
      return { target: metricName, datapoints: dps };
    }

    /***********************************************************************************
    * Returns true if @value is an Array
    ***********************************************************************************/
    function isArray(value) {
      return value && typeof value === 'object' && value instanceof Array && typeof value.length === 'number'
        && typeof value.splice === 'function' && !(value.propertyIsEnumerable('length'));
    };

    /***********************************************************************************
    * Returns true if @gts is a JSON object version of a GTS
    ***********************************************************************************/
    function isGts(gts) {
      if ((gts.c == null) || (gts.l == null) || (gts.a == null) || (gts.v == null)) {
        return false;
      }
      return true;
    };

    /***********************************************************************************
    * Converts @date into µs since Epoch time (Einstein tick format)
    ***********************************************************************************/
    function convertToWarpTime(date) {
      date = kbn.parseDate(date);
      return date.getTime() * 1000;
    }

    /***********************************************************************************
    * Converts @timestamp into ISO 8601 format
    ***********************************************************************************/
    function convertToISO(timestamp) {
      var date = new Date(Math.floor(timestamp/1000));
      return date.toISOString();
    }

    return WarpDatasource;
  });

});
<|endoftext|>


<commit_before>const _ = require('underscore')
const {ipcRenderer} = require('electron')
const {Actions, OnlineStatusStore, IdentityStore} = require('nylas-exports')
const SyncWorker = require('./sync-worker');
const LocalSyncDeltaEmitter = require('./local-sync-delta-emitter').default
const LocalDatabaseConnector = require('../shared/local-database-connector')
const SyncActivity = require('../shared/sync-activity').default

const MAX_WORKER_SILENCE_MS = Math.max(
  SyncWorker.AC_SYNC_LOOP_INTERVAL_MS,
  SyncWorker.BATTERY_SYNC_LOOP_INTERVAL_MS,
  SyncWorker.MAX_SYNC_BACKOFF_MS,
)
const CHECK_HEALTH_TIME_INTERVAL = 1 * 60 * 1000

class SyncProcessManager {
  constructor() {
    this._exiting = false;
    this._resettingEmailCache = false
    this._identityId = IdentityStore.identityId();
    this._workersByAccountId = {};
    this._localSyncDeltaEmittersByAccountId = new Map()

    OnlineStatusStore.listen(this._onOnlineStatusChanged, this)
    IdentityStore.listen(this._onIdentityChanged, this)
    Actions.resetEmailCache.listen(this._resetEmailCache, this)
    Actions.debugSync.listen(this._onDebugSync, this)
    Actions.wakeLocalSyncWorkerForAccount.listen((accountId) =>
      this.wakeWorkerForAccount(accountId, {interrupt: true})
    );
    ipcRenderer.on('app-resumed-from-sleep', () => {
      this._wakeAllWorkers({reason: 'Computer resumed from sleep', interrupt: true})
    })

    this._checkHealthInterval = null;
  }

  _onOnlineStatusChanged() {
    if (OnlineStatusStore.isOnline()) {
      this._wakeAllWorkers({reason: 'Came back online', interrupt: true})
    }
  }

  _onIdentityChanged() {
    const newIdentityId = IdentityStore.identityId()
    if (newIdentityId !== this._identityId) {
      // The IdentityStore can trigger any number of times, but we only want to
      // start sync if we previously didn't have an identity available
      this._identityId = newIdentityId
      this.start()
    }
  }

  _onDebugSync() {
    const win = NylasEnv.getCurrentWindow()
    win.show()
    win.maximize()
    win.openDevTools()
  }

  _wakeAllWorkers({reason, interrupt} = {}) {
    Object.keys(this._workersByAccountId).forEach((id) => {
      this.wakeWorkerForAccount(id, {reason, interrupt})
    })
  }

  async _resetEmailCache() {
    if (this._resettingEmailCache) return;
    this._resettingEmailCache = true
    try {
      try {
        await Promise.all(
          this.workers().map(w => w.destroy())
        )
        .timeout(500, 'Timed out while trying to stop sync')
      } catch (err) {
        global.Logger.warn('SyncProcessManager._resetEmailCache: Error while stopping sync', err)
      }
      const accountIds = Object.keys(this._workersByAccountId)
      for (const accountId of accountIds) {
        await LocalDatabaseConnector.destroyAccountDatabase(accountId)
      }

      ipcRenderer.send('command', 'application:relaunch-to-initial-windows', {
        resetDatabase: true,
      })
    } catch (err) {
      global.Logger.error('Error resetting email cache', err)
    } finally {
      this._resettingEmailCache = false
    }
  }

  _checkHealthByAccountId = async (accountId) => {
    const {time, activity} = SyncActivity.getLastSyncActivityForAccount(accountId);
    if (time < Date.now() - this.MAX_WORKER_SILENCE_MS) {
      const duration = Date.now() - time;
      Actions.recordUserEvent('Stuck Sync Process', {
        accountId: accountId,
        lastActivityTime: time,
        lastActivity: activity,
        duration,
      })
      const {Account} = await LocalDatabaseConnector.forShared();
      const account = await Account.findById(accountId)
      const logger = global.Logger.forAccount(account)
      logger.warn(`SyncProcessManager: Detected stuck worker. Last recorded activity: ${activity} at ${new Date(time)}`)

      await this.removeWorkerForAccountId(accountId, {timeout: 5 * 1000})
      await this.addWorkerForAccount(account)
    }
  }

  _checkHealth = async () => {
    return Promise.all(Object.keys(this._workersByAccountId).map(this._checkHealthByAccountId))
  }

  /**
   * Useful for debugging.
   */
  async start() {
    if (!IdentityStore.identity()) {
      global.Logger.log(`SyncProcessManager: Can't start sync; no Nylas Identity present`)
      return
    }
    global.Logger.log(`SyncProcessManager: Starting sync`)

    const {Account} = await LocalDatabaseConnector.forShared();
    const accounts = await Account.findAll();
    await Promise.all(accounts.map(this.addWorkerForAccount));

    if (!this._checkHealthInterval) {
      this._checkHealthInterval = setInterval(this._checkHealth, this.CHECK_HEALTH_TIME_INTERVAL)
    }
  }

  workers() {
    return _.values(this._workersByAccountId)
  }

  dbs() {
    return this.workers().map(w => w._db)
  }

  wakeWorkerForAccount(accountId, {reason = 'Waking sync', interrupt} = {}) {
    const worker = this._workersByAccountId[accountId]
    if (worker) {
      worker.syncNow({reason, interrupt});
    }
  }

  addWorkerForAccount = async (account) => {
    await LocalDatabaseConnector.ensureAccountDatabase(account.id);
    const logger = global.Logger.forAccount(account)

    try {
      if (this._workersByAccountId[account.id]) {
        logger.warn(`SyncProcessManager.addWorkerForAccount: Worker for account already exists - skipping`)
        return
      }
      const db = await LocalDatabaseConnector.forAccount(account.id);
      this._workersByAccountId[account.id] = new SyncWorker(account, db, this);

      const localSyncDeltaEmitter = new LocalSyncDeltaEmitter(account, db)
      await localSyncDeltaEmitter.activate()
      this._localSyncDeltaEmittersByAccountId.set(account.id, localSyncDeltaEmitter)
      logger.log(`SyncProcessManager: Claiming Account Succeeded`)
    } catch (err) {
      logger.error(`SyncProcessManager: Claiming Account Failed`, err)
    }
  }

  async removeWorkerForAccountId(accountId, {timeout} = {}) {
    if (this._workersByAccountId[accountId]) {
      await this._workersByAccountId[accountId].destroy({timeout})
      this._workersByAccountId[accountId] = null;
    }

    if (this._localSyncDeltaEmittersByAccountId.has(accountId)) {
      this._localSyncDeltaEmittersByAccountId.get(accountId).deactivate();
      this._localSyncDeltaEmittersByAccountId.delete(accountId)
    }
  }
}

window.$n.SyncProcessManager = new SyncProcessManager();
window.$n.SyncProcessManager.MAX_WORKER_SILENCE_MS = MAX_WORKER_SILENCE_MS
window.$n.SyncProcessManager.CHECK_HEALTH_TIME_INTERVAL = CHECK_HEALTH_TIME_INTERVAL

module.exports = window.$n.SyncProcessManager
<commit_msg>[client-sync] Correctly remove sync worker reference when destroying it<commit_after>const _ = require('underscore')
const {ipcRenderer} = require('electron')
const {Actions, OnlineStatusStore, IdentityStore} = require('nylas-exports')
const SyncWorker = require('./sync-worker');
const LocalSyncDeltaEmitter = require('./local-sync-delta-emitter').default
const LocalDatabaseConnector = require('../shared/local-database-connector')
const SyncActivity = require('../shared/sync-activity').default

const MAX_WORKER_SILENCE_MS = Math.max(
  SyncWorker.AC_SYNC_LOOP_INTERVAL_MS,
  SyncWorker.BATTERY_SYNC_LOOP_INTERVAL_MS,
  SyncWorker.MAX_SYNC_BACKOFF_MS,
)
const CHECK_HEALTH_TIME_INTERVAL = 1 * 60 * 1000

class SyncProcessManager {
  constructor() {
    this._exiting = false;
    this._resettingEmailCache = false
    this._identityId = IdentityStore.identityId();
    this._workersByAccountId = {};
    this._localSyncDeltaEmittersByAccountId = new Map()

    OnlineStatusStore.listen(this._onOnlineStatusChanged, this)
    IdentityStore.listen(this._onIdentityChanged, this)
    Actions.resetEmailCache.listen(this._resetEmailCache, this)
    Actions.debugSync.listen(this._onDebugSync, this)
    Actions.wakeLocalSyncWorkerForAccount.listen((accountId) =>
      this.wakeWorkerForAccount(accountId, {interrupt: true})
    );
    ipcRenderer.on('app-resumed-from-sleep', () => {
      this._wakeAllWorkers({reason: 'Computer resumed from sleep', interrupt: true})
    })

    this._checkHealthInterval = null;
  }

  _onOnlineStatusChanged() {
    if (OnlineStatusStore.isOnline()) {
      this._wakeAllWorkers({reason: 'Came back online', interrupt: true})
    }
  }

  _onIdentityChanged() {
    const newIdentityId = IdentityStore.identityId()
    if (newIdentityId !== this._identityId) {
      // The IdentityStore can trigger any number of times, but we only want to
      // start sync if we previously didn't have an identity available
      this._identityId = newIdentityId
      this.start()
    }
  }

  _onDebugSync() {
    const win = NylasEnv.getCurrentWindow()
    win.show()
    win.maximize()
    win.openDevTools()
  }

  _wakeAllWorkers({reason, interrupt} = {}) {
    Object.keys(this._workersByAccountId).forEach((id) => {
      this.wakeWorkerForAccount(id, {reason, interrupt})
    })
  }

  async _resetEmailCache() {
    if (this._resettingEmailCache) return;
    this._resettingEmailCache = true
    try {
      try {
        await Promise.all(
          this.workers().map(w => w.destroy())
        )
        .timeout(500, 'Timed out while trying to stop sync')
      } catch (err) {
        global.Logger.warn('SyncProcessManager._resetEmailCache: Error while stopping sync', err)
      }
      const accountIds = Object.keys(this._workersByAccountId)
      for (const accountId of accountIds) {
        await LocalDatabaseConnector.destroyAccountDatabase(accountId)
      }

      ipcRenderer.send('command', 'application:relaunch-to-initial-windows', {
        resetDatabase: true,
      })
    } catch (err) {
      global.Logger.error('Error resetting email cache', err)
    } finally {
      this._resettingEmailCache = false
    }
  }

  _checkHealthByAccountId = async (accountId) => {
    const {time, activity} = SyncActivity.getLastSyncActivityForAccount(accountId);
    if (time < Date.now() - this.MAX_WORKER_SILENCE_MS) {
      const duration = Date.now() - time;
      Actions.recordUserEvent('Stuck Sync Process', {
        accountId: accountId,
        lastActivityTime: time,
        lastActivity: activity,
        duration,
      })
      const {Account} = await LocalDatabaseConnector.forShared();
      const account = await Account.findById(accountId)
      const logger = global.Logger.forAccount(account)
      logger.warn(`SyncProcessManager: Detected stuck worker. Last recorded activity: ${activity} at ${new Date(time)}`)

      await this.removeWorkerForAccountId(accountId, {timeout: 5 * 1000})
      await this.addWorkerForAccount(account)
    }
  }

  _checkHealth = async () => {
    return Promise.all(Object.keys(this._workersByAccountId).map(this._checkHealthByAccountId))
  }

  /**
   * Useful for debugging.
   */
  async start() {
    if (!IdentityStore.identity()) {
      global.Logger.log(`SyncProcessManager: Can't start sync; no Nylas Identity present`)
      return
    }
    global.Logger.log(`SyncProcessManager: Starting sync`)

    const {Account} = await LocalDatabaseConnector.forShared();
    const accounts = await Account.findAll();
    await Promise.all(accounts.map(this.addWorkerForAccount));

    if (!this._checkHealthInterval) {
      this._checkHealthInterval = setInterval(this._checkHealth, this.CHECK_HEALTH_TIME_INTERVAL)
    }
  }

  workers() {
    return _.values(this._workersByAccountId)
  }

  dbs() {
    return this.workers().map(w => w._db)
  }

  wakeWorkerForAccount(accountId, {reason = 'Waking sync', interrupt} = {}) {
    const worker = this._workersByAccountId[accountId]
    if (worker) {
      worker.syncNow({reason, interrupt});
    }
  }

  addWorkerForAccount = async (account) => {
    await LocalDatabaseConnector.ensureAccountDatabase(account.id);
    const logger = global.Logger.forAccount(account)

    try {
      if (this._workersByAccountId[account.id]) {
        logger.warn(`SyncProcessManager.addWorkerForAccount: Worker for account already exists - skipping`)
        return
      }
      const db = await LocalDatabaseConnector.forAccount(account.id);
      this._workersByAccountId[account.id] = new SyncWorker(account, db, this);

      const localSyncDeltaEmitter = new LocalSyncDeltaEmitter(account, db)
      await localSyncDeltaEmitter.activate()
      this._localSyncDeltaEmittersByAccountId.set(account.id, localSyncDeltaEmitter)
      logger.log(`SyncProcessManager: Claiming Account Succeeded`)
    } catch (err) {
      logger.error(`SyncProcessManager: Claiming Account Failed`, err)
    }
  }

  async removeWorkerForAccountId(accountId, {timeout} = {}) {
    if (this._workersByAccountId[accountId]) {
      await this._workersByAccountId[accountId].destroy({timeout})
      delete this._workersByAccountId[accountId];
    }

    if (this._localSyncDeltaEmittersByAccountId.has(accountId)) {
      this._localSyncDeltaEmittersByAccountId.get(accountId).deactivate();
      this._localSyncDeltaEmittersByAccountId.delete(accountId)
    }
  }
}

window.$n.SyncProcessManager = new SyncProcessManager();
window.$n.SyncProcessManager.MAX_WORKER_SILENCE_MS = MAX_WORKER_SILENCE_MS
window.$n.SyncProcessManager.CHECK_HEALTH_TIME_INTERVAL = CHECK_HEALTH_TIME_INTERVAL

module.exports = window.$n.SyncProcessManager
<|endoftext|>


<commit_before>import React, { PureComponent, createElement } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { IconChevronUpSmallOutline, IconChevronDownSmallOutline } from '@teamleader/ui-icons';
import Counter from '../counter';
import theme from './theme.css';

export default class Input extends PureComponent {
  static propTypes = {
    bold: PropTypes.bool,
    className: PropTypes.string,
    counter: PropTypes.number,
    disabled: PropTypes.bool,
    icon: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
    iconPlacement: PropTypes.oneOf(['left', 'right']),
    id: PropTypes.string,
    onBlur: PropTypes.func,
    onChange: PropTypes.func,
    onFocus: PropTypes.func,
    placeholder: PropTypes.string,
    readOnly: PropTypes.bool,
    size: PropTypes.oneOf(['small', 'medium', 'large']),
    step: PropTypes.number,
    type: PropTypes.string,
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  };

  static defaultProps = {
    iconPlacement: 'left',
    disabled: false,
    placeholder: '',
    readOnly: false,
    size: 'medium',
    step: 1,
  };

  constructor(props) {
    super(props);

    this.isNumberInput = Boolean(props.type === 'number');
    this.handleChange = this.onChange.bind(this);
    this.handleIncreaseValue = this.increaseValue.bind(this);
    this.handleDecreaseValue = this.decreaseValue.bind(this);

    if (this.isNumber) {
      this.state = {
        value: Number(props.value) || undefined,
      };
    } else {
      this.state = {
        value: props.value || '',
      };
    }
  }

  createEvent() {
    return new Event('input', { bubbles: true });
  }

  onChange(value) {
    if (value.target) {
      if (this.isNumberInput) {
        this.setState({ value: Number(value.target.value) });
      } else {
        this.setState({ value: value.target.value });
      }

      return this.props.onChange(this.state.value);
    }

    this.setState({ value });

    return this.props.onChange(value);
  }

  increaseValue() {
    if (this.state.value) {
      return this.onChange(this.state.value + this.props.step);
    }

    return this.onChange(this.props.step);
  }

  decreaseValue() {
    if (this.state.value) {
      return this.onChange(this.state.value - this.props.step);
    }

    return this.onChange(-this.props.step);
  }

  renderInput() {
    const { bold, disabled, id, onBlur, onFocus, placeholder, readOnly, type } = this.props;
    const classNames = cx(theme['input'], {
      [theme['is-disabled']]: disabled,
      [theme['is-read-only']]: readOnly,
      [theme['is-bold']]: bold,
    });
    const props = {
      className: classNames,
      disabled: disabled,
      id,
      onBlur: onBlur,
      onChange: this.handleChange,
      onFocus: onFocus,
      placeholder,
      readOnly,
      type,
      value: this.state.value,
    };

    return <input {...props} />;
  }

  renderCounter() {
    if (this.props.counter) {
      return <Counter className={theme.counter} count={this.props.counter} color="ruby" size="small" />;
    }
  }

  renderSpinnerControls() {
    if (this.isNumberInput) {
      return (

          <button className={theme['spinner-up']} onClick={this.handleIncreaseValue}>
            <IconChevronUpSmallOutline />
          </button>
          <button className={theme['spinner-down']} onClick={this.handleDecreaseValue}>
            <IconChevronDownSmallOutline />
          </button>

      );
    }
  }

  render() {
    const { className, counter, icon, iconPlacement, size } = this.props;
    const classNames = cx(theme.wrapper, theme[size], className, {
      [theme[`has-icon-${iconPlacement}`]]: icon,
      [theme['has-counter']]: counter,
    });

    return (

        {icon &&
          createElement(icon, {
            className: theme.icon,
          })}

        {this.renderInput()}
        {this.renderCounter()}
        {this.renderSpinnerControls()}

    );
  }
}
<commit_msg>Fix typo<commit_after>import React, { PureComponent, createElement } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { IconChevronUpSmallOutline, IconChevronDownSmallOutline } from '@teamleader/ui-icons';
import Counter from '../counter';
import theme from './theme.css';

export default class Input extends PureComponent {
  static propTypes = {
    bold: PropTypes.bool,
    className: PropTypes.string,
    counter: PropTypes.number,
    disabled: PropTypes.bool,
    icon: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
    iconPlacement: PropTypes.oneOf(['left', 'right']),
    id: PropTypes.string,
    onBlur: PropTypes.func,
    onChange: PropTypes.func,
    onFocus: PropTypes.func,
    placeholder: PropTypes.string,
    readOnly: PropTypes.bool,
    size: PropTypes.oneOf(['small', 'medium', 'large']),
    step: PropTypes.number,
    type: PropTypes.string,
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  };

  static defaultProps = {
    iconPlacement: 'left',
    disabled: false,
    placeholder: '',
    readOnly: false,
    size: 'medium',
    step: 1,
  };

  constructor(props) {
    super(props);

    this.isNumberInput = Boolean(props.type === 'number');
    this.handleChange = this.onChange.bind(this);
    this.handleIncreaseValue = this.increaseValue.bind(this);
    this.handleDecreaseValue = this.decreaseValue.bind(this);

    if (this.isNumberInput) {
      this.state = {
        value: Number(props.value) || undefined,
      };
    } else {
      this.state = {
        value: props.value || '',
      };
    }
  }

  createEvent() {
    return new Event('input', { bubbles: true });
  }

  onChange(value) {
    if (value.target) {
      if (this.isNumberInput) {
        this.setState({ value: Number(value.target.value) });
      } else {
        this.setState({ value: value.target.value });
      }

      return this.props.onChange(this.state.value);
    }

    this.setState({ value });

    return this.props.onChange(value);
  }

  increaseValue() {
    if (this.state.value) {
      return this.onChange(this.state.value + this.props.step);
    }

    return this.onChange(this.props.step);
  }

  decreaseValue() {
    if (this.state.value) {
      return this.onChange(this.state.value - this.props.step);
    }

    return this.onChange(-this.props.step);
  }

  renderInput() {
    const { bold, disabled, id, onBlur, onFocus, placeholder, readOnly, type } = this.props;
    const classNames = cx(theme['input'], {
      [theme['is-disabled']]: disabled,
      [theme['is-read-only']]: readOnly,
      [theme['is-bold']]: bold,
    });
    const props = {
      className: classNames,
      disabled: disabled,
      id,
      onBlur: onBlur,
      onChange: this.handleChange,
      onFocus: onFocus,
      placeholder,
      readOnly,
      type,
      value: this.state.value,
    };

    return <input {...props} />;
  }

  renderCounter() {
    if (this.props.counter) {
      return <Counter className={theme.counter} count={this.props.counter} color="ruby" size="small" />;
    }
  }

  renderSpinnerControls() {
    if (this.isNumberInput) {
      return (

          <button className={theme['spinner-up']} onClick={this.handleIncreaseValue}>
            <IconChevronUpSmallOutline />
          </button>
          <button className={theme['spinner-down']} onClick={this.handleDecreaseValue}>
            <IconChevronDownSmallOutline />
          </button>

      );
    }
  }

  render() {
    const { className, counter, icon, iconPlacement, size } = this.props;
    const classNames = cx(theme.wrapper, theme[size], className, {
      [theme[`has-icon-${iconPlacement}`]]: icon,
      [theme['has-counter']]: counter,
    });

    return (

        {icon &&
          createElement(icon, {
            className: theme.icon,
          })}

        {this.renderInput()}
        {this.renderCounter()}
        {this.renderSpinnerControls()}

    );
  }
}
<|endoftext|>


<commit_before>/*
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
define(['angular'], function(angular) {
  /* Keep in sync with docs/markdown/configuration.md*/
  return angular.module('override', [])
    .constant('OVERRIDE', {
      'APP_OPTIONS': {
        // 'appMenuTemplateURL': 'portal/misc/partials/example-menu.html',
        // 'enablePushContentMenu': true,
      },
      'SERVICE_LOC': {
        // 'widgetApi': {
        //   'entry': 'staticFeeds/',
        // },
        // 'aboutURL': 'staticFeeds/about-frame.json',
        // 'aboutPageURL': 'staticFeeds/about-page.json'
        // 'messagesURL': 'staticFeeds/sample-messages.json',
        // 'bannersURL': 'staticFeeds/sample-banners.json'
      },
      'MESSAGES': {
        // 'notificationsPageURL': '/notifications',
      },
    });
});
<commit_msg>docs: alpha-sort commented out example SERVICE_LOC members<commit_after>/*
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
define(['angular'], function(angular) {
  /* Keep in sync with docs/markdown/configuration.md*/
  return angular.module('override', [])
    .constant('OVERRIDE', {
      'APP_OPTIONS': {
        // 'appMenuTemplateURL': 'portal/misc/partials/example-menu.html',
        // 'enablePushContentMenu': true,
      },
      'SERVICE_LOC': {
        // 'widgetApi': {
        //   'entry': 'staticFeeds/',
        // },
        // 'aboutURL': 'staticFeeds/about-frame.json',
        // 'aboutPageURL': 'staticFeeds/about-page.json'
        // 'bannersURL': 'staticFeeds/sample-banners.json',
        // 'messagesURL': 'staticFeeds/sample-messages.json',
      },
      'MESSAGES': {
        // 'notificationsPageURL': '/notifications',
      },
    });
});
<|endoftext|>