diff --git a/app/javascript/components/SocketProvider/SocketProvider.js b/app/javascript/components/SocketProvider/SocketProvider.js index 7c9029c..02cbabb 100644 --- a/app/javascript/components/SocketProvider/SocketProvider.js +++ b/app/javascript/components/SocketProvider/SocketProvider.js @@ -1,11 +1,17 @@ /* global App:false */ import { Component } from 'react'; +import makeSubject from 'callbag-subject'; +import pipe from 'callbag-pipe'; +import map from 'callbag-map'; +import subscribe from 'callbag-subscribe'; + export class SocketProvider extends Component { constructor( props ) { super( props ); this.state = {}; + this.series = makeSubject(); this.publish = this.publish.bind( this ); } @@ -15,6 +21,29 @@ export class SocketProvider extends Component { } componentDidMount() { + this.pipeline = pipe( + this.series, + sample( 500 ), + map( ({ responses = [] }) => { + if ( responses.length === 0 ) return 0; + + return responses.reduce( ( acc, { value }) => acc + value, 0 ) / responses.length; + }), + subscribe( average => { + this.setState( ({ series = [] }) => { + return { + average, + series: [ + ...series.slice( -60 ), + average + ] + }; + }); + + console.log( 'Pipeline:', average ); + }) + ); + this.subscription = App.cable.subscriptions.create({ channel: "IssueChannel" }, { connected: function() { console.log('connected to IssueChannel') @@ -25,6 +54,7 @@ export class SocketProvider extends Component { }, received: ( data ) => { + this.series( 1, data ); this.setState( data ); } }); @@ -37,3 +67,46 @@ export class SocketProvider extends Component { this.subscription.send({ uuid, ...data }); } } + +const sample = ( period = 1000 ) => source => ( start, sink ) => { + let talkback; + let latest; + let interval; + + function receive( data ) { + latest = data; + + if ( !interval ) { + interval = setInterval( send, period ); + + send(); + } + } + + function send() { + sink( 1, latest ); + } + + function terminate( err ) { + clearInterval( interval ); + interval = null; + + // Send any pending data before terminating. + send(); + sink( 2, err ); + } + + if ( start === 0 ) { + source( 0, ( t, d ) => { + if ( t === 0 ) talkback = d; + if ( t === 1 ) receive( d ); + if ( t === 0 || t === 1 ) talkback( 1 ); + if ( t === 2 ) terminate( d ); + }); + + sink( 0, ( t ) => { + // Not pullable, so ignore t === 1. + if ( t === 2 && talkback ) talkback( 2 ); + }); + } +} diff --git a/app/javascript/components/Spark/Spark.css b/app/javascript/components/Spark/Spark.css new file mode 100644 index 0000000..f465879 --- /dev/null +++ b/app/javascript/components/Spark/Spark.css @@ -0,0 +1,4 @@ +.container { + background-color: #F6F6F6; + margin: 10px; +} diff --git a/app/javascript/components/Spark/Spark.js b/app/javascript/components/Spark/Spark.js new file mode 100644 index 0000000..cee767c --- /dev/null +++ b/app/javascript/components/Spark/Spark.js @@ -0,0 +1,12 @@ +// @flow +import React from 'react'; + +import styles from './Spark.css'; + +const HEIGHT = 60; + +export const Spark = ({ series }) => ( + + `${ index ? 'L' : 'M' } ${ 10 * index } ${ HEIGHT - ( value * ( HEIGHT - 10 ) + 5 ) }` )].join( ' ' ) } stroke="#222" fill="transparent" /> + +); diff --git a/app/javascript/components/Spark/index.js b/app/javascript/components/Spark/index.js new file mode 100644 index 0000000..94b2e30 --- /dev/null +++ b/app/javascript/components/Spark/index.js @@ -0,0 +1 @@ +export * from './Spark'; diff --git a/app/javascript/packs/hello_react.jsx b/app/javascript/packs/hello_react.jsx index bc5ee03..a63b24e 100644 --- a/app/javascript/packs/hello_react.jsx +++ b/app/javascript/packs/hello_react.jsx @@ -11,17 +11,19 @@ import { SocketProvider } from '../components/SocketProvider'; import { Controls } from '../components/Controls'; import { Graph } from '../components/Graph'; import { Bar } from '../components/Bar'; +import { Spark } from '../components/Spark'; const Hello = () => (

Focus Group

- { ({ responses = [], q, min, max, publish }) => ( + { ({ responses = [], series = [], q, min, max, publish }) => ( - { responses.length > 0 && ( - acc + value, 0 ) / responses.length } /> - )} + { responses.length > 0 && ( + acc + value, 0 ) / responses.length } /> + )} + )} diff --git a/package.json b/package.json index 6714f77..a6706c0 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,10 @@ "@rails/webpacker": "3.5", "autoprefixer": "^9.1.5", "babel-preset-react": "^6.24.1", + "callbag-map": "^1.0.1", + "callbag-pipe": "^1.1.1", + "callbag-subject": "^1.0.2", + "callbag-subscribe": "^1.4.1", "css-loader": "^1.0.0", "formik": "^1.2.0", "postcss-loader": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 0c02a86..44f3980 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1276,6 +1276,22 @@ call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" +callbag-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/callbag-map/-/callbag-map-1.0.1.tgz#a5bcd41cde60c226cdd22f5868beb7b2ecd6c167" + +callbag-pipe@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/callbag-pipe/-/callbag-pipe-1.1.1.tgz#f9d9f651f0bb64bc78d91f7132d738f066e3a4cf" + +callbag-subject@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/callbag-subject/-/callbag-subject-1.0.2.tgz#63923185000bcb10ec12d03491102aa43df93080" + +callbag-subscribe@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/callbag-subscribe/-/callbag-subscribe-1.4.1.tgz#f9bd86b713230c9113acf6b7c5c6baf7d5ec772f" + caller-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"