UNPKG

pondjs

Version:

A timeseries library build on top of immutable.js

1,072 lines (969 loc) 36.4 kB
/** * Copyright (c) 2016-2017, The Regents of the University of California, * through Lawrence Berkeley National Laboratory (subject to receipt * of any required approvals from the U.S. Dept. of Energy). * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ /* eslint-disable */ import Collection from "../collection"; import CollectionOut from "../io/collectionout"; import EventOut from "../io/eventout"; import IndexedEvent from "../indexedevent"; import Stream from "../io/stream"; import TimeEvent from "../timeevent"; import TimeRange from "../timerange"; import TimeRangeEvent from "../timerangeevent"; import TimeSeries from "../timeseries"; import { Pipeline } from "../pipeline"; import { keep, avg, sum, median, min, max, percentile } from "../base/functions"; const EVENT_LIST = [ new TimeEvent(new Date("2015-04-22T03:30:00Z"), { in: 1, out: 2 }), new TimeEvent(new Date("2015-04-22T03:31:00Z"), { in: 3, out: 4 }), new TimeEvent(new Date("2015-04-22T03:32:00Z"), { in: 5, out: 6 }) ]; const TRAFFIC_DATA = { name: "traffic", columns: ["time", "value"], points: [ [1409529600000, 80], [1409533200000, 88], [1409536800000, 52], [1409540400000, 80], // < 50 [1409544000000, 26], //1 [1409547600000, 37], //2 [1409551200000, 6], //3 [1409554800000, 32], //4 [1409558400000, 69], [1409562000000, 21], //5 [1409565600000, 6], //6 [1409569200000, 54], [1409572800000, 88], [1409576400000, 41], //7 [1409580000000, 35], //8 [1409583600000, 43], //9 [1409587200000, 84], [1409590800000, 32], //10 avg= (26 + 37 + 6 + 32 + 21 + 6 + 41 + 35 + 43 + 32)/10 = 27.9 [1409594400000, 41], [1409598000000, 57], [1409601600000, 27], [1409605200000, 50], [1409608800000, 13], [1409612400000, 63], [1409616000000, 58], [1409619600000, 80], [1409623200000, 59], [1409626800000, 96], [1409630400000, 2], [1409634000000, 20], [1409637600000, 64], [1409641200000, 7], [1409644800000, 50], [1409648400000, 88], [1409652000000, 34], [1409655600000, 31], [1409659200000, 16], [1409662800000, 38], [1409666400000, 94], [1409670000000, 78], [1409673600000, 86], [1409677200000, 13], [1409680800000, 34], [1409684400000, 29], [1409688000000, 48], [1409691600000, 80], [1409695200000, 30], [1409698800000, 15], [1409702400000, 62], [1409706000000, 66], [1409709600000, 44], [1409713200000, 94], [1409716800000, 78], [1409720400000, 29], [1409724000000, 21], [1409727600000, 4], [1409731200000, 83], [1409734800000, 15], [1409738400000, 89], [1409742000000, 53], [1409745600000, 70], [1409749200000, 41], [1409752800000, 47], [1409756400000, 30], [1409760000000, 68], [1409763600000, 89], [1409767200000, 29], [1409770800000, 17], [1409774400000, 38], [1409778000000, 67], [1409781600000, 75], [1409785200000, 89], [1409788800000, 47], [1409792400000, 82], [1409796000000, 33], [1409799600000, 67], [1409803200000, 93], [1409806800000, 86], [1409810400000, 97], [1409814000000, 19], [1409817600000, 19], [1409821200000, 31], [1409824800000, 56], [1409828400000, 19], [1409832000000, 43], [1409835600000, 29], [1409839200000, 72], [1409842800000, 27], [1409846400000, 21], [1409850000000, 88], [1409853600000, 18], [1409857200000, 30], [1409860800000, 46], [1409864400000, 34], [1409868000000, 31], [1409871600000, 20], [1409875200000, 45], [1409878800000, 17], [1409882400000, 24], [1409886000000, 84], [1409889600000, 6], [1409893200000, 91], [1409896800000, 82], [1409900400000, 71], [1409904000000, 97], [1409907600000, 43], [1409911200000, 38], [1409914800000, 1], [1409918400000, 71], [1409922000000, 50], [1409925600000, 19], [1409929200000, 19], [1409932800000, 86], [1409936400000, 65], [1409940000000, 93], [1409943600000, 35] ] }; const inOutData = { name: "traffic", columns: ["time", "in", "out", "perpendicular"], points: [ [1409529600000, 80, 37, 1000], [1409533200000, 88, 22, 1001], [1409536800000, 52, 56, 1002] ] }; describe("Pipeline", () => { describe("test processor using offsetBy", () => { it("can transform process events with an offsetBy chain", () => { const events = EVENT_LIST; const collection = new Collection(events); let c1; let c2; const p1 = Pipeline() .from(collection) .offsetBy(1, "in") .offsetBy(2) .to(CollectionOut, c => c1 = c); // --> Specified output, evokes batch op const p2 = p1.offsetBy(3, "in").to(CollectionOut, c => { // || c2 = c; }); // --> Specified output, evokes batch op expect(c1.size()).toBe(3); expect(c1.at(0).get("in")).toBe(4); expect(c1.at(1).get("in")).toBe(6); expect(c1.at(2).get("in")).toBe(8); expect(c2.size()).toBe(3); expect(c2.at(0).get("in")).toBe(7); expect(c2.at(1).get("in")).toBe(9); expect(c2.at(2).get("in")).toBe(11); }); it("can stream from an unbounded source directly to output", () => { let out; const events = EVENT_LIST; const stream = new Stream(); const p = Pipeline().from(stream).to(CollectionOut, c => out = c); stream.addEvent(events[0]); stream.addEvent(events[1]); expect(out.size()).toBe(2); }); it("can stream events with an offsetBy pipeline", () => { let out; const events = EVENT_LIST; const stream = new Stream(); const p = Pipeline() .from(stream) .offsetBy(3, "in") .to(CollectionOut, c => out = c); stream.addEvent(events[0]); stream.addEvent(events[1]); expect(out.size()).toBe(2); expect(out.at(0).get("in")).toBe(4); expect(out.at(1).get("in")).toBe(6); }); it("can stream events with two offsetBy pipelines...", () => { let out1, out2; const events = EVENT_LIST; const stream = new Stream(); const p1 = Pipeline() .from(stream) .offsetBy(1, "in") .offsetBy(2) .to(CollectionOut, c => out1 = c); const p2 = p1.offsetBy(3, "in").to(CollectionOut, c => out2 = c); stream.addEvent(events[0]); expect(out1.size()).toBe(1); expect(out2.size()).toBe(1); expect(out1.at(0).get("in")).toBe(4); expect(out2.at(0).get("in")).toBe(7); }); }); describe("TimeSeries pipeline", () => { const data = { name: "traffic", columns: ["time", "value", "status"], points: [ [1400425947000, 52, "ok"], [1400425948000, 18, "ok"], [1400425949000, 26, "fail"], [1400425950000, 93, "offline"] ] }; it("can transform process events with an offsetBy chain", () => { let out; const timeseries = new TimeSeries(data); const p1 = Pipeline() .from(timeseries.collection()) .offsetBy(1, "value") .offsetBy(2) .to(CollectionOut, c => out = c); expect(out.at(0).get()).toBe(55); expect(out.at(1).get()).toBe(21); expect(out.at(2).get()).toBe(29); expect(out.at(3).get()).toBe(96); }); it( "can transform events with an offsetBy chain straight from a TimeSeries", () => { let out; const timeseries = new TimeSeries(data); timeseries .pipeline() .offsetBy(1, "value") .offsetBy(2) .to(CollectionOut, c => out = c); expect(out.at(0).get()).toBe(55); expect(out.at(1).get()).toBe(21); expect(out.at(2).get()).toBe(29); expect(out.at(3).get()).toBe(96); } ); it("should be able to batch a TimeSeries with an offset", () => { const timeseries = new TimeSeries(TRAFFIC_DATA); const outputEvents = []; Pipeline() .from(timeseries) .offsetBy(1, "value") .offsetBy(2) .to(EventOut, c => outputEvents.push(c)); expect(outputEvents.length).toBe(timeseries.size()); }); it( "should be able to batch a TimeSeries with no processing nodes", () => { const stream = new TimeSeries(TRAFFIC_DATA); const outputEvents = []; Pipeline().from(stream).to(EventOut, c => outputEvents.push(c)); expect(outputEvents.length).toBe(stream.size()); } ); }); describe("aggregation", () => { it("can aggregate events into by windowed avg", () => { const eventsIn = []; eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 7, 57, 0), { in: 3, out: 1 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 7, 58, 0), { in: 9, out: 2 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 7, 59, 0), { in: 6, out: 6 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 8, 0, 0), { in: 4, out: 7 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 8, 1, 0), { in: 5, out: 9 })); const stream = new Stream(); const result = {}; const p = Pipeline() .from(stream) .windowBy("1h") .emitOn("eachEvent") .aggregate({ in_avg: { in: avg() }, out_avg: { out: avg() } }) .to(EventOut, event => { result[`${event.index()}`] = event; }); eventsIn.forEach(event => stream.addEvent(event)); expect(result["1h-396199"].get("in_avg")).toBe(6); expect(result["1h-396199"].get("out_avg")).toBe(3); expect(result["1h-396200"].get("in_avg")).toBe(4.5); expect(result["1h-396200"].get("out_avg")).toBe(8); }); it("an collect together events and aggregate", () => { const eventsIn = []; eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 7, 57, 0), { type: "a", in: 3, out: 1 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 7, 58, 0), { type: "a", in: 9, out: 2 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 7, 59, 0), { type: "b", in: 6, out: 6 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 8, 0, 0), { type: "a", in: 4, out: 7 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 8, 1, 0), { type: "b", in: 5, out: 9 })); const stream = new Stream(); const result = {}; const p = Pipeline() .from(stream) .groupBy("type") .windowBy("1h") .emitOn("eachEvent") .aggregate({ type: { type: keep() }, // keep the type in_avg: { in: avg() }, // avg in -> in_avg // avg out -> out_avg out_avg: { out: avg() } }) .to( EventOut, event => result[`${event.index()}:${event.get("type")}`] = event ); eventsIn.forEach(event => stream.addEvent(event)); expect(result["1h-396199:a"].get("in_avg")).toBe(6); expect(result["1h-396199:a"].get("out_avg")).toBe(1.5); expect(result["1h-396199:b"].get("in_avg")).toBe(6); expect(result["1h-396199:b"].get("out_avg")).toBe(6); expect(result["1h-396200:a"].get("in_avg")).toBe(4); expect(result["1h-396200:a"].get("out_avg")).toBe(7); expect(result["1h-396200:b"].get("in_avg")).toBe(5); expect(result["1h-396200:b"].get("out_avg")).toBe(9); }); it( "can aggregate events by windowed avg and convert them to TimeEvents", () => { const eventsIn = []; eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 1, 57, 0), { in: 3, out: 1 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 1, 58, 0), { in: 9, out: 2 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 1, 59, 0), { in: 6, out: 6 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 2, 0, 0), { in: 4, out: 7 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 2, 1, 0), { in: 5, out: 9 })); const stream = new Stream(); const result = {}; Pipeline() .from(stream) .windowBy("1h") .emitOn("eachEvent") .aggregate({ in_avg: { in: avg() }, out_avg: { out: avg() } }) .asTimeRangeEvents({ alignment: "lag" }) .to(EventOut, event => { result[`${+event.timestamp()}`] = event; }); eventsIn.forEach(event => stream.addEvent(event)); expect(result["1426294800000"].get("in_avg")).toBe(6); expect(result["1426294800000"].get("out_avg")).toBe(3); expect(result["1426298400000"].get("in_avg")).toBe(4.5); expect(result["1426298400000"].get("out_avg")).toBe(8); } ); it("can aggregate events to get percentiles", () => { const eventsIn = []; eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 1, 57, 0), { in: 3, out: 1 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 1, 58, 0), { in: 9, out: 2 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 1, 59, 0), { in: 6, out: 6 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 2, 0, 0), { in: 4, out: 7 })); eventsIn.push(new TimeEvent(Date.UTC(2015, 2, 14, 2, 1, 0), { in: 5, out: 9 })); const stream = new Stream(); const result = {}; Pipeline() .from(stream) .windowBy("1h") .emitOn("eachEvent") .aggregate({ in_low: { in: min() }, in_25th: { in: percentile(25) }, in_median: { in: median() }, in_75th: { in: percentile(75) }, in_high: { in: max() } }) .asTimeEvents() .to(EventOut, event => { result[`${+event.timestamp()}`] = event; }); eventsIn.forEach(event => stream.addEvent(event)); expect(result["1426296600000"].get("in_low")).toBe(3); expect(result["1426296600000"].get("in_25th")).toBe(4.5); expect(result["1426296600000"].get("in_median")).toBe(6); expect(result["1426296600000"].get("in_75th")).toBe(7.5); expect(result["1426296600000"].get("in_high")).toBe(9); }); }); describe("Pipeline event conversion", () => { const timestamp = new Date(1426316400000); const e = new TimeEvent(timestamp, 3); it( "should be able to convert from an TimeEvent to an TimeRangeEvent, using a duration, in front of the event", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeRangeEvents({ alignment: "front", duration: "1h" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"timerange":[1426316400000,1426320000000],"data":{"value":3}}` ); done(); }); stream.addEvent(e); } ); it( "should be able to convert from an TimeEvent to an TimeRangeEvent, using a duration, surrounding the event", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeRangeEvents({ alignment: "center", duration: "1h" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"timerange":[1426314600000,1426318200000],"data":{"value":3}}` ); done(); }); stream.addEvent(e); } ); it( "should be able to convert from an TimeEvent to an TimeRangeEvent, using a duration, in behind of the event", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeRangeEvents({ alignment: "behind", duration: "1h" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"timerange":[1426312800000,1426316400000],"data":{"value":3}}` ); done(); }); stream.addEvent(e); } ); it( "should be able to convert from an TimeEvent to an IndexedEvent", done => { const stream = new Stream(); Pipeline() .from(stream) .asIndexedEvents({ duration: "1h" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"index":"1h-396199","data":{"value":3}}` ); done(); }); stream.addEvent(e); } ); it( "should be able to convert from an TimeEvent to an TimeEvent as a noop", done => { const stream = new Stream(); Pipeline().from(stream).asTimeEvents().to(EventOut, event => { expect(event).toBe(e); done(); }); stream.addEvent(e); } ); }); describe("TimeRangeEvent conversion", () => { const timeRange = new TimeRange([1426316400000, 1426320000000]); const timeRangeEvent = new TimeRangeEvent(timeRange, 3); it( "should be able to convert from an TimeRangeEvent to an TimeEvent, using the center of the range", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeEvents({ alignment: "center" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"time":1426318200000,"data":{"value":3}}` ); done(); }); stream.addEvent(timeRangeEvent); } ); it( "should be able to convert from an TimeRangeEvent to an TimeEvent, using beginning of the range", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeEvents({ alignment: "lag" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"time":1426316400000,"data":{"value":3}}` ); done(); }); stream.addEvent(timeRangeEvent); } ); it( "should be able to convert from an TimeRangeEvent to an TimeEvent, using the end of the range", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeEvents({ alignment: "lead" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"time":1426320000000,"data":{"value":3}}` ); done(); }); stream.addEvent(timeRangeEvent); } ); it( "should be able to convert from an TimeRangeEvent to an TimeRangeEvent as a noop", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeRangeEvents() .to(EventOut, event => { expect(event).toBe(timeRangeEvent); done(); }); stream.addEvent(timeRangeEvent); } ); }); describe("IndexedEvent conversion", () => { const indexedEvent = new IndexedEvent("1h-396199", 3); it( "should be able to convert from an IndexedEvent to an TimeEvent, using the center of the range", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeEvents({ alignment: "center" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"time":1426318200000,"data":{"value":3}}` ); done(); }); stream.addEvent(indexedEvent); } ); it( "should be able to convert from an IndexedEvent to an TimeEvent, using beginning of the range", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeEvents({ alignment: "lag" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"time":1426316400000,"data":{"value":3}}` ); done(); }); stream.addEvent(indexedEvent); } ); it( "should be able to convert from an IndexedEvent to an TimeEvent, using the end of the range", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeEvents({ alignment: "lead" }) .to(EventOut, event => { expect(`${event}`).toBe( `{"time":1426320000000,"data":{"value":3}}` ); done(); }); stream.addEvent(indexedEvent); } ); it( "should be able to convert from an IndexedEvent to an TimeRangeEvent", done => { const stream = new Stream(); Pipeline() .from(stream) .asTimeRangeEvents() .to(EventOut, event => { expect(`${event}`).toBe( `{"timerange":[1426316400000,1426320000000],"data":{"value":3}}` ); done(); }); stream.addEvent(indexedEvent); } ); it( "should be able to convert from an IndexedEvent to an IndexedEvent as a noop", done => { const stream = new Stream(); Pipeline() .from(stream) .asIndexedEvents() .to(EventOut, event => { expect(event).toBe(indexedEvent); done(); }); stream.addEvent(indexedEvent); } ); }); describe("Filtering events in batch", () => { it("should be able to filter a TimeSeries", () => { const outputEvents = []; const timeseries = new TimeSeries(TRAFFIC_DATA); Pipeline() .from(timeseries) .filter(e => e.value() > 65) .to(EventOut, c => outputEvents.push(c)); expect(outputEvents.length).toBe(39); }); }); describe("Selecting subset of columns from a TimeSeries in batch", () => { it("should be able select a single column", () => { let result; const timeseries = new TimeSeries(inOutData); Pipeline().from(timeseries).select("in").to( CollectionOut, c => result = new TimeSeries({ name: "newTimeseries", collection: c }) ); expect(result.columns()).toEqual(["in"]); }); it("should be able select a subset of columns", () => { let result; const timeseries = new TimeSeries(inOutData); Pipeline().from(timeseries).select(["out", "perpendicular"]).to( CollectionOut, c => result = new TimeSeries({ name: "subset", collection: c }) ); expect(result.columns()).toEqual(["out", "perpendicular"]); }); }); describe("Collapsing in batch", () => { it("should be able collapse a subset of columns", done => { const timeseries = new TimeSeries(inOutData); Pipeline() .from(timeseries) .collapse(["in", "out"], "in_out_sum", sum()) .emitOn("flush") .to( CollectionOut, c => { const ts = new TimeSeries({ name: "subset", collection: c }); expect(ts.at(0).get("in_out_sum")).toBe(117); expect(ts.at(1).get("in_out_sum")).toBe(110); expect(ts.at(2).get("in_out_sum")).toBe(108); done(); }, /*flush=*/ true ); }); it("should be able chain collapse operations together", done => { const timeseries = new TimeSeries(inOutData); Pipeline() .from(timeseries) .collapse(["in", "out"], "in_out_sum", sum(), true) .collapse(["in", "out"], "in_out_max", max(), true) .emitOn("flush") .to( CollectionOut, c => { const ts = new TimeSeries({ name: "subset", collection: c }); expect(ts.at(0).get("in_out_sum")).toBe(117); expect(ts.at(1).get("in_out_sum")).toBe(110); expect(ts.at(2).get("in_out_sum")).toBe(108); expect(ts.at(0).get("in_out_max")).toBe(80); expect(ts.at(1).get("in_out_max")).toBe(88); expect(ts.at(2).get("in_out_max")).toBe(56); done(); }, /*flush=*/ true ); }); it("should be able sum element-wise and then find the max", done => { const timeseries = new TimeSeries(inOutData); Pipeline() .from(timeseries) .collapse(["in", "out"], "total", sum()) .emitOn("flush") .aggregate({ max_total: { total: max() } }) .to( EventOut, e => { expect(e.get("max_total")).toBe(117); done(); }, /*flush=*/ true ); }); }); describe("Batch pipeline with return value", () => { it( "should be able to collect first 10 events over 65 and under 65", () => { let result = {}; const timeseries = new TimeSeries(TRAFFIC_DATA); const collections = Pipeline() .from(timeseries) .emitOn("flush") .groupBy(e => e.value() > 65 ? "high" : "low") .take(10) .toKeyedCollections(); //expect(result["low"].size()).toBe(10); //expect(result["high"].size()).toBe(10); } ); }); describe("Mapping in batch", () => { it("should be able map one event to a modified event", done => { const timeseries = new TimeSeries(inOutData); Pipeline() .from(timeseries) .map(e => e.setData({ in: e.get("out"), out: e.get("in") })) .emitOn("flush") .to( CollectionOut, c => { const ts = new TimeSeries({ name: "subset", collection: c }); expect(ts.at(0).get("in")).toBe(37); expect(ts.at(0).get("out")).toBe(80); expect(ts.size()).toBe(3); done(); }, /*flush=*/ true ); }); }); describe("Take n events in batch", () => { it("should be able to take 10 events from a TimeSeries", () => { let result; const timeseries = new TimeSeries(TRAFFIC_DATA); Pipeline().from(timeseries).take(10).to( CollectionOut, c => result = new TimeSeries({ name: "result", collection: c }) ); expect(result.size()).toBe(10); }); it("should be able to aggregate in batch global window", () => { let result; const timeseries = new TimeSeries(TRAFFIC_DATA); const p = Pipeline() .from(timeseries) .filter(e => e.value() < 50) .take(10) .aggregate({ value: { value: avg() } }) .to( EventOut, event => { result = event; }, true ); expect(result.timerange().toString()).toBe( "[1409544000000,1409590800000]" ); expect(result.value()).toBe(27.9); }); it("should be able to collect first 10 events over 65", () => { let result; const timeseries = new TimeSeries(TRAFFIC_DATA); const p = Pipeline() .from(timeseries) .filter(e => e.value() > 65) .take(10) .to( CollectionOut, collection => { result = collection; }, true ); expect(result.size()).toBe(10); expect(result.at(0).value()).toBe(80); expect(result.at(1).value()).toBe(88); expect(result.at(5).value()).toBe(84); }); it( "should be able to collect first 10 events over 65 and under 65", () => { let result = {}; const timeseries = new TimeSeries(TRAFFIC_DATA); const p = Pipeline() .from(timeseries) .groupBy(e => e.value() > 65 ? "high" : "low") .take(10) .to( CollectionOut, (collection, windowKey, groupByKey) => { result[groupByKey] = collection; }, true ); expect(result["low"].size()).toBe(10); expect(result["high"].size()).toBe(10); } ); it( "should be able to take the first 10 events, then split over 65 and under 65 into two collections", () => { let result = {}; const timeseries = new TimeSeries(TRAFFIC_DATA); const p = Pipeline() .from(timeseries) .take(10) .groupBy(e => e.value() > 65 ? "high" : "low") .to( CollectionOut, (collection, windowKey, groupByKey) => { result[groupByKey] = collection; }, true ); expect(result["high"].size()).toBe(4); expect(result["low"].size()).toBe(6); } ); it("should be able to count() the split over 65 and under 65", () => { let result = {}; const timeseries = new TimeSeries(TRAFFIC_DATA); const p = Pipeline() .from(timeseries) .take(10) .groupBy(e => e.value() > 65 ? "high" : "low") .emitOn("flush") .count( (count, windowKey, groupByKey) => result[groupByKey] = count ); expect(result["high"]).toBe(4); expect(result["low"]).toBe(6); }); }); });