Home Identifier Source

src/event.js

import moment from "moment";
import _ from "underscore";
import Immutable from "immutable";
import Index from "./index";
import TimeRange from "./range";

//
// Util
//

function timestampFromArgs(arg1) {
    let timestamp;
    if (_.isNumber(arg1)) {
        timestamp = new Date(arg1);
    } else if (_.isDate(arg1)) {
        timestamp = new Date(arg1.getTime());
    } else if (moment.isMoment(arg1)) {
        timestamp = new Date(arg1.valueOf());
    }
    return timestamp;
}

function dataFromArgs(arg1) {
    let data = {};
    if (_.isObject(arg1)) {
        data = new Immutable.Map(arg1);
    } else if (data instanceof Immutable.Map) {
        data = arg1;
    } else {
        data = new Immutable.Map({value: arg1});
    }
    return data;
}

/**
 * A generic event
 *
 * This represents a data object at a single timestamp, supplied
 * at initialization.
 *
 * The timestamp may be a javascript Date object or a Moment, but is
 * stored internally as ms since UNIX epoch.
 *
 * The data may be any type.
 *
 * Asking the Event object for the timestamp returns an integer copy
 * of the number of ms since the UNIX epoch. There's no method on
 * the Event object to mutate the Event timestamp after it is created.
 */
export class Event {

    /**
     * The creation of an Event is done by combining two parts:
     * the timestamp (or time range, or Index...) and the data.
     *
     * To construct you specify the timestamp as either:
     *     - Javascript Date object
     *     - a Moment, or
     *     - ms timestamp: the number of ms since the UNIX epoch
     *
     * To specify the data you can supply either:
     *     - a Javascript object containing key values pairs
     *     - an Immutable.Map, or
     *     - a simple type such as an integer. In the case of the simple type
     *       this is a shorthand for supplying {"value": v}.
     */
    constructor(arg1, arg2) {
        // Copy constructor
        if (arg1 instanceof Event) {
            let other = arg1;
            this._time = other._time;
            this._data = other._data;
            return;
        }

        // Timestamp
        this._time = timestampFromArgs(arg1);

        // Data
        this._data = dataFromArgs(arg2);
    }

    /**
     * Returns the Event as a JSON object, essentially:
     *  {time: t, data: {key: value, ...}}
     * @return {Object} The event as JSON.
     */
    toJSON() {
        return {time: this._time.getTime(), data: this._data.toJSON()};
    }

    /**
     * Retruns the Event as a string, useful for serialization.
     * @return {string} The Event as a string
     */
    toString() {
        return JSON.stringify(this.toJSON());
    }

    /**
     * The timestamp of this data, in UTC time, as a string.
     * @return {string} Time of this data.
     */
    timestampAsUTCString() {
        return this._time.toUTCString();
    }

    /**
     * The timestamp of this data, in Local time, as a string.
     * @return {string} Time of this data.
     */
    timestampAsLocalString() {
        return this._time.toString();
    }

    /**
     * The timestamp of this data
     * @return {Date} Time of this data.
     */
    timestamp() {
        return this._time;
    }

    /**
     * Access the event data
     * @return {Immutable.Map} Data for the Event
     */
    data() {
        return this._data;
    }

    /**
     * Get specific data out of the Event
     * @param  {string} key Key to lookup, or "value" if not specified.
     * @return {Object}     The data associated with this key
     */
    get(key) {
        const k = key || "value";
        return this._data.get(k);
    }

    stringify() {
        return JSON.stringify(this._data);
    }
}

/**
 * A TimeRangeEvent uses a TimeRange to specify the range over
 * which the event occurs and maps that to a data object representing some
 * measurements or metrics during that time range.
 *
 * You supply the timerange as a TimeRange object.
 *
 * The data is also specified during construction and me be either:
 *  1) a Javascript object or simple type
 *  2) an Immutable.Map.
 *  3) Simple measurement
 *
 * If an Javascript object is provided it will be stored internally as an
 * Immutable Map. If the data provided is some other simple type (such as an
 * integer) then it will be equivalent to supplying an object of {value: data}.
 * Data may also be undefined.
 *
 * To get the data out of an TimeRangeEvent instance use `data()`.
 * It will return an Immutable.Map. Alternatively you can call `toJSON()`
 * to return a Javascript object representation of the data, while
 * `toString()` will serialize the event to a string.
 */
export class TimeRangeEvent {

    /**
     * The creation of an TimeRangeEvent is done by combining two parts:
     * the timerange and the data.
     *
     * To construct you specify a TimeRange, along with the data.
     *
     * To specify the data you can supply either:
     *     - a Javascript object containing key values pairs
     *     - an Immutable.Map, or
     *     - a simple type such as an integer. In the case of the simple type
     *       this is a shorthand for supplying {"value": v}.
     */
    constructor(arg1, arg2) {
        // Timerange
        if (arg1 instanceof TimeRange) {
            let timerange = arg1;
            this._range = timerange;
        }

        // Data
        this._data = dataFromArgs(arg2);
    }

    toJSON() {
        return {timerange: this._range.toJSON(), data: this._data.toJSON()};
    }

    toString() {
        return JSON.stringify(this.toJSON());
    }

    //
    // Access the timerange represented by the index
    //

    /**
     * The TimeRange of this data
     * @return {TimeRange} TimeRange of this data.
     */
    timerange() {
        return this._range;
    }

    /**
     * The TimeRange of this data, in UTC, as a string.
     * @return {string} TimeRange of this data.
     */
    timerangeAsUTCString() {
        return this.timerange().toUTCString();
    }

    /**
     * The TimeRange of this data, in Local time, as a string.
     * @return {string} TimeRange of this data.
     */
    timerangeAsLocalString() {
        return this.timerange().toLocalString();
    }

    /**
     * The begin time of this Event
     * @return {Data} Begin time
     */
    begin() {
        return this._range.begin();
    }

    /**
     * The end time of this Event
     * @return {Data} End time
     */
    end() {
        return this._range.end();
    }

    /**
     * Alias for the begin() time.
     * @return {Data} Time representing this Event
     */
    timestamp() {
        return this.begin();
    }

    humanizeDuration() {
        return this._range.humanizeDuration();
    }

    /**
     * Access the event data
     * @return {Immutable.Map} Data for the Event
     */
    data() {
        return this._data;
    }

    /**
     * Get specific data out of the Event
     * @param  {string} key Key to lookup, or "value" if not specified.
     * @return {Object}     The data associated with this key
     */
    get(key) {
        const k = key || "value";
        return this._data.get(k);
    }
}

/**
 * An IndexedEvent uses an Index to specify a timerange over which the event
 * occurs and maps that to a data object representing some measurement or metric
 * during that time range.
 *
 * You can supply the index as a string or as an Index object.
 *
 * Example Indexes are:
 *     - 1d-1565 is the entire duration of the 1565th day since the UNIX epoch
 *     - 2014-03 is the entire duration of march in 2014
 *
 * The range, as expressed by the Index, is provided by the convenience method
 * `range()`, which returns a TimeRange instance. Alternatively the begin
 * and end times represented by the Index can be found with `begin()`
 * and `end()` respectively.
 *
 * The data is also specified during construction, and is generally expected to
 * be an object or an Immutable.Map. If an object is provided it will be stored
 * internally as an ImmutableMap. If the data provided is some other type then
 * it will be equivalent to supplying an object of `{value: data}`. Data may be
 * undefined.
 *
 * The get the data out of an IndexedEvent instance use `data()`. It will return
 * an Immutable.Map.
 */
export class IndexedEvent {

    /**
     * The creation of an IndexedEvent is done by combining two parts:
     * the Index and the data.
     *
     * To construct you specify an Index, along with the data.
     *
     * The index may be an Index, or a string.
     *
     * To specify the data you can supply either:
     *     - a Javascript object containing key values pairs
     *     - an Immutable.Map, or
     *     - a simple type such as an integer. In the case of the simple type
     *       this is a shorthand for supplying {"value": v}.
     */
    constructor(index, data, utc) {
        // Index
        if (_.isString(index)) {
            this._index = new Index(index, utc);
        } else if (index instanceof Index) {
            this._index = index;
        }

        // Data
        if (_.isObject(data)) {
            this._data = new Immutable.Map(data);
        } else if (data instanceof Immutable.Map) {
            this._data = data;
        } else {
            this._data = new Immutable.Map({value: data});
        }
    }

    toJSON() {
        return {index: this._index.asString(), data: this._data.toJSON()};
    }

    toString() {
        return JSON.stringify(this.toJSON());
    }

    /**
     * Returns the Index associated with the data in this Event
     * @return {Index} The Index
     */
    index() {
        return this._index;
    }

    /**
     * The TimeRange of this data, in UTC, as a string.
     * @return {string} TimeRange of this data.
     */
    timerangeAsUTCString() {
        return this.timerange().toUTCString();
    }

    /**
     * The TimeRange of this data, in Local time, as a string.
     * @return {string} TimeRange of this data.
     */
    timerangeAsLocalString() {
        return this.timerange().toLocalString();
    }

    /**
     * The TimeRange of this data
     * @return {TimeRange} TimeRange of this data.
     */
    timerange() {
        return this._index.asTimerange();
    }

    /**
     * The begin time of this Event
     * @return {Data} Begin time
     */
    begin() {
        return this.timerange().begin();
    }

    /**
     * The end time of this Event
     * @return {Data} End time
     */
    end() {
        return this.timerange().end();
    }

    /**
     * Alias for the begin() time.
     * @return {Data} Time representing this Event
     */
    timestamp() {
        return this.begin();
    }

    /**
     * Access the event data
     * @return {Immutable.Map} Data for the Event
     */
    data() {
        return this._data;
    }

    /**
     * Get specific data out of the Event
     * @param  {string} key Key to lookup, or "value" if not specified.
     * @return {Object}     The data associated with this key
     */
    get(key) {
        const k = key || "value";
        return this._data.get(k);
    }
}