<commit_before>import moment from 'moment';
import React from 'react';
import MetricsStore from '../stores/MetricsStore';

const MAX_POINTS_PER_GRAPH = 300;
const RUNNING_GRAPH_SHOWED_DURATION = 10; // minutes
const MIN_GRAPHS_TO_BEGIN_COMPRESSION = 20;

class _DataStream {
    constructor(name, aggregated, metrics) {
        this.name = name;
        this.aggregated = aggregated;
        this.metrics = metrics;
        this.streams = [];
    }

    subscribeToEntireMetric(benchId, subsamplingInterval, continueStreamingAfterEnd) {
        this.streams = this.metrics.map((metric) => {
            return MetricsStore.subscribeToEntireMetric(benchId, metric, subsamplingInterval, continueStreamingAfterEnd);
        });
        this._createAggregatedMetric(benchId, undefined);
    }

    subscribeToMetricSubset(benchId, subsamplingInterval, beginTime, endTime) {
        this.streams = this.metrics.map((metric) => {
            return MetricsStore.subscribeToEntireMetric(benchId, metric, subsamplingInterval, beginTime, endTime);
        });
        this._createAggregatedMetric(benchId, undefined);
    }

    subscribeToMetricWithTimeWindow(benchId, timeInterval) {
        this.streams = this.metrics.map((metric) => {
            return MetricsStore.subscribeToMetricWithTimeWindow(benchId, metric, timeInterval);
        });
        this._createAggregatedMetric(benchId, timeInterval);
    }

    unsubscribeFromMetric() {
        this._destroyAggregatedMetric();
        this.streams.forEach((streamId) => {
            MetricsStore.unsubscribeFromMetric(streamId);
        });
    }

    getMetricMaxDate() {
        return MetricsStore.getMetricMaxDate(this.streams[this.streams.length - 1]);
    }

    getMetricData() {
        return MetricsStore.getMetricData(this.streams[this.streams.length - 1]);
    }

    getBatchCounter() {
        return MetricsStore.getBatchCounter(this.streams[this.streams.length - 1]);
    }

    _createAggregatedMetric(benchId, timeInterval) {
        if(this.aggregated) {
            const id = MetricsStore.createAggregatedStream(benchId, this.streams.slice(), timeInterval);
            this.streams.push(id);
        }
    }

    _destroyAggregatedMetric() {
        if(this.aggregated) {
            const id = this.streams.pop();
            MetricsStore.removeAggregatedStream(id);
        }
    }
};

class Graph extends React.Component {
    constructor(props) {
        super(props);

        this.streams = [];
        this.currentZoom = undefined;
        this.previouslyRunning = undefined;
        this.updatesCounter = 0;

        this._createStreams();

        this.state = this._resolveState();
        this._onChange = this._onChange.bind(this);
        this._updateGraph = this._updateGraph.bind(this);
    }

    componentDidMount() {
        MetricsStore.onChange(this._onChange);
        this.previouslyRunning = this.props.isRunning;

        this._createSubscriptions();
        this._renderGraph();
    }

    componentWillUnmount() {
        MetricsStore.off(this._onChange);

        this._destroySubscriptions();
    }

    shouldComponentUpdate(nextProps, nextState) {
        if(nextProps.targets[0] != this.props.targets[0]) {
            // The change of the first target will change the DOM Ids
            // of the MetricsGraphics.js targets. Its the only case
            // when rendering should be done.
            //
            // Happens in fullscreen mode.
            return true;
        } else {
            // Graph should probably be updated, but not the DOM.
            setTimeout(this._updateGraph, 1);
            return false;
        }
    }

    componentDidUpdate() {
        this._onChange();
        setTimeout(this._renderGraph.bind(this), 1 + Math.round(3000*Math.random()));
    }

    render() {
        return (

);
    }

    _graphDOMId() {
        return this.props.domPrefix + this.props.targets[0].replace(/\./g, "-") + "-graph";
    }

    _formatClockNumber(number) {
        let result = number.toString();
        if(number < 10) {
            result = "0" + result;
        }
        return result;
    }

    _formatDate(rawDate) {
        const absDate = (rawDate < 0)?-1*rawDate:rawDate;
        const negDate = rawDate < 0;
        let date = moment.duration(absDate, 'seconds');

        let result = `${this._formatClockNumber(date.minutes())}:${this._formatClockNumber(date.seconds())}`;
        if(date.hours() > 0) {
            result = `${this._formatClockNumber(date.hours())}:` + result;
        }
        if(date.days() > 0) {
            result = `${date.days()}d ` + result;
        }
        if(date.months() > 0) {
            result = `${date.months()}m ` + result;
        }
        if(date.years() > 0) {
            result = `${date.years()}y ` + result;
        }

        if(negDate) {
            return '-' + result;
        } else {
            return result;
        }
    }

    _formatRolloverTextFullscreen(data, i) {
        let rolloverTextContainer = d3.select('#' + this._graphDOMId() + ' svg .mg-active-datapoint');
        rolloverTextContainer.selectAll('*').remove();

        if(data.key) {
            const dateString = this._formatDate(data.values[0].date);
            rolloverTextContainer.append('tspan').text(dateString).classed('mg-area1-color', true);

            let lineCount = 1;
            let lineHeight = 1.1;
            data.values.forEach((value) => {
                let label = rolloverTextContainer.append('tspan')
                            .attr({ x: 0, y: (lineCount * lineHeight) + 'em' })
                            .text(`${this.streams[value.line_id - 1].name}: ${value.value}`)
                            .classed('mg-area' + value.line_id + '-color', true);

                ++lineCount;
            });
        } else {
            const dateString = this._formatDate(data.date);
            rolloverTextContainer.append('tspan').text(dateString).classed(`mg-area${data.line_id}-color`, true);

            let label = rolloverTextContainer.append('tspan')
                        .attr({ x: 0, y: 1.1 + 'em' })
                        .text(`${this.streams[data.line_id - 1].name}: ${data.value}`)
                        .classed(`mg-area${data.line_id}-color`, true);
        }
    }

    _formatRolloverTextNormal(data, i) {
        let rolloverTextContainer = d3.select('#' + this._graphDOMId() + ' svg .mg-active-datapoint');
        rolloverTextContainer.selectAll('*').remove();

        if(data.key) {
            const dateString = this._formatDate(data.values[0].date);
            rolloverTextContainer.append('tspan').text(dateString).classed('mg-area1-color', true);

            let lineCount = 1;
            let lineHeight = 1.1;
            data.values.forEach((value) => {
                let label = rolloverTextContainer.append('tspan')
                            .attr({ x: 0, y: (lineCount * lineHeight) + 'em' })
                            .text(`${this.streams[value.line_id - 1].name}: ${value.value}`)
                            .classed('mg-area' + value.line_id + '-color', true);

                ++lineCount;
            });
        } else {
            const dateString = this._formatDate(data.date);
            rolloverTextContainer.append('tspan').text(dateString).classed(`mg-area${data.line_id}-color`, true);

            let label = rolloverTextContainer.append('tspan')
                        .attr({ x: 0, y: 1.1 + 'em' })
                        .text(`${this.streams[data.line_id - 1].name}: ${data.value}`)
                        .classed(`mg-area${data.line_id}-color`, true);
        }
    }

    _formatRolloverText(data, i) {
        if(this.props.renderFullscreen) {
            this._formatRolloverTextFullscreen(data, i);
        } else {
            this._formatRolloverTextNormal(data, i);
        }
    }

    _performZoom(step) {
        this.currentZoom = step;
        this._destroySubscriptions();
        this._createStreams();
        this._createSubscriptions();
        this._resetState();
    }

    _calcDataMax() {
        return this.state.data.reduce((acc, stream) => {
            const stream_max = stream.reduce((acc, value) => {
                if(acc === undefined || value.max > acc) return value.max;
                else return acc;
            }, undefined);

            if(acc === undefined || stream_max > acc) return stream_max;
            else return acc;
        }, undefined);
    }

    _renderGraph() {
        let graph_options = {
            title: this.props.title,
            y_label: this.props.units,
            missing_text: "Loading...",

            buffer: 0,
            left: 65,
            right: 0,
            height: 400,

            area: false,
            brushing: false,
            brushing_history: false,
            interpolate: 'monotone',
            x_extended_ticks: true,
            y_extended_ticks: true,
            full_width: true,
            aggregate_rollover: false,
            transition_on_update: false
        };

        if(this.props.renderFullscreen) {
            graph_options.width = window.innerWidth - 100;
            graph_options.height = window.innerHeight - 200;
            graph_options.full_width = false;

            graph_options.brushing = true;
            graph_options.brushing_history = true;
            graph_options.after_brushing = this._performZoom.bind(this);
            graph_options.brushing_manual_redraw = true;
            graph_options.aggregate_rollover = this.streams.length > 1;
        }

        if(this.state.isLoaded) {
            let isEmpty = this.state.data.reduce((prev, dataset) => {
                if (dataset.length > 0) return false;
                else return prev;
            }, true);
            graph_options.data = isEmpty ? [[{date: 0, value: 0, min: 0, max: 0}]] : this.state.data;
            graph_options.legend = this.streams.map((stream) => { return stream.name; });

            graph_options.target = document.getElementById(this._graphDOMId());

            graph_options.show_confidence_band = ['min', 'max'];

            graph_options.xax_format = this._formatDate.bind(this);
            graph_options.mouseover = this._formatRolloverText.bind(this);
            graph_options.x_sort = false;

            graph_options.min_x = (this.props.isRunning && !this.props.renderFullscreen)?this.state.maxDate - RUNNING_GRAPH_SHOWED_DURATION*60:0;
            graph_options.max_x = this.state.maxDate;
            graph_options.min_y = 0;
            graph_options.max_y = this._calcDataMax();

            MG.data_graphic(graph_options);
        } else {
            graph_options.chart_type = 'missing-data';
            graph_options.legend = this.streams.map((stream) => { return stream.name; });
            graph_options.target = document.getElementById(this._graphDOMId());

            MG.data_graphic(graph_options);
        }
    }

    _updateGraph() {
        if (!this.state.isLoaded) {
            return;
        }

        if (this.previouslyRunning != this.props.isRunning) {
            this.previouslyRunning = this.props.isRunning;
            this._resetGraphs();
        } else {
            const newUpdatesCounter = this.state.dataBatchs.reduce((a, b) => { return a + b }, 0);
            let dataUpdated = newUpdatesCounter > this.updatesCounter;

            if(dataUpdated) {
                this._renderGraph();
                this.updatesCounter = newUpdatesCounter;
            }
        }
    }

    _createStreams() {
        if(this.props.renderFullscreen || !this.props.isRunning
                || this.props.targets.length < MIN_GRAPHS_TO_BEGIN_COMPRESSION) {
            this.streams = this.props.targets.map((metric) => {
                let m = [];
                m.push(metric);
                return new _DataStream(metric, false, m);
            });
        } else {
            this.streams = [];

            let metricsToAggregate = [];
            for(let i = 0; i < this.props.targets.length; i++) {
                if(this.props.targets[i].match("systemload.*mzb_worker") === null) {
                    let m = [];
                    m.push(this.props.targets[i]);
                    this.streams.push(new _DataStream(this.props.targets[i], false, m));
                } else {
                    metricsToAggregate.push(this.props.targets[i]);
                }
            }

            if(metricsToAggregate.length !== 0) {
                let tmp = metricsToAggregate[0].split(".");
                tmp.pop();
                const metricName = tmp.join(".") + ".mzb_worker.aggregated";
                this.streams.push(new _DataStream(metricName, true, metricsToAggregate));
            }
        }
    }

    _resetGraphs() {
        this._destroySubscriptions();
        this._createStreams();
        this._resetState();
        this._createSubscriptions();
    }

    _computeSubsamplingInterval() {
        if(this.currentZoom) {
            const duration = this.currentZoom.max_x - this.currentZoom.min_x;
            return Math.floor(duration/MAX_POINTS_PER_GRAPH);
        }

        if(this.props.benchStartTime) {
            const lastActiveTime = this.props.isRunning ? moment() : this.props.benchFinishTime;
            const benchDuration = lastActiveTime.diff(this.props.benchStartTime, 'seconds');

            return Math.floor(benchDuration/MAX_POINTS_PER_GRAPH);
        } else {
            return 0;
        }
    }

    _createSubscriptions() {
        this.streams.forEach((stream) => {
            this._subscribeToMetric(this.props.benchId, stream);
        });
    }

    _destroySubscriptions() {
        this.streams.forEach((stream) => {
            stream.unsubscribeFromMetric();
        });
    }

    _subscribeToMetric(benchId, stream) {
        if(this.props.renderFullscreen) {
            if(this.currentZoom) {
                stream.subscribeToMetricSubset(benchId, this._computeSubsamplingInterval(),
                    this.currentZoom.min_x, this.currentZoom.max_x);
            } else {
                stream.subscribeToEntireMetric(benchId, this._computeSubsamplingInterval(), false);
            }
        } else {
            if(this.props.isRunning) {
                stream.subscribeToMetricWithTimeWindow(benchId, RUNNING_GRAPH_SHOWED_DURATION*60);
            } else {
                stream.subscribeToEntireMetric(benchId, this._computeSubsamplingInterval(), false);
            }
        }
    }

    _resetState() {
        this.updatesCounter = 0;

        this.setState({
            maxDate: 0,
            data: this.streams.map((stream) => { return []; }),
            dataBatchs: this.streams.map((streams) => { return 0; }),
            isLoaded: false
        });
    }

    _resolveState() {
        const maxDate = this.streams.reduce((maxDate, stream) => {
            const metricMaxDate = stream.getMetricMaxDate();

            if(metricMaxDate) {
                if(metricMaxDate > maxDate) {
                    return metricMaxDate;
                } else {
                    return maxDate;
                }
            } else {
                return maxDate;
            }
        }, 0);

        const metricData = this.streams.map((stream) => {
            const data = stream.getMetricData();
            if(data) {
                return data;
            } else {
                return [];
            }
        });

        const metricBatchs = this.streams.map((stream) => {
            const batchCounter = stream.getBatchCounter();
            if(batchCounter) {
                return batchCounter;
            } else {
                return 0;
            }
        });

        const isLoaded = metricBatchs.reduce((prev, v) => {
            if (v <= 0) return false;
            else return prev;
        }, true);

        return {
            maxDate: maxDate,
            data: metricData,
            dataBatchs: metricBatchs,
            isLoaded: isLoaded
        };
    }

    _onChange() {
        this.setState(this._resolveState());
    }
};

Graph.propTypes = {
    benchId: React.PropTypes.number.isRequired,
    benchStartTime: React.PropTypes.object,
    benchFinishTime: React.PropTypes.object,

    targets: React.PropTypes.array.isRequired,
    isRunning: React.PropTypes.bool,
    title: React.PropTypes.string,
    units: React.PropTypes.string,

    domPrefix: React.PropTypes.string,
    renderFullscreen: React.PropTypes.bool
};

Graph.defaultProps = {
    benchStartTime: undefined,
    benchFinishTime: undefined,

    isRunning: false,
    title: "",
    units: "",

    domPrefix: "",
    renderFullscreen: false
};

export default Graph;
<commit_msg>Bugfix zoom on graphs<commit_after>import moment from 'moment';
import React from 'react';
import MetricsStore from '../stores/MetricsStore';

const MAX_POINTS_PER_GRAPH = 300;
const RUNNING_GRAPH_SHOWED_DURATION = 10; // minutes
const MIN_GRAPHS_TO_BEGIN_COMPRESSION = 20;

class _DataStream {
    constructor(name, aggregated, metrics) {
        this.name = name;
        this.aggregated = aggregated;
        this.metrics = metrics;
        this.streams = [];
    }

    subscribeToEntireMetric(benchId, subsamplingInterval, continueStreamingAfterEnd) {
        this.streams = this.metrics.map((metric) => {
            return MetricsStore.subscribeToEntireMetric(benchId, metric, subsamplingInterval, continueStreamingAfterEnd);
        });
        this._createAggregatedMetric(benchId, undefined);
    }

    subscribeToMetricSubset(benchId, subsamplingInterval, beginTime, endTime) {
        this.streams = this.metrics.map((metric) => {
            return MetricsStore.subscribeToMetricSubset(benchId, metric, subsamplingInterval, beginTime, endTime);
        });
        this._createAggregatedMetric(benchId, undefined);
    }

    subscribeToMetricWithTimeWindow(benchId, timeInterval) {
        this.streams = this.metrics.map((metric) => {
            return MetricsStore.subscribeToMetricWithTimeWindow(benchId, metric, timeInterval);
        });
        this._createAggregatedMetric(benchId, timeInterval);
    }

    unsubscribeFromMetric() {
        this._destroyAggregatedMetric();
        this.streams.forEach((streamId) => {
            MetricsStore.unsubscribeFromMetric(streamId);
        });
    }

    getMetricMaxDate() {
        return MetricsStore.getMetricMaxDate(this.streams[this.streams.length - 1]);
    }

    getMetricData() {
        return MetricsStore.getMetricData(this.streams[this.streams.length - 1]);
    }

    getBatchCounter() {
        return MetricsStore.getBatchCounter(this.streams[this.streams.length - 1]);
    }

    _createAggregatedMetric(benchId, timeInterval) {
        if(this.aggregated) {
            const id = MetricsStore.createAggregatedStream(benchId, this.streams.slice(), timeInterval);
            this.streams.push(id);
        }
    }

    _destroyAggregatedMetric() {
        if(this.aggregated) {
            const id = this.streams.pop();
            MetricsStore.removeAggregatedStream(id);
        }
    }
};

class Graph extends React.Component {
    constructor(props) {
        super(props);

        this.streams = [];
        this.currentZoom = undefined;
        this.previouslyRunning = undefined;
        this.updatesCounter = 0;

        this._createStreams();

        this.state = this._resolveState();
        this._onChange = this._onChange.bind(this);
        this._updateGraph = this._updateGraph.bind(this);
    }

    componentDidMount() {
        MetricsStore.onChange(this._onChange);
        this.previouslyRunning = this.props.isRunning;

        this._createSubscriptions();
        this._renderGraph();
    }

    componentWillUnmount() {
        MetricsStore.off(this._onChange);

        this._destroySubscriptions();
    }

    shouldComponentUpdate(nextProps, nextState) {
        if(nextProps.targets[0] != this.props.targets[0]) {
            // The change of the first target will change the DOM Ids
            // of the MetricsGraphics.js targets. Its the only case
            // when rendering should be done.
            //
            // Happens in fullscreen mode.
            return true;
        } else {
            // Graph should probably be updated, but not the DOM.
            setTimeout(this._updateGraph, 1);
            return false;
        }
    }

    componentDidUpdate() {
        this._onChange();
        setTimeout(this._renderGraph.bind(this), 1 + Math.round(3000*Math.random()));
    }

    render() {
        return (

);
    }

    _graphDOMId() {
        return this.props.domPrefix + this.props.targets[0].replace(/\./g, "-") + "-graph";
    }

    _formatClockNumber(number) {
        let result = number.toString();
        if(number < 10) {
            result = "0" + result;
        }
        return result;
    }

    _formatDate(rawDate) {
        const absDate = (rawDate < 0)?-1*rawDate:rawDate;
        const negDate = rawDate < 0;
        let date = moment.duration(absDate, 'seconds');

        let result = `${this._formatClockNumber(date.minutes())}:${this._formatClockNumber(date.seconds())}`;
        if(date.hours() > 0) {
            result = `${this._formatClockNumber(date.hours())}:` + result;
        }
        if(date.days() > 0) {
            result = `${date.days()}d ` + result;
        }
        if(date.months() > 0) {
            result = `${date.months()}m ` + result;
        }
        if(date.years() > 0) {
            result = `${date.years()}y ` + result;
        }

        if(negDate) {
            return '-' + result;
        } else {
            return result;
        }
    }

    _formatRolloverTextFullscreen(data, i) {
        let rolloverTextContainer = d3.select('#' + this._graphDOMId() + ' svg .mg-active-datapoint');
        rolloverTextContainer.selectAll('*').remove();

        if(data.key) {
            const dateString = this._formatDate(data.values[0].date);
            rolloverTextContainer.append('tspan').text(dateString).classed('mg-area1-color', true);

            let lineCount = 1;
            let lineHeight = 1.1;
            data.values.forEach((value) => {
                let label = rolloverTextContainer.append('tspan')
                            .attr({ x: 0, y: (lineCount * lineHeight) + 'em' })
                            .text(`${this.streams[value.line_id - 1].name}: ${value.value}`)
                            .classed('mg-area' + value.line_id + '-color', true);

                ++lineCount;
            });
        } else {
            const dateString = this._formatDate(data.date);
            rolloverTextContainer.append('tspan').text(dateString).classed(`mg-area${data.line_id}-color`, true);

            let label = rolloverTextContainer.append('tspan')
                        .attr({ x: 0, y: 1.1 + 'em' })
                        .text(`${this.streams[data.line_id - 1].name}: ${data.value}`)
                        .classed(`mg-area${data.line_id}-color`, true);
        }
    }

    _formatRolloverTextNormal(data, i) {
        let rolloverTextContainer = d3.select('#' + this._graphDOMId() + ' svg .mg-active-datapoint');
        rolloverTextContainer.selectAll('*').remove();

        if(data.key) {
            const dateString = this._formatDate(data.values[0].date);
            rolloverTextContainer.append('tspan').text(dateString).classed('mg-area1-color', true);

            let lineCount = 1;
            let lineHeight = 1.1;
            data.values.forEach((value) => {
                let label = rolloverTextContainer.append('tspan')
                            .attr({ x: 0, y: (lineCount * lineHeight) + 'em' })
                            .text(`${this.streams[value.line_id - 1].name}: ${value.value}`)
                            .classed('mg-area' + value.line_id + '-color', true);

                ++lineCount;
            });
        } else {
            const dateString = this._formatDate(data.date);
            rolloverTextContainer.append('tspan').text(dateString).classed(`mg-area${data.line_id}-color`, true);

            let label = rolloverTextContainer.append('tspan')
                        .attr({ x: 0, y: 1.1 + 'em' })
                        .text(`${this.streams[data.line_id - 1].name}: ${data.value}`)
                        .classed(`mg-area${data.line_id}-color`, true);
        }
    }

    _formatRolloverText(data, i) {
        if(this.props.renderFullscreen) {
            this._formatRolloverTextFullscreen(data, i);
        } else {
            this._formatRolloverTextNormal(data, i);
        }
    }

    _performZoom(step) {
        this.currentZoom = step;
        this._destroySubscriptions();
        this._createStreams();
        this._resetState();
        this._createSubscriptions();
    }

    _calcDataMax() {
        return this.state.data.reduce((acc, stream) => {
            const stream_max = stream.reduce((acc, value) => {
                if(acc === undefined || value.max > acc) return value.max;
                else return acc;
            }, undefined);

            if(acc === undefined || stream_max > acc) return stream_max;
            else return acc;
        }, undefined);
    }

    _renderGraph() {
        let graph_options = {
            title: this.props.title,
            y_label: this.props.units,
            missing_text: "Loading...",

            buffer: 0,
            left: 65,
            right: 0,
            height: 400,

            area: false,
            brushing: false,
            brushing_history: false,
            interpolate: 'monotone',
            x_extended_ticks: true,
            y_extended_ticks: true,
            full_width: true,
            aggregate_rollover: false,
            transition_on_update: false
        };

        if(this.props.renderFullscreen) {
            graph_options.width = window.innerWidth - 100;
            graph_options.height = window.innerHeight - 200;
            graph_options.full_width = false;

            graph_options.brushing = true;
            graph_options.brushing_history = true;
            graph_options.after_brushing = this._performZoom.bind(this);
            graph_options.brushing_manual_redraw = true;
            graph_options.aggregate_rollover = this.streams.length > 1;
        }

        if(this.state.isLoaded) {
            let isEmpty = this.state.data.reduce((prev, dataset) => {
                if (dataset.length > 0) return false;
                else return prev;
            }, true);
            graph_options.data = isEmpty ? [[{date: 0, value: 0, min: 0, max: 0}]] : this.state.data;
            graph_options.legend = this.streams.map((stream) => { return stream.name; });

            graph_options.target = document.getElementById(this._graphDOMId());

            graph_options.show_confidence_band = ['min', 'max'];

            graph_options.xax_format = this._formatDate.bind(this);
            graph_options.mouseover = this._formatRolloverText.bind(this);
            graph_options.x_sort = false;

            graph_options.min_x = (this.props.isRunning && !this.props.renderFullscreen)?this.state.maxDate - RUNNING_GRAPH_SHOWED_DURATION*60:0;
            graph_options.max_x = this.state.maxDate;
            graph_options.min_y = 0;
            graph_options.max_y = this._calcDataMax();

            MG.data_graphic(graph_options);
        } else {
            graph_options.chart_type = 'missing-data';
            graph_options.legend = this.streams.map((stream) => { return stream.name; });
            graph_options.target = document.getElementById(this._graphDOMId());

            MG.data_graphic(graph_options);
        }
    }

    _updateGraph() {
        if (!this.state.isLoaded) {
            return;
        }

        if (this.previouslyRunning != this.props.isRunning) {
            this.previouslyRunning = this.props.isRunning;
            this._resetGraphs();
        } else {
            const newUpdatesCounter = this.state.dataBatchs.reduce((a, b) => { return a + b }, 0);
            let dataUpdated = newUpdatesCounter > this.updatesCounter;

            if(dataUpdated) {
                this._renderGraph();
                this.updatesCounter = newUpdatesCounter;
            }
        }
    }

    _createStreams() {
        if(this.props.renderFullscreen || !this.props.isRunning
                || this.props.targets.length < MIN_GRAPHS_TO_BEGIN_COMPRESSION) {
            this.streams = this.props.targets.map((metric) => {
                let m = [];
                m.push(metric);
                return new _DataStream(metric, false, m);
            });
        } else {
            this.streams = [];

            let metricsToAggregate = [];
            for(let i = 0; i < this.props.targets.length; i++) {
                if(this.props.targets[i].match("systemload.*mzb_worker") === null) {
                    let m = [];
                    m.push(this.props.targets[i]);
                    this.streams.push(new _DataStream(this.props.targets[i], false, m));
                } else {
                    metricsToAggregate.push(this.props.targets[i]);
                }
            }

            if(metricsToAggregate.length !== 0) {
                let tmp = metricsToAggregate[0].split(".");
                tmp.pop();
                const metricName = tmp.join(".") + ".mzb_worker.aggregated";
                this.streams.push(new _DataStream(metricName, true, metricsToAggregate));
            }
        }
    }

    _resetGraphs() {
        this._destroySubscriptions();
        this._createStreams();
        this._resetState();
        this._createSubscriptions();
    }

    _computeSubsamplingInterval() {
        if(this.currentZoom) {
            const duration = this.currentZoom.max_x - this.currentZoom.min_x;
            return Math.floor(duration/MAX_POINTS_PER_GRAPH);
        }

        if(this.props.benchStartTime) {
            const lastActiveTime = this.props.isRunning ? moment() : this.props.benchFinishTime;
            const benchDuration = lastActiveTime.diff(this.props.benchStartTime, 'seconds');

            return Math.floor(benchDuration/MAX_POINTS_PER_GRAPH);
        } else {
            return 0;
        }
    }

    _createSubscriptions() {
        this.streams.forEach((stream) => {
            this._subscribeToMetric(this.props.benchId, stream);
        });
    }

    _destroySubscriptions() {
        this.streams.forEach((stream) => {
            stream.unsubscribeFromMetric();
        });
    }

    _subscribeToMetric(benchId, stream) {
        if(this.props.renderFullscreen) {
            if(this.currentZoom) {
                stream.subscribeToMetricSubset(benchId, this._computeSubsamplingInterval(),
                    this.currentZoom.min_x, this.currentZoom.max_x);
            } else {
                stream.subscribeToEntireMetric(benchId, this._computeSubsamplingInterval(), false);
            }
        } else {
            if(this.props.isRunning) {
                stream.subscribeToMetricWithTimeWindow(benchId, RUNNING_GRAPH_SHOWED_DURATION*60);
            } else {
                stream.subscribeToEntireMetric(benchId, this._computeSubsamplingInterval(), false);
            }
        }
    }

    _resetState() {
        this.updatesCounter = 0;

        this.setState({
            maxDate: 0,
            data: this.streams.map((stream) => { return []; }),
            dataBatchs: this.streams.map((streams) => { return 0; }),
            isLoaded: false
        });
    }

    _resolveState() {
        const maxDate = this.streams.reduce((maxDate, stream) => {
            const metricMaxDate = stream.getMetricMaxDate();

            if(metricMaxDate) {
                if(metricMaxDate > maxDate) {
                    return metricMaxDate;
                } else {
                    return maxDate;
                }
            } else {
                return maxDate;
            }
        }, 0);

        const metricData = this.streams.map((stream) => {
            const data = stream.getMetricData();
            if(data) {
                return data;
            } else {
                return [];
            }
        });

        const metricBatchs = this.streams.map((stream) => {
            const batchCounter = stream.getBatchCounter();
            if(batchCounter) {
                return batchCounter;
            } else {
                return 0;
            }
        });

        const isLoaded = metricBatchs.reduce((prev, v) => {
            if (v <= 0) return false;
            else return prev;
        }, true);

        return {
            maxDate: maxDate,
            data: metricData,
            dataBatchs: metricBatchs,
            isLoaded: isLoaded
        };
    }

    _onChange() {
        this.setState(this._resolveState());
    }
};

Graph.propTypes = {
    benchId: React.PropTypes.number.isRequired,
    benchStartTime: React.PropTypes.object,
    benchFinishTime: React.PropTypes.object,

    targets: React.PropTypes.array.isRequired,
    isRunning: React.PropTypes.bool,
    title: React.PropTypes.string,
    units: React.PropTypes.string,

    domPrefix: React.PropTypes.string,
    renderFullscreen: React.PropTypes.bool
};

Graph.defaultProps = {
    benchStartTime: undefined,
    benchFinishTime: undefined,

    isRunning: false,
    title: "",
    units: "",

    domPrefix: "",
    renderFullscreen: false
};

export default Graph;
<|endoftext|>


<commit_before>const _ = require('underscore');

const DrawCard = require('../../../drawcard.js');

class JoryCassel extends DrawCard {
    constructor(owner, cardData) {
        super(owner, cardData);

        this.registerEvents(['onKillingCharacter']);
    }

    onKillingCharacter(event, player, card, allowSave) {
        if(!allowSave || player !== this.controller || this.isBlank()) {
            return;
        }

        this.game.promptWithMenu(player, this, {
            activePrompt: {
                menuTitle: 'Use ' + this.name + ' to save ' + card.name + '?',
                buttons: [
                    { text: 'Yes', method: 'save' },
                    { text: 'No', method: 'cancel' }
                ]
            },
            waitingPromptTitle: 'Waiting for opponent to use save effects'
        });

        this.toKill = card;

        event.cancel = true;
    }

    save(player) {
        var message = '{0} uses {1} to save {2}';

        this.controller.sacrificeCard(this);

        if(_.any(this.game.getPlayers(), player => {
            return player.activePlot.hasTrait('Winter');
        })) {
            this.toKill.modifyPower(1);

            message += ' and make it gain 1 power';
        }

        this.game.addMessage(message, player, this, this.toKill);

        return true;
    }

    cancel(player) {
        player.killCharacter(this.toKill, false);

        this.game.addMessage('{0} declines to trigger {1}', player, this);

        return true;
    }
}

JoryCassel.code = '03008';

module.exports = JoryCassel;
<commit_msg>only trigger jory cassel for unique stark characters.  Fixes #240<commit_after>const _ = require('underscore');

const DrawCard = require('../../../drawcard.js');

class JoryCassel extends DrawCard {
    constructor(owner, cardData) {
        super(owner, cardData);

        this.registerEvents(['onKillingCharacter']);
    }

    onKillingCharacter(event, player, card, allowSave) {
        if(!allowSave || player !== this.controller || this.isBlank()) {
            return;
        }

        if(!card.isUnique() || card.getFaction() !== 'stark') {
            return;
        }

        this.game.promptWithMenu(player, this, {
            activePrompt: {
                menuTitle: 'Use ' + this.name + ' to save ' + card.name + '?',
                buttons: [
                    { text: 'Yes', method: 'save' },
                    { text: 'No', method: 'cancel' }
                ]
            },
            waitingPromptTitle: 'Waiting for opponent to use save effects'
        });

        this.toKill = card;

        event.cancel = true;
    }

    save(player) {
        var message = '{0} uses {1} to save {2}';

        this.controller.sacrificeCard(this);

        if(_.any(this.game.getPlayers(), player => {
            return player.activePlot.hasTrait('Winter');
        })) {
            this.toKill.modifyPower(1);

            message += ' and make it gain 1 power';
        }

        this.game.addMessage(message, player, this, this.toKill);

        return true;
    }

    cancel(player) {
        player.killCharacter(this.toKill, false);

        this.game.addMessage('{0} declines to trigger {1}', player, this);

        return true;
    }
}

JoryCassel.code = '03008';

module.exports = JoryCassel;
<|endoftext|>


<commit_before>const St = imports.gi.St;
const GObject = imports.gi.GObject;
const ModalDialog = imports.ui.modalDialog;
const CheckBox = imports.ui.checkBox;
const Clutter = imports.gi.Clutter;

const Gettext = imports.gettext.domain("clipboard-indicator");
const _ = Gettext.gettext;

function openConfirmDialog(title, message, sub_message, ok_label, cancel_label, callback) {
    new ConfirmDialog(title, message + "\n" + sub_message, ok_label, cancel_label, callback).open();
}

const ConfirmDialog = GObject.registerClass(
  class ConfirmDialog extends ModalDialog.ModalDialog {

    _init(title, desc, ok_label, cancel_label, callback) {
      super._init();

      let main_box = new St.BoxLayout({
        vertical: false,
        style_class: 'gt-modal-dialog',
      });
      this.contentLayout.add(main_box, { x_fill: true, y_fill: true });

      let message_box = new St.BoxLayout({
        vertical: true
      });
      main_box.add(message_box, { y_align: St.Align.START });

      let subject_label = new St.Label({
        style: `font-weight: 700`,
        text: title
      });

      message_box.add(subject_label, { y_fill: true, y_align: St.Align.START });

      let desc_label = new St.Label({
        style: 'padding-top: 12px; ',
        text: desc
      });

      message_box.add(desc_label, { y_fill: true, y_align: St.Align.START });

      this.setButtons([
        {
          label: cancel_label,
          action: () => {
            this.close();
          },
          key: Clutter.Escape
        },
        {
          label: ok_label,
          action: () => {
            this.close();
            callback();
          }
        }
      ]);
    }
  }
);
<commit_msg>Update confirmDialog.js<commit_after>const St = imports.gi.St;
const GObject = imports.gi.GObject;
const ModalDialog = imports.ui.modalDialog;
const CheckBox = imports.ui.checkBox;
const Clutter = imports.gi.Clutter;

function openConfirmDialog(title, message, sub_message, ok_label, cancel_label, callback) {
    new ConfirmDialog(title, message + "\n" + sub_message, ok_label, cancel_label, callback).open();
}

const ConfirmDialog = GObject.registerClass(
  class ConfirmDialog extends ModalDialog.ModalDialog {

    _init(title, desc, ok_label, cancel_label, callback) {
      super._init();

      let main_box = new St.BoxLayout({
        vertical: false,
        style_class: 'gt-modal-dialog',
      });
      this.contentLayout.add(main_box, { x_fill: true, y_fill: true });

      let message_box = new St.BoxLayout({
        vertical: true
      });
      main_box.add(message_box, { y_align: St.Align.START });

      let subject_label = new St.Label({
        style: `font-weight: 700`,
        text: title
      });

      message_box.add(subject_label, { y_fill: true, y_align: St.Align.START });

      let desc_label = new St.Label({
        style: 'padding-top: 12px; ',
        text: desc
      });

      message_box.add(desc_label, { y_fill: true, y_align: St.Align.START });

      this.setButtons([
        {
          label: cancel_label,
          action: () => {
            this.close();
          },
          key: Clutter.Escape
        },
        {
          label: ok_label,
          action: () => {
            this.close();
            callback();
          }
        }
      ]);
    }
  }
);
<|endoftext|>


<commit_before>var _ = require('underscore');
var mongojs = require('mongojs');
var csv = require('./lib/csv');
var format = require('./lib/format');
var async = require('async');

var dbUrl = process.env.MONGOLAB_URI || 'mongodb://localhost/openerz',
    coll = ['calendar', 'station']
    db = mongojs(dbUrl, coll),

db.on('error',function(err) {
    console.log('database error', err);
});

db.on('ready', function() {
    console.log('db connected', dbUrl);
    // remove previous entries
    db.station.remove();
    db.calendar.remove();

    async.series([
        function(callback){
            csv.convertToJson('./csv/Entsorgung_Sammelstellen_2014.csv', format.stationEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    db.station.insert(obj);
                });
                console.log("Finished inserting stations!");
                callback(null, "stations");
            });
        },
        function(callback){
            csv.convertToJson('./csv/Entsorgungskalender_Bioabfall_2014.csv', format.calendarEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    obj['type'] = 'organic';
                    db.calendar.insert(obj);
                });
                console.log("Finished inserting calendar for organic waste!");
                callback(null, "organic waste");
            });
        },
        function(callback){
            csv.convertToJson('./csv/Entsorgungskalender_CargoTram_2014.csv', format.calendarEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    obj['type'] = 'cargotram';
                    db.calendar.insert(obj);
                });
                console.log("Finished inserting calendar of cargo tram!");
                callback(null, "cargo tram");
            });
        },
        function(callback){
            csv.convertToJson('./csv/Entsorgungskalender_eTram_2014.csv', format.calendarEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    obj['type'] = 'etram';
                    db.calendar.insert(obj);
                });
                console.log("Finished inserting calendar of e-tram!");
                callback(null, "e-tram");
            });
        },
        function(callback){
            csv.convertToJson('./csv/Entsorgungskalender_Karton_2014.csv', format.calendarEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    obj['type'] = 'cardboard';
                    db.calendar.insert(obj);
                });
                console.log("Finished inserting calendar for cardboard waste!");
                callback(null, "cardboard");
            });
        },
        function(callback){
            csv.convertToJson('./csv/Entsorgungskalender_Papier_2014.csv', format.calendarEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    obj['type'] = 'paper';
                    db.calendar.insert(obj);
                });
                console.log("Finished inserting calendar for paper waste!");
                callback(null, "paper");
            });
        },
        function(callback){
            csv.convertToJson('./csv/Entsorgungskalender_Kehricht_2014.csv', format.calendarEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    obj['type'] = 'waste';
                    db.calendar.insert(obj);
                });
                console.log("Finished inserting calendar for general waste!");
                callback(null, "waste");
            });
        },
        function(callback){
            csv.convertToJson('./csv/Entsorgungskalender_Sonderabfall_2014.csv', format.calendarEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    obj['type'] = 'special';
                    db.calendar.insert(obj);
                });
                console.log("Finished inserting calendar for special waste!");
                callback(null, "special");
            });
        },
        function(callback){
            csv.convertToJson('./csv/Entsorgungskalender_Textilien_2014.csv', format.calendarEntry, function (objArr) {
                console.log('CSV converted, got ' + objArr.length + ' objects');

                _.each(objArr, function (obj) {
                    obj['type'] = 'textile';
                    db.calendar.insert(obj);
                });
                console.log("Finished inserting calendar for textile waste!");
                callback(null, "cargo tram");
            });
        },
    ],
    function(err, results){
        if (err) {
            console.log("Error occured:", err);
            process.exit(code=1);
        }
        console.log("Finished importing", results);
        process.exit(code=0);
    });
});

// make sure the database in opened and the 'ready' event is fired
db.open();
<commit_msg>Refactoring of loadData to reduce duplicate code<commit_after>var _ = require('underscore');
var mongojs = require('mongojs');
var csv = require('./lib/csv');
var format = require('./lib/format');
var async = require('async');

var dbUrl = process.env.MONGOLAB_URI || 'mongodb://localhost/openerz',
    coll = ['calendar', 'station']
    db = mongojs(dbUrl, coll),

db.on('error',function(err) {
    console.log('database error', err);
});

db.on('ready', function() {
    console.log('db connected', dbUrl);
    // remove previous entries
    db.station.remove();
    db.calendar.remove();

    var importCsv = function(path, format, collection, type, callback) {
        csv.convertToJson(path, format, function (objArr) {
            console.log('CSV converted, got ' + objArr.length + ' objects');

            _.each(objArr, function (obj) {
                if (type !== 'stations') {
                    obj['type'] = type;
                }
                collection.insert(obj);
            });
            console.log("Finished inserting", type);
            callback();
        });
    }

    var csvs = [
        {
            'path': './csv/Entsorgung_Sammelstellen_2014.csv',
            'format': format.stationEntry,
            'collection': db.station,
            'type': 'stations'
        },
        {
            'path': './csv/Entsorgungskalender_Bioabfall_2014.csv',
            'format': format.calendarEntry,
            'collection': db.calendar,
            'type': 'organic'
        },
        {
            'path': './csv/Entsorgungskalender_CargoTram_2014.csv',
            'format': format.calendarEntry,
            'collection': db.calendar,
            'type': 'cargotram'
        },
        {
            'path': './csv/Entsorgungskalender_eTram_2014.csv',
            'format': format.calendarEntry,
            'collection': db.calendar,
            'type': 'etram'
        },
        {
            'path': './csv/Entsorgungskalender_Karton_2014.csv',
            'format': format.calendarEntry,
            'collection': db.calendar,
            'type': 'cardboard'
        },
        {
            'path': './csv/Entsorgungskalender_Papier_2014.csv',
            'format': format.calendarEntry,
            'collection': db.calendar,
            'type': 'paper'
        },
        {
            'path': './csv/Entsorgungskalender_Kehricht_2014.csv',
            'format': format.calendarEntry,
            'collection': db.calendar,
            'type': 'waste'
        },
        {
            'path': './csv/Entsorgungskalender_Sonderabfall_2014.csv',
            'format': format.calendarEntry,
            'collection': db.calendar,
            'type': 'special'
        },
        {
            'path': './csv/Entsorgungskalender_Textilien_2014.csv',
            'format': format.calendarEntry,
            'collection': db.calendar,
            'type': 'textile'
        },
    ];

    async.eachSeries(
        csvs,
        function (csv, callback) {
            try {
                importCsv(csv.path, csv.format, csv.collection, csv.type, callback);
            } catch (e) {
                callback(e);
            }
        },
        function(err){
            if (err) {
              console.log('Import failed:', err);
                process.exit(code=1);
            } else {
                console.log("Import finished");
                process.exit(code=0);
            }
        }
    );
});

// make sure the database in opened and the 'ready' event is fired
db.open();
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Actor LLC. <https://actor.im>
 */

import { assign, forEach } from 'lodash';
import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import ReactMixin from 'react-mixin';
import { IntlMixin } from 'react-intl';
import { Container } from 'flux/utils';

import ActorClient from '../../utils/ActorClient';
import Inputs from '../../utils/Inputs';

import { KeyCodes } from '../../constants/ActorAppConstants';

import MessageActionCreators from '../../actions/MessageActionCreators';
import ComposeActionCreators from '../../actions/ComposeActionCreators';
import AttachmentsActionCreators from '../../actions/AttachmentsActionCreators';
import EmojiActionCreators from '../../actions/EmojiActionCreators';

import GroupStore from '../../stores/GroupStore';
import PreferencesStore from '../../stores/PreferencesStore';
import ComposeStore from '../../stores/ComposeStore';
import AttachmentStore from '../../stores/AttachmentStore';
import DialogStore from '../../stores/DialogStore';

import AvatarItem from '../common/AvatarItem.react';
import MentionDropdown from '../common/MentionDropdown.react';
import EmojiDropdown from '../common/EmojiDropdown.react';
import VoiceRecorder from '../common/VoiceRecorder.react';
import DropZone from '../common/DropZone.react';
import SendAttachment from '../modals/SendAttachment';

class ComposeSection extends Component {
  static getStores = () => [DialogStore, GroupStore, PreferencesStore, AttachmentStore, ComposeStore];

  static calculateState(prevState) {
    return {
      peer: DialogStore.getCurrentPeer(),
      text: ComposeStore.getText(),
      profile: ActorClient.getUser(ActorClient.getUid()),
      sendByEnter: PreferencesStore.isSendByEnterEnabled(),
      mentions: ComposeStore.getMentions(),
      isSendAttachmentOpen: AttachmentStore.isOpen(),
      isMarkdownHintShow: prevState ? prevState.isMarkdownHintShow || false : false,
      isFocusDisabled: ComposeStore.isFocusDisabled()
    };
  }

  static contextTypes = {
    isExperimental: PropTypes.bool
  };

  constructor(props) {
    super(props);

    window.addEventListener('focus', this.setFocus);
    document.addEventListener('keydown', this.handleKeyDown, false);
  }

  componentWillUnmount() {
    window.removeEventListener('focus', this.setFocus);
    document.removeEventListener('keydown', this.handleKeyDown, false);
  }

  componentDidMount() {
    this.setFocus();
  }

  componentDidUpdate() {
    this.setFocus();
  }

  handleKeyDown = (event) => {
    if (!event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
      this.setFocus();
    }
  };

  onMessageChange = event => {
    const text = event.target.value;
    const { peer } = this.state;

    if (text.length >= 3) {
      this.setState({isMarkdownHintShow: true})
    } else {
      this.setState({isMarkdownHintShow: false})
    }

    ComposeActionCreators.onTyping(peer, text, this.getCaretPosition());
  };

  onKeyDown = event => {
    const { mentions, sendByEnter } = this.state;

    const send = () => {
      event.preventDefault();
      this.sendTextMessage();
      this.setState({isMarkdownHintShow: false})
    };

    if (mentions === null) {
      if (sendByEnter === true) {
        if (event.keyCode === KeyCodes.ENTER && !event.shiftKey) {
          send();
        }
      } else {
        if (event.keyCode === KeyCodes.ENTER && event.metaKey) {
          send();
        }
      }
    }
  };

  sendTextMessage = () => {
    const { peer, text } = this.state;

    if (text.trim().length !== 0) {
      MessageActionCreators.sendTextMessage(peer, text);
    }
    ComposeActionCreators.cleanText();
  };

  resetAttachmentForm = () => {
    const form = React.findDOMNode(this.refs.attachmentForm);
    form.reset();
  };

  onPaste = event => {
    let preventDefault = false;

    forEach(event.clipboardData.items, (item) => {
      if (item.type.indexOf('image') !== -1) {
        preventDefault = true;
        MessageActionCreators.sendClipboardPhotoMessage(this.props.peer, item.getAsFile());
      }
    }, this);

    if (preventDefault) {
      event.preventDefault();
    }
  };

  onMentionSelect = (mention) => {
    const { peer, text } = this.state;

    ComposeActionCreators.insertMention(peer, text, this.getCaretPosition(), mention);
    React.findDOMNode(this.refs.area).focus();
  };

  onMentionClose = () => {
    ComposeActionCreators.closeMention();
  };

  getCaretPosition = () => {
    const composeArea = React.findDOMNode(this.refs.area);
    const selection = Inputs.getInputSelection(composeArea);
    return selection.start;
  };

  handleEmojiSelect = (emoji) => {
    EmojiActionCreators.insertEmoji(this.state.text, this.getCaretPosition(), emoji);
    React.findDOMNode(this.refs.area).focus()
  };

  setFocus = () => {
    const { isFocusDisabled } = this.state;

    if (!isFocusDisabled) {
      React.findDOMNode(this.refs.area).focus();
    }

  };

  handleDrop = (files) => {
    let attachments = [];

    forEach(files, (file) => attachments.push(file));

    if (attachments.length > 0) {
      AttachmentsActionCreators.show(attachments);
    }
  };

  handleAttachmentClick = () => {
    const attachmentInputNode = React.findDOMNode(this.refs.attachment);
    attachmentInputNode.setAttribute('multiple', true);
    attachmentInputNode.click();
  };

  handleComposeAttachmentChange = () => {
    const attachmentInputNode = React.findDOMNode(this.refs.attachment);
    let attachments = [];

    forEach(attachmentInputNode.files, (file) => attachments.push(file));

    AttachmentsActionCreators.show(attachments);
    this.resetAttachmentForm();
  };

  sendVoiceRecord = (record) => {
    console.debug('sendVoiceRecord: ', record)
  };

  render() {
    const { text, profile, mentions, isMardownHintShow, isSendAttachmentOpen } = this.state;
    const { isExperimental } = this.context;
    const markdownHintClassName = classnames('compose__markdown-hint', {
      'compose__markdown-hint--active': isMardownHintShow
    });

    return (

        <MentionDropdown mentions={mentions}
                         onSelect={this.onMentionSelect}
                         onClose={this.onMentionClose}/>

        <EmojiDropdown onSelect={this.handleEmojiSelect}/>

        {
          isExperimental
            ? <VoiceRecorder onFinish={this.sendVoiceRecord}/>
            : null
        }

          *{this.getIntlMessage('compose.markdown.bold')}*

          _{this.getIntlMessage('compose.markdown.italic')}_

          ```{this.getIntlMessage('compose.markdown.preformatted')}```

        <AvatarItem className="my-avatar"
                    image={profile.avatar}
                    placeholder={profile.placeholder}
                    title={profile.name}/>

        <textarea className="compose__message"
                  onChange={this.onMessageChange}
                  onKeyDown={this.onKeyDown}
                  onPaste={this.onPaste}
                  value={text}
                  ref="area"/>

        <DropZone onDropComplete={this.handleDrop}>{this.getIntlMessage('compose.dropzone')}</DropZone>

          <button className="button attachment" onClick={this.handleAttachmentClick}>
            attachment {this.getIntlMessage('compose.attach')}
          </button>

          <button className="button button--lightblue" onClick={this.sendTextMessage}>
            {this.getIntlMessage('compose.send')}
          </button>

        <form className="compose__hidden" ref="attachmentForm">
          <input ref="attachment" onChange={this.handleComposeAttachmentChange} type="file"/>
        </form>

        {/* Attachment modal */}
        {isSendAttachmentOpen ? <SendAttachment/> : null}

    );
  }
}

ReactMixin.onClass(ComposeSection, IntlMixin);

export default Container.create(ComposeSection);
<commit_msg>fix(web): fix markdown hint  showing<commit_after>/*
 * Copyright (C) 2015 Actor LLC. <https://actor.im>
 */

import { assign, forEach } from 'lodash';
import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import ReactMixin from 'react-mixin';
import { IntlMixin } from 'react-intl';
import { Container } from 'flux/utils';

import ActorClient from '../../utils/ActorClient';
import Inputs from '../../utils/Inputs';

import { KeyCodes } from '../../constants/ActorAppConstants';

import MessageActionCreators from '../../actions/MessageActionCreators';
import ComposeActionCreators from '../../actions/ComposeActionCreators';
import AttachmentsActionCreators from '../../actions/AttachmentsActionCreators';
import EmojiActionCreators from '../../actions/EmojiActionCreators';

import GroupStore from '../../stores/GroupStore';
import PreferencesStore from '../../stores/PreferencesStore';
import ComposeStore from '../../stores/ComposeStore';
import AttachmentStore from '../../stores/AttachmentStore';
import DialogStore from '../../stores/DialogStore';

import AvatarItem from '../common/AvatarItem.react';
import MentionDropdown from '../common/MentionDropdown.react';
import EmojiDropdown from '../common/EmojiDropdown.react';
import VoiceRecorder from '../common/VoiceRecorder.react';
import DropZone from '../common/DropZone.react';
import SendAttachment from '../modals/SendAttachment';

class ComposeSection extends Component {
  static getStores = () => [DialogStore, GroupStore, PreferencesStore, AttachmentStore, ComposeStore];

  static calculateState(prevState) {
    return {
      peer: DialogStore.getCurrentPeer(),
      text: ComposeStore.getText(),
      profile: ActorClient.getUser(ActorClient.getUid()),
      sendByEnter: PreferencesStore.isSendByEnterEnabled(),
      mentions: ComposeStore.getMentions(),
      isSendAttachmentOpen: AttachmentStore.isOpen(),
      isMarkdownHintShow: prevState ? prevState.isMarkdownHintShow || false : false,
      isFocusDisabled: ComposeStore.isFocusDisabled()
    };
  }

  static contextTypes = {
    isExperimental: PropTypes.bool
  };

  constructor(props) {
    super(props);

    window.addEventListener('focus', this.setFocus);
    document.addEventListener('keydown', this.handleKeyDown, false);
  }

  componentWillUnmount() {
    window.removeEventListener('focus', this.setFocus);
    document.removeEventListener('keydown', this.handleKeyDown, false);
  }

  componentDidMount() {
    this.setFocus();
  }

  componentDidUpdate() {
    this.setFocus();
  }

  handleKeyDown = (event) => {
    if (!event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
      this.setFocus();
    }
  };

  onMessageChange = event => {
    const text = event.target.value;
    const { peer } = this.state;

    if (text.length >= 3) {
      this.setState({isMarkdownHintShow: true})
    } else {
      this.setState({isMarkdownHintShow: false})
    }

    ComposeActionCreators.onTyping(peer, text, this.getCaretPosition());
  };

  onKeyDown = event => {
    const { mentions, sendByEnter } = this.state;

    const send = () => {
      event.preventDefault();
      this.sendTextMessage();
      this.setState({isMarkdownHintShow: false})
    };

    if (mentions === null) {
      if (sendByEnter === true) {
        if (event.keyCode === KeyCodes.ENTER && !event.shiftKey) {
          send();
        }
      } else {
        if (event.keyCode === KeyCodes.ENTER && event.metaKey) {
          send();
        }
      }
    }
  };

  sendTextMessage = () => {
    const { peer, text } = this.state;

    if (text.trim().length !== 0) {
      MessageActionCreators.sendTextMessage(peer, text);
    }
    ComposeActionCreators.cleanText();
  };

  resetAttachmentForm = () => {
    const form = React.findDOMNode(this.refs.attachmentForm);
    form.reset();
  };

  onPaste = event => {
    let preventDefault = false;

    forEach(event.clipboardData.items, (item) => {
      if (item.type.indexOf('image') !== -1) {
        preventDefault = true;
        console.debug(item.getAsFile());
        MessageActionCreators.sendClipboardPhotoMessage(this.state.peer, item.getAsFile());
      }
    }, this);

    if (preventDefault) {
      event.preventDefault();
    }
  };

  onMentionSelect = (mention) => {
    const { peer, text } = this.state;

    ComposeActionCreators.insertMention(peer, text, this.getCaretPosition(), mention);
    React.findDOMNode(this.refs.area).focus();
  };

  onMentionClose = () => {
    ComposeActionCreators.closeMention();
  };

  getCaretPosition = () => {
    const composeArea = React.findDOMNode(this.refs.area);
    const selection = Inputs.getInputSelection(composeArea);
    return selection.start;
  };

  handleEmojiSelect = (emoji) => {
    EmojiActionCreators.insertEmoji(this.state.text, this.getCaretPosition(), emoji);
    React.findDOMNode(this.refs.area).focus();
  };

  setFocus = () => {
    const { isFocusDisabled } = this.state;

    if (!isFocusDisabled) {
      React.findDOMNode(this.refs.area).focus();
    }

  };

  handleDrop = (files) => {
    let attachments = [];

    forEach(files, (file) => attachments.push(file));

    if (attachments.length > 0) {
      AttachmentsActionCreators.show(attachments);
    }
  };

  handleAttachmentClick = () => {
    const attachmentInputNode = React.findDOMNode(this.refs.attachment);
    attachmentInputNode.setAttribute('multiple', true);
    attachmentInputNode.click();
  };

  handleComposeAttachmentChange = () => {
    const attachmentInputNode = React.findDOMNode(this.refs.attachment);
    let attachments = [];

    forEach(attachmentInputNode.files, (file) => attachments.push(file));

    AttachmentsActionCreators.show(attachments);
    this.resetAttachmentForm();
  };

  sendVoiceRecord = (record) => {
    console.debug('sendVoiceRecord: ', record)
  };

  render() {
    const { text, profile, mentions, isMarkdownHintShow, isSendAttachmentOpen } = this.state;
    const { isExperimental } = this.context;
    const markdownHintClassName = classnames('compose__markdown-hint', {
      'compose__markdown-hint--active': isMarkdownHintShow
    });

    return (

        <MentionDropdown mentions={mentions}
                         onSelect={this.onMentionSelect}
                         onClose={this.onMentionClose}/>

        <EmojiDropdown onSelect={this.handleEmojiSelect}/>

        {
          isExperimental
            ? <VoiceRecorder onFinish={this.sendVoiceRecord}/>
            : null
        }

          *{this.getIntlMessage('compose.markdown.bold')}*

          _{this.getIntlMessage('compose.markdown.italic')}_

          ```{this.getIntlMessage('compose.markdown.preformatted')}```

        <AvatarItem className="my-avatar"
                    image={profile.avatar}
                    placeholder={profile.placeholder}
                    title={profile.name}/>

        <textarea className="compose__message"
                  onChange={this.onMessageChange}
                  onKeyDown={this.onKeyDown}
                  onPaste={this.onPaste}
                  value={text}
                  ref="area"/>

        <DropZone onDropComplete={this.handleDrop}>{this.getIntlMessage('compose.dropzone')}</DropZone>

          <button className="button attachment" onClick={this.handleAttachmentClick}>
            attachment {this.getIntlMessage('compose.attach')}
          </button>

          <button className="button button--lightblue" onClick={this.sendTextMessage}>
            {this.getIntlMessage('compose.send')}
          </button>

        <form className="compose__hidden" ref="attachmentForm">
          <input ref="attachment" onChange={this.handleComposeAttachmentChange} type="file"/>
        </form>

        {/* Attachment modal */}
        {isSendAttachmentOpen ? <SendAttachment/> : null}

    );
  }
}

ReactMixin.onClass(ComposeSection, IntlMixin);

export default Container.create(ComposeSection);
<|endoftext|>


<commit_before>var dataUrl = 'https://api.github.com/events';
var cacheDelayInput = document.getElementById('cache-delay');
var cacheFailInput = document.getElementById('cache-fail');
var networkDelayInput = document.getElementById('network-delay');
var networkFailInput = document.getElementById('network-fail');
var cacheStatus = document.getElementById('cache-status');
var networkStatus = document.getElementById('network-status');
var getDataButton = document.getElementById('getDataButton');
var dataElement = document.getElementById('data');

/*
 * Caches are per-origin, so we need to pick a name that no
 * other page on this origin is going to try to use.
 */
var cacheName = 'cache-then-network';

/*
 * If network data was received, we don't want an in-flight cache
 * fetch to complete and overwrite the data that we just got from
 * the network. We use this flag to let the cache fetch callbacks
 * know whether a network fetch has already completed.
 */
var gotNetworkData = false;

/*
 * For showing elapsed times
 */
var networkFetchStartTime;
var cacheFetchStartTime;

/*
 * UI functions
 */
function reset() {
  dataElement.textContent = '';
  cacheStatus.textContent = '';
  networkStatus.textContent = '';
  gotNetworkData = false;
}

function disableUI() {
  getDataButton.disabled = true;
  cacheDelayInput.disabled = true;
  cacheFailInput.disabled = true;
  networkDelayInput.disabled = true;
  networkFailInput.disabled = true;
  reset();
}

function enableUI() {
  getDataButton.disabled = false;
  cacheDelayInput.disabled = false;
  cacheFailInput.disabled = false;
  networkDelayInput.disabled = false;
  networkFailInput.disabled = false;
}

function updatePage(data) {
  dataElement.textContent = 'User ' + data[0].actor.login + ' modified repo ' + data[0].repo.name;
}

/*
 * Callbacks for network and cache fetches
 */
function handleFetchCompletion(res) {
  // Simulate a network error by throwing in the callback
  var shouldNetworkError = networkFailInput.checked;
  if (shouldNetworkError) {
    throw new Error('Network error');
  }

  // We have to clone the response here because request bodies
  // can only be read once. Placing a response in the cache
  // counts as a read and so does the `res.json` call below.
  var resClone = res.clone();
  caches.open(cacheName).then(function(cache) {
    // Cache the response so that subsequent cache fetches
    // will find this updated data.
    cache.put(dataUrl, resClone);
  });

  // Read the response as json
  res.json().then(function(data) {
    updatePage(data);
    // Make sure that any in-flight cache requests don't
    // overwrite the information that we just received.
    gotNetworkData = true;
  });
}

function handleCacheFetchCompletion(res) {
  var shouldCacheError = cacheFailInput.checked;
  if (shouldCacheError || !res) {
    throw Error('Cache miss');
  }

  // Read the response as json
  res.json().then(function(data) {
    // Only update the page if the network request hasn't
    // already returned. Otherwise we'd be overwriting the
    // data we just pulled from the network.
    if (!gotNetworkData) {
      updatePage(data);
    }
  });
}

/*
 * This is the heart of the demo. Whenever the user clicks the
 * button, we'll make simultaneous (or nearly-simultaneous) requests
 * of the cache and the network for data. In a real application, this
 * would probably happen on page load rather than as a result of
 * user action, but to make the page more illustrative and allow
 * for user-specified delays, we've got a button.
 */
getDataButton.addEventListener('click', function handleClick() {
  disableUI();

  // Initiate network fetch
  networkStatus.textContent = 'Fetching...';
  networkFetchStartTime = Date.now();
  // Use the Fetch API to actually request data
  var networkFetch = fetch(dataUrl, { mode: 'cors', cache: 'no-cache' }).then(function(res) {
    var networkDelay = networkDelayInput.value || 0;

    // We simulate network delays by waiting before actually calling
    // our network fetch callback. If the callback errors, we want
    // to make sure to reject the Promise we got from the original
    // fetch.
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          handleFetchCompletion(res);
          resolve();
        } catch (err) {
          reject(err);
        }
      }, networkDelay);
    });
  }).then(function() {
    var now = Date.now();
    var elapsed = now - networkFetchStartTime;
    networkStatus.textContent = 'Success after ' + elapsed + 'ms';
  }).catch(function(err) {
    var now = Date.now();
    var elapsed = now - networkFetchStartTime;
    networkStatus.textContent = err + ' after ' + elapsed + 'ms';
  });

  // Get cached data
  cacheStatus.textContent = 'Fetching...';
  cacheFetchStartTime = Date.now();
  var cacheFetch = caches.open(cacheName).then(function(cache) {
    return cache.match(dataUrl).then(function(res) {
      var cacheDelay = cacheDelayInput.value || 0;

      // We simulate cache delays by waiting before actually calling
      // our cache fetch callback. If the callback errors, we want
      // to make sure to reject the Promise we got from the original
      // call to `match`.
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            handleCacheFetchCompletion(res);
            resolve();
          } catch (err) {
            reject(err);
          }
        }, cacheDelay);
      });
    }).then(function() {
      var now = Date.now();
      var elapsed = now - cacheFetchStartTime;
      cacheStatus.textContent = 'Success after ' + elapsed + 'ms';
    }).catch(function(err) {
      var now = Date.now();
      var elapsed = now - cacheFetchStartTime;
      cacheStatus.textContent = err + ' after ' + elapsed + 'ms';
    });
  });

  Promise.all([networkFetch, cacheFetch]).then(enableUI);
});
<commit_msg>Use a cache buster in cache-then-network fetch<commit_after>var dataUrl = 'https://api.github.com/events';
var cacheDelayInput = document.getElementById('cache-delay');
var cacheFailInput = document.getElementById('cache-fail');
var networkDelayInput = document.getElementById('network-delay');
var networkFailInput = document.getElementById('network-fail');
var cacheStatus = document.getElementById('cache-status');
var networkStatus = document.getElementById('network-status');
var getDataButton = document.getElementById('getDataButton');
var dataElement = document.getElementById('data');

/*
 * Caches are per-origin, so we need to pick a name that no
 * other page on this origin is going to try to use.
 */
var cacheName = 'cache-then-network';

/*
 * If network data was received, we don't want an in-flight cache
 * fetch to complete and overwrite the data that we just got from
 * the network. We use this flag to let the cache fetch callbacks
 * know whether a network fetch has already completed.
 */
var gotNetworkData = false;

/*
 * For showing elapsed times
 */
var networkFetchStartTime;
var cacheFetchStartTime;

/*
 * UI functions
 */
function reset() {
  dataElement.textContent = '';
  cacheStatus.textContent = '';
  networkStatus.textContent = '';
  gotNetworkData = false;
}

function disableUI() {
  getDataButton.disabled = true;
  cacheDelayInput.disabled = true;
  cacheFailInput.disabled = true;
  networkDelayInput.disabled = true;
  networkFailInput.disabled = true;
  reset();
}

function enableUI() {
  getDataButton.disabled = false;
  cacheDelayInput.disabled = false;
  cacheFailInput.disabled = false;
  networkDelayInput.disabled = false;
  networkFailInput.disabled = false;
}

function updatePage(data) {
  dataElement.textContent = 'User ' + data[0].actor.login + ' modified repo ' + data[0].repo.name;
}

/*
 * Callbacks for network and cache fetches
 */
function handleFetchCompletion(res) {
  // Simulate a network error by throwing in the callback
  var shouldNetworkError = networkFailInput.checked;
  if (shouldNetworkError) {
    throw new Error('Network error');
  }

  // We have to clone the response here because request bodies
  // can only be read once. Placing a response in the cache
  // counts as a read and so does the `res.json` call below.
  var resClone = res.clone();
  caches.open(cacheName).then(function(cache) {
    // Cache the response so that subsequent cache fetches
    // will find this updated data.
    cache.put(dataUrl, resClone);
  });

  // Read the response as json
  res.json().then(function(data) {
    updatePage(data);
    // Make sure that any in-flight cache requests don't
    // overwrite the information that we just received.
    gotNetworkData = true;
  });
}

function handleCacheFetchCompletion(res) {
  var shouldCacheError = cacheFailInput.checked;
  if (shouldCacheError || !res) {
    throw Error('Cache miss');
  }

  // Read the response as json
  res.json().then(function(data) {
    // Only update the page if the network request hasn't
    // already returned. Otherwise we'd be overwriting the
    // data we just pulled from the network.
    if (!gotNetworkData) {
      updatePage(data);
    }
  });
}

/*
 * This is the heart of the demo. Whenever the user clicks the
 * button, we'll make simultaneous (or nearly-simultaneous) requests
 * of the cache and the network for data. In a real application, this
 * would probably happen on page load rather than as a result of
 * user action, but to make the page more illustrative and allow
 * for user-specified delays, we've got a button.
 */
getDataButton.addEventListener('click', function handleClick() {
  disableUI();

  // Initiate network fetch
  networkStatus.textContent = 'Fetching...';
  networkFetchStartTime = Date.now();

  // Use the Fetch API to actually request data
  //
  // It should not be necessary to use a cache busting URL param
  // because we're using the 'no-cache' caching option in the fetch
  // call, but those caching options are not yet implemented
  // in Firefox or Chrome. See bug 1120715 for the Firefox
  // implementation status.
  var cacheBuster = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
  var networkFetch = fetch(dataUrl + '?cacheBuster=' + cacheBuster, { mode: 'cors', cache: 'no-cache' }).then(function(res) {
    var networkDelay = networkDelayInput.value || 0;

    // We simulate network delays by waiting before actually calling
    // our network fetch callback. If the callback errors, we want
    // to make sure to reject the Promise we got from the original
    // fetch.
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          handleFetchCompletion(res);
          resolve();
        } catch (err) {
          reject(err);
        }
      }, networkDelay);
    });
  }).then(function() {
    var now = Date.now();
    var elapsed = now - networkFetchStartTime;
    networkStatus.textContent = 'Success after ' + elapsed + 'ms';
  }).catch(function(err) {
    var now = Date.now();
    var elapsed = now - networkFetchStartTime;
    networkStatus.textContent = err + ' after ' + elapsed + 'ms';
  });

  // Get cached data
  cacheStatus.textContent = 'Fetching...';
  cacheFetchStartTime = Date.now();
  var cacheFetch = caches.open(cacheName).then(function(cache) {
    return cache.match(dataUrl).then(function(res) {
      var cacheDelay = cacheDelayInput.value || 0;

      // We simulate cache delays by waiting before actually calling
      // our cache fetch callback. If the callback errors, we want
      // to make sure to reject the Promise we got from the original
      // call to `match`.
      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            handleCacheFetchCompletion(res);
            resolve();
          } catch (err) {
            reject(err);
          }
        }, cacheDelay);
      });
    }).then(function() {
      var now = Date.now();
      var elapsed = now - cacheFetchStartTime;
      cacheStatus.textContent = 'Success after ' + elapsed + 'ms';
    }).catch(function(err) {
      var now = Date.now();
      var elapsed = now - cacheFetchStartTime;
      cacheStatus.textContent = err + ' after ' + elapsed + 'ms';
    });
  });

  Promise.all([networkFetch, cacheFetch]).then(enableUI);
});
<|endoftext|>


<commit_before>import rules from '../src/validationRules';

describe('Validation Rules', () => {
    describe('isRequired', () => {
        it('should successfully validate the value', () => {
            const valid = rules.isRequired(null, 'abc');
            expect(valid).toBe(true);
        });
        it('should invalidate the value', () => {
            let valid = rules.isRequired(null, '');
            expect(valid).toBe(false);
            valid = rules.isRequired(null, null);
            expect(valid).toBe(false);
            valid = rules.isRequired(null, undefined);
            expect(valid).toBe(false);
        });
    });

    describe('maxLength', () => {
        it('should validate uninitialized values', () => {
            let valid = rules.maxLength(null, '', 1);
            expect(valid).toBe(true);
            valid = rules.maxLength(null, null, 1);
            expect(valid).toBe(true);
            valid = rules.maxLength(null, undefined, 1);
            expect(valid).toBe(true);
        });
        it('should validate inbound strings', () => {
            let valid = rules.maxLength(null, 'asd', 4);
            expect(valid).toBe(true);
            valid = rules.maxLength(null, 'asd', 3);
            expect(valid).toBe(true);
        });
        it('should invalidate outbound string', () => {
            let valid = rules.maxLength(null, 'asd', 2);
            expect(valid).toBe(false);
        });
    });

    describe('equals', () => {
        it('should successfully validate the value', () => {
            const valid = rules.equals(null, 'abc', 'abc');
            expect(valid).toBe(true);
        });
        it('should invalidate the value', () => {
            let valid = rules.equals(null, '1', 1);
            expect(valid).toBe(false);
            valid = rules.equals(null, 'abc', 'ac');
            expect(valid).toBe(false);
        });
    });

    describe('equalsField', () => {
        it('should successfully validate the value', () => {
            const valid = rules.equalsField({email: 'abc'}, 'abc', 'email');
            expect(valid).toBe(true);
        });
        it('should invalidate the value', () => {
            let valid = rules.equalsField({name: 1}, '1', 'name');
            expect(valid).toBe(false);
            valid = rules.equalsField({}, 'abc', 'ac');
            expect(valid).toBe(false);
        });
    });

    describe('equalsFields', () => {
        it('should successfully validate the value', () => {
            const valid = rules.equalsFields({email: 'abc', email2: 'abc'}, 'abc', ['email', 'email2']);
            expect(valid).toBe(true);
        });
        it('should invalidate the value', () => {
            let valid = rules.equalsFields({email: 1, email2: '1'}, '1', 'name');
            expect(valid).toBe(false);
            valid = rules.equalsFields({email: '1', email2: 1}, '1', 'name');
            expect(valid).toBe(false);
        });
    });
});
<commit_msg>added tests for isEmail and validationRules<commit_after>import rules from '../src/validationRules';

describe('Validation Rules', () => {
    describe('isRequired', () => {
        it('should successfully validate the value', () => {
            const valid = rules.isRequired(null, 'abc');
            expect(valid).toBe(true);
        });
        it('should invalidate the value', () => {
            let valid = rules.isRequired(null, '');
            expect(valid).toBe(false);
            valid = rules.isRequired(null, null);
            expect(valid).toBe(false);
            valid = rules.isRequired(null, undefined);
            expect(valid).toBe(false);
        });
    });

    describe('maxLength', () => {
        it('should validate uninitialized values', () => {
            let valid = rules.maxLength(null, '', 1);
            expect(valid).toBe(true);
            valid = rules.maxLength(null, null, 1);
            expect(valid).toBe(true);
            valid = rules.maxLength(null, undefined, 1);
            expect(valid).toBe(true);
        });
        it('should validate inbound strings', () => {
            let valid = rules.maxLength(null, 'asd', 4);
            expect(valid).toBe(true);
            valid = rules.maxLength(null, 'asd', 3);
            expect(valid).toBe(true);
        });
        it('should invalidate outbound string', () => {
            let valid = rules.maxLength(null, 'asd', 2);
            expect(valid).toBe(false);
        });
    });

    describe('equals', () => {
        it('should successfully validate the value', () => {
            const valid = rules.equals(null, 'abc', 'abc');
            expect(valid).toBe(true);
        });
        it('should invalidate the value', () => {
            let valid = rules.equals(null, '1', 1);
            expect(valid).toBe(false);
            valid = rules.equals(null, 'abc', 'ac');
            expect(valid).toBe(false);
        });
    });

    describe('equalsField', () => {
        it('should successfully validate the value', () => {
            const valid = rules.equalsField({email: 'abc'}, 'abc', 'email');
            expect(valid).toBe(true);
        });
        it('should invalidate the value', () => {
            let valid = rules.equalsField({name: 1}, '1', 'name');
            expect(valid).toBe(false);
            valid = rules.equalsField({}, 'abc', 'ac');
            expect(valid).toBe(false);
        });
    });

    describe('equalsFields', () => {
        it('should successfully validate the value', () => {
            const valid = rules.equalsFields({email: 'abc', email2: 'abc'}, 'abc', ['email', 'email2']);
            expect(valid).toBe(true);
        });
        it('should invalidate the value', () => {
            let valid = rules.equalsFields({email: 1, email2: '1'}, '1', 'name');
            expect(valid).toBe(false);
            valid = rules.equalsFields({email: '1', email2: 1}, '1', 'name');
            expect(valid).toBe(false);
        });
    });

    describe('isEmail', () => {
        it('should validate an correct email successfully', () => {
            const valid = rules.isEmail(null, 'test@gmail.com');
            expect(valid).toBe(true);
        });
        it('should invalidate an incorrect email', () => {
            let valid = rules.isEmail(null, 'abc');
            expect(valid).toBe(false);
            valid = rules.isEmail(null, null);
            expect(valid).toBe(false);
            valid = rules.isEmail(null, 'abc@abc');
            expect(valid).toBe(false);
            valid = rules.isEmail(null, 'abc@abc.');
            expect(valid).toBe(false);
        });
    });

    describe('minLength', () => {
        it('should validate the string successfully', () => {
            let valid = rules.minLength(null, 'abcdef', 6);
            expect(valid).toBe(true);
            valid = rules.minLength(null, 'abcdef', 4);
            expect(valid).toBe(true);
        });
        it('should invalidate the string', () => {
            let valid = rules.minLength(null, 'abcde', 6);
            expect(valid).toBe(false);
        });
    })
});
<|endoftext|>


<commit_before>(function() {

    angular.module('catalinker.vocabulary', [])
        .constant('ontologyUri', 'http://192.168.50.12:8005/ontology')
        .factory('ontologyFactory', ['$q', '$http', 'ontologyUri', function($q, $http, ontologyUri) {
            var httpPromise,
                deferred;

            // convert httpPromise to normal promise
            deferred = $q.defer();
            httpPromise = $http.get(ontologyUri, { headers: { accept: 'application/ld+json' } });
            httpPromise
                .success(function(data) {
                    deferred.resolve(data);
                }).error(function(data, status, headers) {
                    deferred.reject({status: status, headers: headers, data: data});
                });
            return deferred.promise;
        }])
        .factory('vocabulary', ['$q', 'ontologyFactory', function($q, ontologyFactory) {
            var context,
                vocab,
                labelIndex = {},
                deferred;

            function replaceUriWithContext(uri) {
                var result = uri;
                angular.forEach(context, function(c) {
                    if (uri.indexOf(c) === 0) {
                        result = c;
                    }
                });
                return result;
            }

            function init(ontology) {
                context = ontology['@context'];
                vocab = ontology['@graph'];

                vocab.forEach(function (obj) {
                    var label = {
                            'default': obj['rdfs:label']
                        };
                    if (obj['rdfs:label'] instanceof Array) {
                        obj['rdfs:label'].forEach(function(l) {
                            label[l['@language']] = l['@value'];
                        });
                        label['default'] = label.no || label.en;
                    }
                    labelIndex[obj['@id']] = label;
                    labelIndex[replaceUriWithContext(obj['@id'])] = label;
                });
            }

            deferred = $q.defer();
            ontologyFactory
                .then(function(data) {
                    init(data);
                    deferred.resolve();
                },function() {
                    deferred.reject();
                });

            return {
                labels: labelIndex,
                vocabulary: vocab,
                promise: deferred.promise
            };
        }]);
}());<commit_msg>Extract promise conversion.<commit_after>(function() {

    function asNormalPromise($q, httpPromise) {
        var deferred = $q.defer();
        httpPromise
            .success(function (data) {
                deferred.resolve(data);
            }).error(function (data, status, headers) {
                deferred.reject({status: status, headers: headers, data: data});
            });
        return deferred.promise;
    }

    angular.module('catalinker.vocabulary', [])
        .constant('ontologyUri', 'http://192.168.50.12:8005/ontology')
        .factory('ontologyFactory', ['$q', '$http', 'ontologyUri', function($q, $http, ontologyUri) {
            return asNormalPromise($q, $http.get(ontologyUri, {headers: {accept: 'application/ld+json'}}));
        }])
        .factory('vocabulary', ['$q', 'ontologyFactory', function($q, ontologyFactory) {
            var context,
                vocab,
                labelIndex = {},
                deferred;

            function replaceUriWithContext(uri) {
                var result = uri;
                angular.forEach(context, function(c) {
                    if (uri.indexOf(c) === 0) {
                        result = c;
                    }
                });
                return result;
            }

            function init(ontology) {
                context = ontology['@context'];
                vocab = ontology['@graph'];

                vocab.forEach(function (obj) {
                    var label = {
                            'default': obj['rdfs:label']
                        };
                    if (obj['rdfs:label'] instanceof Array) {
                        obj['rdfs:label'].forEach(function(l) {
                            label[l['@language']] = l['@value'];
                        });
                        label['default'] = label.no || label.en;
                    }
                    labelIndex[obj['@id']] = label;
                    labelIndex[replaceUriWithContext(obj['@id'])] = label;
                });
            }

            deferred = $q.defer();
            ontologyFactory
                .then(function(data) {
                    init(data);
                    deferred.resolve();
                },function() {
                    deferred.reject();
                });

            return {
                labels: labelIndex,
                vocabulary: vocab,
                promise: deferred.promise
            };
        }]);
}());<|endoftext|>