<commit_before>import React from 'react';
import { shallow } from 'enzyme';
import { MetricsPlotPanel, CHART_TYPE_BAR, CHART_TYPE_LINE } from './MetricsPlotPanel';
import { X_AXIS_RELATIVE, X_AXIS_STEP, X_AXIS_WALL } from './MetricsPlotControls';
import Utils from '../utils/Utils';

describe('unit tests', () => {
  let wrapper;
  let instance;
  let minimalPropsForLineChart;
  let minimalPropsForBarChart;

  beforeEach(() => {
    const location = {
      search: '?runs=["runUuid1","runUuid2"]&experiment=0' +
        '&plot_metric_keys=["metric_1","metric_2"]&plot_layout={}',
    };
    const history = {
      push: (url) => {
        location.search = "?" + url.split("?")[1];
      },
    };
    minimalPropsForLineChart = {
      experimentId: 1,
      runUuids: ['runUuid1', 'runUuid2'],
      metricKey: 'metric_1',
      latestMetricsByRunUuid: {
        runUuid1: { metric_1: 100, metric_2: 200 },
        runUuid2: { metric_1: 111, metric_2: 222 },
      },
      distinctMetricKeys: ['metric_1', 'metric_2'],
      // An array of { metricKey, history, runUuid, runDisplayName }
      metricsWithRunInfoAndHistory: [
        {
          metricKey: 'metric_1',
          history: [
            /* Intentionally reversed timestamp and step here for testing */
            { key: 'metric_1', value: 100, step: 2, timestamp: 1556662044000 },
            { key: 'metric_1', value: 50, step: 1, timestamp: 1556662043000 },
          ],
          runUuid: 'runUuid1',
          runDisplayName: 'runDisplayName1',
        },
        {
          metricKey: 'metric_2',
          history: [
            { key: 'metric_2', value: 55, step: -1, timestamp: 1556662043000 },
            { key: 'metric_2', value: 111, step: 0, timestamp: 1556662044000 },
          ],
          runUuid: 'runUuid1',
          runDisplayName: 'runDisplayName1',
        },
        {
          metricKey: 'metric_1',
          history: [
            { key: 'metric_1', value: 150, step: 3, timestamp: 1556662043000 },
            { key: 'metric_1', value: 200, step: 4, timestamp: 1556662044000 },
          ],
          runUuid: 'runUuid2',
          runDisplayName: 'runDisplayName2',
        },
        {
          metricKey: 'metric_2',
          history: [
            { key: 'metric_2', value: 155, step: -4, timestamp: 1556662043000 },
            { key: 'metric_2', value: 222, step: -3, timestamp: 1556662044000 },
          ],
          runUuid: 'runUuid2',
          runDisplayName: 'runDisplayName2',
        },
      ],
      getMetricHistoryApi: jest.fn(),
      location: location,
      history: history,
      runDisplayNames: ['runDisplayName1', 'runDisplayName2'],
    };

    minimalPropsForBarChart = {
      experimentId: 1,
      runUuids: ['runUuid1', 'runUuid2'],
      metricKey: 'metric_1',
      latestMetricsByRunUuid: {
        runUuid1: { metric_1: 100, metric_2: 200 },
        runUuid2: { metric_1: 111, metric_2: 222 },
      },
      distinctMetricKeys: ['metric_1', 'metric_2'],
      // An array of { metricKey, history, runUuid, runDisplayName }
      metricsWithRunInfoAndHistory: [
        {
          metricKey: 'metric_1',
          history: [{ key: 'metric_1', value: 50, step: 0, timestamp: 1556662043000 }],
          runUuid: 'runUuid1',
          runDisplayName: 'runDisplayName1',
        },
        {
          metricKey: 'metric_2',
          history: [{ key: 'metric_2', value: 55, step: 0, timestamp: 1556662043000 }],
          runUuid: 'runUuid1',
          runDisplayName: 'runDisplayName1',
        },
        {
          metricKey: 'metric_1',
          history: [{ key: 'metric_1', value: 150, step: 0, timestamp: 1556662043000 }],
          runUuid: 'runUuid2',
          runDisplayName: 'runDisplayName2',
        },
        {
          metricKey: 'metric_2',
          history: [{ key: 'metric_2', value: 155, step: 0, timestamp: 1556662043000 }],
          runUuid: 'runUuid2',
          runDisplayName: 'runDisplayName2',
        },
      ],
      getMetricHistoryApi: jest.fn(),
      location: location,
      history: history,
      runDisplayNames: ['runDisplayName1', 'runDisplayName2'],
      deselectedCurves: [],
    };
  });

  test('should render with minimal props without exploding', () => {
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForLineChart} />);
    expect(wrapper.length).toBe(1);
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForBarChart} />);
    expect(wrapper.length).toBe(1);
  });

  test('predictChartType()', () => {
    expect(
      MetricsPlotPanel.predictChartType(minimalPropsForLineChart.metricsWithRunInfoAndHistory),
    ).toBe(CHART_TYPE_LINE);
    expect(
      MetricsPlotPanel.predictChartType(minimalPropsForBarChart.metricsWithRunInfoAndHistory),
    ).toBe(CHART_TYPE_BAR);
  });

  test('isComparing()', () => {
    const s1 = '?runs=["runUuid1","runUuid2"]&plot_metric_keys=["metric_1","metric_2"]';
    const s2 = '?runs=["runUuid1"]&plot_metric_keys=["metric_1","metric_2"]';
    expect(MetricsPlotPanel.isComparing(s1)).toBe(true);
    expect(MetricsPlotPanel.isComparing(s2)).toBe(false);
  });

  test('getMetrics() should sort the history by timestamp for `Time (Relative)` x-axis', () => {
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForLineChart} />);
    instance = wrapper.instance();

    instance.setState({ selectedXAxis: X_AXIS_RELATIVE });
    const metrics = minimalPropsForLineChart.metricsWithRunInfoAndHistory;
    metrics[0].history.sort(); // sort in place before comparison
    expect(instance.getMetrics()).toEqual(metrics);
  });

  test('getMetrics() should sort the history by timestamp for `Time (Wall)` x-axis', () => {
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForLineChart} />);
    instance = wrapper.instance();

    instance.setState({ selectedXAxis: X_AXIS_WALL });
    const metrics = minimalPropsForLineChart.metricsWithRunInfoAndHistory;
    metrics[0].history.sort(); // sort in place before comparison
    expect(instance.getMetrics()).toEqual(metrics);
  });

  test('getMetrics() should sort the history by step&timestamp for `Step` x-axis', () => {
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForLineChart} />);
    instance = wrapper.instance();

    instance.setState({ selectedXAxis: X_AXIS_STEP });
    const metrics = minimalPropsForLineChart.metricsWithRunInfoAndHistory;
    metrics[0].history.sort(Utils.compareByStepAndTimestamp); // sort in place before comparison
    expect(instance.getMetrics()).toEqual(metrics);
  });

  test('handleYAxisLogScale properly converts layout between log and linear scales', () => {
    const props = {
      ...minimalPropsForLineChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    // Test converting to & from log scale for an empty layout (e.g. a layout without any
    // user-specified zoom)
    instance.handleYAxisLogScaleChange(true);
    expect(instance.getUrlState().layout).toEqual({yaxis: {type: "log", autorange: true}});
    instance.handleYAxisLogScaleChange(false);
    expect(instance.getUrlState().layout).toEqual({yaxis: {type: "linear", autorange: true}});
    // Test converting to & from log scale for a layout with specified y axis range bounds
    instance.handleLayoutChange({"xaxis.range[0]": 2, "xaxis.range[1]": 4, "yaxis.range[0]": 1, "yaxis.range[1]": 100});
    instance.handleYAxisLogScaleChange(true);
    expect(instance.getUrlState().layout).toEqual(
      {xaxis: {range: [2, 4], autorange: false}, yaxis: {range: [0, 2], type: "log"}});
    instance.handleYAxisLogScaleChange(false);
    expect(instance.getUrlState().layout).toEqual(
        {xaxis: {range: [2, 4], autorange: false}, yaxis: {range: [1, 100], type: "linear"}});
    // Test converting to & from log scale for a layout with negative Y axis
    instance.handleLayoutChange({"xaxis.range[0]": -5, "xaxis.range[1]": 5, "yaxis.range[0]": -3, "yaxis.range[1]": 6});
    instance.handleYAxisLogScaleChange(true);
    expect(instance.getUrlState().layout).toEqual({xaxis: {range: [-5, 5], autorange: false},
      yaxis: {autorange: true, type: "log"}});
    instance.handleYAxisLogScaleChange(false);
    expect(instance.getUrlState().layout).toEqual(
        {xaxis: {range: [-5, 5], autorange: false}, yaxis: {range: [-3, 6], type: "linear"}});
    // Test converting to & from log scale for a layout with zero-valued Y axis bound
    instance.handleLayoutChange({"yaxis.range[0]": 0, "yaxis.range[1]": 6});
    instance.handleYAxisLogScaleChange(true);
    expect(instance.getUrlState().layout).toEqual({xaxis: {range: [-5, 5], autorange: false},
      yaxis: {autorange: true, type: "log"}});
    instance.handleYAxisLogScaleChange(false);
    expect(instance.getUrlState().layout).toEqual(
        {xaxis: {range: [-5, 5], autorange: false}, yaxis: {range: [0, 6], type: "linear"}});
  });

  test('single-click handler in metric comparison plot - line chart', (done) => {
    const props = {
      ...minimalPropsForLineChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    // Verify that clicking doesn't immediately update the run state
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    instance.handleLegendClick({curveNumber: 0, data: [{runId: 'runUuid2', metricName: 'metric_1'}]});
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    // Wait a second, verify first run was deselected
    window.setTimeout(() => {
      expect(instance.getUrlState().deselectedCurves).toEqual(['runUuid2-metric_1']);
      done();
    }, 1000);
  });

  test('single-click handler in metric comparison plot - bar chart', (done) => {
    const props = {
      ...minimalPropsForBarChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    // Verify that clicking doesn't immediately update the run state
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    instance.handleLegendClick({curveNumber: 0, data: [{runId: 'runUuid2', type: 'bar'}]});
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    // Wait a second, verify first run was deselected
    window.setTimeout(() => {
      expect(instance.getUrlState().deselectedCurves).toEqual(['runUuid2']);
      done();
    }, 1000);
  });

  test('double-click handler in metric comparison plot - line chart', (done) => {
    const props = {
      ...minimalPropsForLineChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    const data = [
      {runId: 'runUuid1', metricName: 'metric_1'},
      {runId: 'runUuid2', metricName: 'metric_2'},
    ];
    // Verify that clicking doesn't immediately update the run state
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    instance.handleLegendClick({curveNumber: 1, data: data});
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    // Double-click, verify that only the clicked run is selected (that the other one is deselected)
    instance.handleLegendClick({curveNumber: 1, data: data});
    window.setTimeout(() => {
      expect(instance.getUrlState().deselectedCurves).toEqual(['runUuid1-metric_1']);
      done();
    }, 1000);
  });

  test('double-click handler in metric comparison plot - bar chart', (done) => {
    const props = {
      ...minimalPropsForBarChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    const data = [
      {runId: 'runUuid1', type: 'bar'},
      {runId: 'runUuid2', type: 'bar'},
    ];
    // Verify that clicking doesn't immediately update the run state
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    instance.handleLegendClick({curveNumber: 1, data: data});
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    // Double-click, verify that only the clicked run is selected (that the other one is deselected)
    instance.handleLegendClick({curveNumber: 1, data: data});
    window.setTimeout(() => {
      expect(instance.getUrlState().deselectedCurves).toEqual(['runUuid1']);
      done();
    }, 1000);
  });
});
<commit_msg>Fix test<commit_after>import React from 'react';
import { shallow } from 'enzyme';
import { MetricsPlotPanel, CHART_TYPE_BAR, CHART_TYPE_LINE } from './MetricsPlotPanel';
import { X_AXIS_RELATIVE, X_AXIS_STEP, X_AXIS_WALL } from './MetricsPlotControls';
import Utils from '../utils/Utils';

describe('unit tests', () => {
  let wrapper;
  let instance;
  let minimalPropsForLineChart;
  let minimalPropsForBarChart;

  beforeEach(() => {
    const location = {
      search: '?runs=["runUuid1","runUuid2"]&experiment=0' +
        '&plot_metric_keys=["metric_1","metric_2"]&plot_layout={}',
    };
    const history = {
      push: (url) => {
        location.search = "?" + url.split("?")[1];
      },
      replace: (url) => {
        location.search = "?" + url.split("?")[1];
      },
    };
    minimalPropsForLineChart = {
      experimentId: 1,
      runUuids: ['runUuid1', 'runUuid2'],
      metricKey: 'metric_1',
      latestMetricsByRunUuid: {
        runUuid1: { metric_1: 100, metric_2: 200 },
        runUuid2: { metric_1: 111, metric_2: 222 },
      },
      distinctMetricKeys: ['metric_1', 'metric_2'],
      // An array of { metricKey, history, runUuid, runDisplayName }
      metricsWithRunInfoAndHistory: [
        {
          metricKey: 'metric_1',
          history: [
            /* Intentionally reversed timestamp and step here for testing */
            { key: 'metric_1', value: 100, step: 2, timestamp: 1556662044000 },
            { key: 'metric_1', value: 50, step: 1, timestamp: 1556662043000 },
          ],
          runUuid: 'runUuid1',
          runDisplayName: 'runDisplayName1',
        },
        {
          metricKey: 'metric_2',
          history: [
            { key: 'metric_2', value: 55, step: -1, timestamp: 1556662043000 },
            { key: 'metric_2', value: 111, step: 0, timestamp: 1556662044000 },
          ],
          runUuid: 'runUuid1',
          runDisplayName: 'runDisplayName1',
        },
        {
          metricKey: 'metric_1',
          history: [
            { key: 'metric_1', value: 150, step: 3, timestamp: 1556662043000 },
            { key: 'metric_1', value: 200, step: 4, timestamp: 1556662044000 },
          ],
          runUuid: 'runUuid2',
          runDisplayName: 'runDisplayName2',
        },
        {
          metricKey: 'metric_2',
          history: [
            { key: 'metric_2', value: 155, step: -4, timestamp: 1556662043000 },
            { key: 'metric_2', value: 222, step: -3, timestamp: 1556662044000 },
          ],
          runUuid: 'runUuid2',
          runDisplayName: 'runDisplayName2',
        },
      ],
      getMetricHistoryApi: jest.fn(),
      location: location,
      history: history,
      runDisplayNames: ['runDisplayName1', 'runDisplayName2'],
    };

    minimalPropsForBarChart = {
      experimentId: 1,
      runUuids: ['runUuid1', 'runUuid2'],
      metricKey: 'metric_1',
      latestMetricsByRunUuid: {
        runUuid1: { metric_1: 100, metric_2: 200 },
        runUuid2: { metric_1: 111, metric_2: 222 },
      },
      distinctMetricKeys: ['metric_1', 'metric_2'],
      // An array of { metricKey, history, runUuid, runDisplayName }
      metricsWithRunInfoAndHistory: [
        {
          metricKey: 'metric_1',
          history: [{ key: 'metric_1', value: 50, step: 0, timestamp: 1556662043000 }],
          runUuid: 'runUuid1',
          runDisplayName: 'runDisplayName1',
        },
        {
          metricKey: 'metric_2',
          history: [{ key: 'metric_2', value: 55, step: 0, timestamp: 1556662043000 }],
          runUuid: 'runUuid1',
          runDisplayName: 'runDisplayName1',
        },
        {
          metricKey: 'metric_1',
          history: [{ key: 'metric_1', value: 150, step: 0, timestamp: 1556662043000 }],
          runUuid: 'runUuid2',
          runDisplayName: 'runDisplayName2',
        },
        {
          metricKey: 'metric_2',
          history: [{ key: 'metric_2', value: 155, step: 0, timestamp: 1556662043000 }],
          runUuid: 'runUuid2',
          runDisplayName: 'runDisplayName2',
        },
      ],
      getMetricHistoryApi: jest.fn(),
      location: location,
      history: history,
      runDisplayNames: ['runDisplayName1', 'runDisplayName2'],
      deselectedCurves: [],
    };
  });

  test('should render with minimal props without exploding', () => {
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForLineChart} />);
    expect(wrapper.length).toBe(1);
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForBarChart} />);
    expect(wrapper.length).toBe(1);
  });

  test('predictChartType()', () => {
    expect(
      MetricsPlotPanel.predictChartType(minimalPropsForLineChart.metricsWithRunInfoAndHistory),
    ).toBe(CHART_TYPE_LINE);
    expect(
      MetricsPlotPanel.predictChartType(minimalPropsForBarChart.metricsWithRunInfoAndHistory),
    ).toBe(CHART_TYPE_BAR);
  });

  test('isComparing()', () => {
    const s1 = '?runs=["runUuid1","runUuid2"]&plot_metric_keys=["metric_1","metric_2"]';
    const s2 = '?runs=["runUuid1"]&plot_metric_keys=["metric_1","metric_2"]';
    expect(MetricsPlotPanel.isComparing(s1)).toBe(true);
    expect(MetricsPlotPanel.isComparing(s2)).toBe(false);
  });

  test('getMetrics() should sort the history by timestamp for `Time (Relative)` x-axis', () => {
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForLineChart} />);
    instance = wrapper.instance();

    instance.setState({ selectedXAxis: X_AXIS_RELATIVE });
    const metrics = minimalPropsForLineChart.metricsWithRunInfoAndHistory;
    metrics[0].history.sort(); // sort in place before comparison
    expect(instance.getMetrics()).toEqual(metrics);
  });

  test('getMetrics() should sort the history by timestamp for `Time (Wall)` x-axis', () => {
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForLineChart} />);
    instance = wrapper.instance();

    instance.setState({ selectedXAxis: X_AXIS_WALL });
    const metrics = minimalPropsForLineChart.metricsWithRunInfoAndHistory;
    metrics[0].history.sort(); // sort in place before comparison
    expect(instance.getMetrics()).toEqual(metrics);
  });

  test('getMetrics() should sort the history by step&timestamp for `Step` x-axis', () => {
    wrapper = shallow(<MetricsPlotPanel {...minimalPropsForLineChart} />);
    instance = wrapper.instance();

    instance.setState({ selectedXAxis: X_AXIS_STEP });
    const metrics = minimalPropsForLineChart.metricsWithRunInfoAndHistory;
    metrics[0].history.sort(Utils.compareByStepAndTimestamp); // sort in place before comparison
    expect(instance.getMetrics()).toEqual(metrics);
  });

  test('handleYAxisLogScale properly converts layout between log and linear scales', () => {
    const props = {
      ...minimalPropsForLineChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    // Test converting to & from log scale for an empty layout (e.g. a layout without any
    // user-specified zoom)
    instance.handleYAxisLogScaleChange(true);
    expect(instance.getUrlState().layout).toEqual({yaxis: {type: "log", autorange: true}});
    instance.handleYAxisLogScaleChange(false);
    expect(instance.getUrlState().layout).toEqual({yaxis: {type: "linear", autorange: true}});
    // Test converting to & from log scale for a layout with specified y axis range bounds
    instance.handleLayoutChange({"xaxis.range[0]": 2, "xaxis.range[1]": 4, "yaxis.range[0]": 1, "yaxis.range[1]": 100});
    instance.handleYAxisLogScaleChange(true);
    expect(instance.getUrlState().layout).toEqual(
      {xaxis: {range: [2, 4], autorange: false}, yaxis: {range: [0, 2], type: "log"}});
    instance.handleYAxisLogScaleChange(false);
    expect(instance.getUrlState().layout).toEqual(
        {xaxis: {range: [2, 4], autorange: false}, yaxis: {range: [1, 100], type: "linear"}});
    // Test converting to & from log scale for a layout with negative Y axis
    instance.handleLayoutChange({"xaxis.range[0]": -5, "xaxis.range[1]": 5, "yaxis.range[0]": -3, "yaxis.range[1]": 6});
    instance.handleYAxisLogScaleChange(true);
    expect(instance.getUrlState().layout).toEqual({xaxis: {range: [-5, 5], autorange: false},
      yaxis: {autorange: true, type: "log"}});
    instance.handleYAxisLogScaleChange(false);
    expect(instance.getUrlState().layout).toEqual(
        {xaxis: {range: [-5, 5], autorange: false}, yaxis: {range: [-3, 6], type: "linear"}});
    // Test converting to & from log scale for a layout with zero-valued Y axis bound
    instance.handleLayoutChange({"yaxis.range[0]": 0, "yaxis.range[1]": 6});
    instance.handleYAxisLogScaleChange(true);
    expect(instance.getUrlState().layout).toEqual({xaxis: {range: [-5, 5], autorange: false},
      yaxis: {autorange: true, type: "log"}});
    instance.handleYAxisLogScaleChange(false);
    expect(instance.getUrlState().layout).toEqual(
        {xaxis: {range: [-5, 5], autorange: false}, yaxis: {range: [0, 6], type: "linear"}});
  });

  test('single-click handler in metric comparison plot - line chart', (done) => {
    const props = {
      ...minimalPropsForLineChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    // Verify that clicking doesn't immediately update the run state
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    instance.handleLegendClick({curveNumber: 0, data: [{runId: 'runUuid2', metricName: 'metric_1'}]});
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    // Wait a second, verify first run was deselected
    window.setTimeout(() => {
      expect(instance.getUrlState().deselectedCurves).toEqual(['runUuid2-metric_1']);
      done();
    }, 1000);
  });

  test('single-click handler in metric comparison plot - bar chart', (done) => {
    const props = {
      ...minimalPropsForBarChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    // Verify that clicking doesn't immediately update the run state
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    instance.handleLegendClick({curveNumber: 0, data: [{runId: 'runUuid2', type: 'bar'}]});
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    // Wait a second, verify first run was deselected
    window.setTimeout(() => {
      expect(instance.getUrlState().deselectedCurves).toEqual(['runUuid2']);
      done();
    }, 1000);
  });

  test('double-click handler in metric comparison plot - line chart', (done) => {
    const props = {
      ...minimalPropsForLineChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    const data = [
      {runId: 'runUuid1', metricName: 'metric_1'},
      {runId: 'runUuid2', metricName: 'metric_2'},
    ];
    // Verify that clicking doesn't immediately update the run state
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    instance.handleLegendClick({curveNumber: 1, data: data});
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    // Double-click, verify that only the clicked run is selected (that the other one is deselected)
    instance.handleLegendClick({curveNumber: 1, data: data});
    window.setTimeout(() => {
      expect(instance.getUrlState().deselectedCurves).toEqual(['runUuid1-metric_1']);
      done();
    }, 1000);
  });

  test('double-click handler in metric comparison plot - bar chart', (done) => {
    const props = {
      ...minimalPropsForBarChart,
    };
    wrapper = shallow(<MetricsPlotPanel {...props} />);
    instance = wrapper.instance();
    const data = [
      {runId: 'runUuid1', type: 'bar'},
      {runId: 'runUuid2', type: 'bar'},
    ];
    // Verify that clicking doesn't immediately update the run state
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    instance.handleLegendClick({curveNumber: 1, data: data});
    expect(instance.getUrlState().deselectedCurves).toEqual([]);
    // Double-click, verify that only the clicked run is selected (that the other one is deselected)
    instance.handleLegendClick({curveNumber: 1, data: data});
    window.setTimeout(() => {
      expect(instance.getUrlState().deselectedCurves).toEqual(['runUuid1']);
      done();
    }, 1000);
  });
});
<|endoftext|>


<commit_before>"use strict";

app.factory("Countdown", function($rootScope, Socket, FBCountdowns){
    var LOCAL_STORAGE_ID = "mfccCountdown",
        countDownString = localStorage[LOCAL_STORAGE_ID];

    var emptyCountdown = {
        model: undefined,
        tips:[],
        amt: 0,
        complete: true
    };

    var countDown = countDownString ? JSON.parse(countDownString) : emptyCountdown;

    $rootScope.$watch(function(){ return countDown;}, function(){
        localStorage[LOCAL_STORAGE_ID] = JSON.stringify(countDown);
    }, true);

    var tipRegex = /(\w*) has tipped (\w*) (\d*) tokens?(.*)/;

    //start up the countdown listener if there is an active
    //countdown that isn't complete
    if (undefined != countDown.complete && !countDown.complete)
        listenForTips();

    function currentTotal(){
        return countDown.tips.reduce(function(total, tip){
            return total - tip.Amount;
        }, countDown.amt);
    }

    function completeCountdown() {
        countDown.complete = true;
        Socket.removeListener("message", parseTips);
    }

    function cancelCountdown(){
        countDown = emptyCountdown;
        Socket.removeListener("message", parseTips);
    }

    function parseTips(msg){
        if (msg.Type !== MFCMessageType.FCTYPE_TOKENINC)
            return;

        if (!msg.Data)
            return;

        //tips messages do not come from anybody
//        if (msg.Arg1 != 0 || msg.Arg2 != 0 || msg.From != 0 || msg.To != countDown.model.broadcasterId)
//            return;

//        var tipMsg = msg.Data.msg;
//
//        var match = tipRegex.exec(tipMsg);
//        if (match){
//            var tipNote = "";
//            if (match.length == 5 && match[4] != ".")
//                tipNote = match[4].substring(3, match[4].length - 1);

            var tip = {
                Name: msg.Data.u[2],
                Amount: msg.Data.tokens,
                TipNote: msg.Data.msg,
                When: new Date()
            };

            $rootScope.$apply(function(){
                countDown.tips.push(tip);
                console.log(tip);

            });

            if (currentTotal() < 0)
                completeCountdown();
        //}
    }

    function listenForTips(){
        if (!Socket.loggedIn()){
            setTimeout(listenForTips, 500);
            return;
        }

        Socket.listen("message", parseTips);
        Socket.send(new MFCMessage({
            Type: MFCMessageType.FCTYPE_JOINCHAN,
            From: Socket.getSessionId(),
            To: 0,
            Arg1: countDown.model.broadcasterId,
            Arg2: MFCChatOpt.FCCHAN_JOIN
        }));
    }

    function startCountdown(model, amount){
        countDown = {
            amt: amount,
            model: model,
            tips: [],
            complete: false
        };

        FBCountdowns.$add(countDown);

        listenForTips();
    }

    function removeTip(tip){
        countDown.tips.splice(countDown.tips.indexOf(tip), 1);
        if (currentTotal() < countDown.amt)
            countDown.complete = false;
    }

    return {
        start: startCountdown,
        cancel: cancelCountdown,
        resume: listenForTips,
        active: function(){
            return !countDown.complete;
        },
        current: currentTotal,
        tips: function(){
            return countDown.tips;
        },
        amount: function(){
            return countDown.amt;
        },
        removeTip: removeTip
    }

});<commit_msg>Update firebase with current total/completion<commit_after>"use strict";

app.factory("Countdown", function($rootScope, Socket, FBCountdowns){
    var LOCAL_STORAGE_ID = "mfccCountdown",
        countDownString = localStorage[LOCAL_STORAGE_ID];

    var emptyCountdown = {
        model: undefined,
        tips:[],
        amt: 0,
        complete: true
    };

    var countDown = countDownString ? JSON.parse(countDownString) : emptyCountdown;

    $rootScope.$watch(function(){ return countDown;}, function(){
        localStorage[LOCAL_STORAGE_ID] = JSON.stringify(countDown);
    }, true);

    var tipRegex = /(\w*) has tipped (\w*) (\d*) tokens?(.*)/;

    //start up the countdown listener if there is an active
    //countdown that isn't complete
    if (undefined != countDown.complete && !countDown.complete)
        listenForTips();

    function currentTotal(){
        return countDown.tips.reduce(function(total, tip){
            return total - tip.Amount;
        }, countDown.amt);
    }

    function completeCountdown() {
        countDown.complete = true;
        FBCountdowns.$child(countDown.fb_id).$update({complete: true});
        Socket.removeListener("message", parseTips);
    }

    function cancelCountdown(){
        countDown = emptyCountdown;
        Socket.removeListener("message", parseTips);
    }

    function parseTips(msg){
        if (msg.Type !== MFCMessageType.FCTYPE_TOKENINC)
            return;

        if (!msg.Data)
            return;

        //make sure the tip is for the currently selected model
        if (msg.Data.ch != countDown.model.broadcasterId)
            return;

        //tips messages do not come from anybody
//        if (msg.Arg1 != 0 || msg.Arg2 != 0 || msg.From != 0 || msg.To != countDown.model.broadcasterId)
//            return;

//        var tipMsg = msg.Data.msg;
//
//        var match = tipRegex.exec(tipMsg);
//        if (match){
//            var tipNote = "";
//            if (match.length == 5 && match[4] != ".")
//                tipNote = match[4].substring(3, match[4].length - 1);

            var tip = {
                Name: msg.Data.u[2],
                Amount: msg.Data.tokens,
                TipNote: msg.Data.msg,
                When: new Date()
            };

            $rootScope.$apply(function(){
                countDown.tips.push(tip);
            });

            FBCountdowns.$child(countDown.fb_id).$update({currentTotal: currentTotal()});

            if (currentTotal() < 0)
                completeCountdown();
        //}
    }

    function listenForTips(){
        if (!Socket.loggedIn()){
            setTimeout(listenForTips, 500);
            return;
        }

        Socket.listen("message", parseTips);
        Socket.send(new MFCMessage({
            Type: MFCMessageType.FCTYPE_JOINCHAN,
            From: Socket.getSessionId(),
            To: 0,
            Arg1: countDown.model.broadcasterId,
            Arg2: MFCChatOpt.FCCHAN_JOIN
        }));
    }

    function startCountdown(model, amount){
        countDown = {
            amt: amount,
            model: model,
            tips: [],
            complete: false
        };

        FBCountdowns.$add(countDown).then(function(ref){
            countDown.fb_id = ref.name();
        });

        listenForTips();
    }

    function removeTip(tip){
        countDown.tips.splice(countDown.tips.indexOf(tip), 1);
        if (currentTotal() < countDown.amt)
            countDown.complete = false;
    }

    return {
        start: startCountdown,
        cancel: cancelCountdown,
        resume: listenForTips,
        active: function(){
            return !countDown.complete;
        },
        current: currentTotal,
        tips: function(){
            return countDown.tips;
        },
        amount: function(){
            return countDown.amt;
        },
        removeTip: removeTip
    }

});<|endoftext|>


<commit_before>// @flow
const log = require('mwp-logger-plugin').logger;
const avro = require('avsc');

/*
 * There are currently 2 distinct analytics logging methods
 * 1. `stdout`, which will be consumed automatically from apps running in
 *    Google Container Engine
 * 2. Google Pub/Sub, which is used from inside Google App Engine
 *
 * The Pub/Sub setup _only_ works when the environment is configured to
 * automatically authorize Pub/Sub messages, so we use an `isGAE` env variable
 * to determine whether the app is running in GAE.
 *
 * @see https://meetup.atlassian.net/wiki/display/MUP/Analytics+Logging#AnalyticsLogging-Theinputmechanism
 */
const getPlatformAnalyticsLog = (
	isGAE: ?string = process.env.GAE_INSTANCE
): (string => void) => {
	if (isGAE) {
		const pubsub = require('@google-cloud/pubsub')({
			projectId: 'meetup-prod',
		});
		const publisher = pubsub.topic('analytics-log-json').publisher();
		return (serializedRecord: string) => {
			publisher
				.publish(new Buffer(serializedRecord))
				.catch(err => log.error(err));
		};
	}
	return (serializedRecord: string) => {
		process.stdout.write(`analytics=${serializedRecord}\n`);
	};
};

const analyticsLog = getPlatformAnalyticsLog();
const debugLog = deserializedRecord =>
	console.log(JSON.stringify(deserializedRecord));

// currently the schema is manually copied from
// https://github.dev.meetup.com/meetup/meetup/blob/master/modules/base/src/main/versioned_avro/Click_v2.avsc
const click = {
	namespace: 'com.meetup.base.avro',
	type: 'record',
	name: 'Click',
	doc: 'v2',
	fields: [
		{ name: 'timestamp', type: 'string' },
		{ name: 'requestId', type: 'string' },
		{ name: 'memberId', type: 'int' },
		{ name: 'lineage', type: 'string' },
		{ name: 'linkText', type: 'string' },
		{ name: 'coordX', type: 'int' },
		{ name: 'coordY', type: 'int' },
		{ name: 'tag', type: 'string', default: '' },
	],
};

// currently the schema is manually copied from
// https://github.com/meetup/meetup/blob/master/modules/base/src/main/versioned_avro/Activity_v8.avsc
const activity = {
	namespace: 'com.meetup.base.avro',
	type: 'record',
	name: 'Activity',
	doc: 'v8',
	fields: [
		{ name: 'requestId', type: 'string' },
		{ name: 'timestamp', type: 'string' },
		{ name: 'url', type: 'string' },
		{ name: 'aggregratedUrl', type: 'string', default: '' }, // it's misspelled in the original spec
		{ name: 'ip', type: 'string', default: '' },
		{ name: 'agent', type: 'string', default: '' },
		{ name: 'memberId', type: 'int' },
		{ name: 'trackId', type: 'string' },
		{ name: 'mobileWeb', type: 'boolean' },
		{ name: 'platform', type: 'string' },
		{ name: 'referer', type: 'string' }, // it's misspelled in the original spec
		{ name: 'trax', type: { type: 'map', values: 'string' } },
		{
			name: 'platformAgent',
			type: {
				type: 'enum',
				name: 'PlatformAgent',
				symbols: [
					'WEB',
					'MUP_WEB',
					'PRO_WEB',
					'NATIVE',
					'NATIVE_APP_WEB_VIEW',
					'THIRD_PARTY_UNKNOWN',
					'UNKNOWN',
				],
			},
			default: 'UNKNOWN',
		},
		{ name: 'isUserActivity', type: 'boolean', default: true },
		{ name: 'browserId', type: 'string', default: '' },
		{ name: 'parentRequestId', type: ['null', 'string'], default: null },
		{ name: 'oauthConsumerId', type: ['null', 'int'], default: null },
		{ name: 'apiVersion', type: ['null', 'string'], default: null },
		{ name: 'viewName', type: ['null', 'string'], default: null },
		{ name: 'subViewName', type: ['null', 'string'], default: null },
	],
};

type Serializer = Object => string;
type Deserializer = string => Object;

const avroSerializer: Object => Serializer = schema => {
	const codec = avro.parse(schema);
	const schemaPath = `gs://meetup-logs/avro_schemas/${schema.name}_${schema.doc}.avsc`;
	return data => {
		const record = codec.toBuffer(data);
		// data.timestamp _must_ be ISOString if it exists
		const timestamp = data.timestamp || new Date().toISOString();
		const analytics = {
			record: record.toString('base64'),
			schema: schemaPath,
			date: timestamp.substr(0, 10), // YYYY-MM-DD
		};
		return JSON.stringify(analytics);
	};
};

const avroDeserializer: Object => Deserializer = schema => {
	const codec = avro.parse(schema);
	return serialized => {
		const { record } = JSON.parse(serialized);
		const avroBuffer = new Buffer(record, 'base64');
		return codec.fromBuffer(avroBuffer);
	};
};

const logger = (serializer: Serializer, deserializer: Deserializer) => (
	record: Object
) => {
	const serializedRecord = serializer(record);
	const deserializedRecord = deserializer(serializedRecord);
	analyticsLog(serializedRecord);
	if (process.argv.includes('--debug')) {
		debugLog(deserializedRecord);
	}
};

const schemas = {
	activity,
	click,
};
const serializers = {
	avro: avroSerializer,
	activity: avroSerializer(schemas.activity),
	click: avroSerializer(schemas.click),
};
const deserializers = {
	avro: avroDeserializer,
	activity: avroDeserializer(schemas.activity),
	click: avroDeserializer(schemas.click),
};
const loggers = {
	activity: logger(serializers.activity, deserializers.activity),
	click: logger(serializers.click, deserializers.click),
};

module.exports = {
	avroSerializer,
	getPlatformAnalyticsLog,
	schemas,
	serializers,
	deserializers,
	loggers,
};
<commit_msg>enable pubsub with GOOGLE_APPLICATION_CREDENTIALS<commit_after>// @flow
const log = require('mwp-logger-plugin').logger;
const avro = require('avsc');

const canUsePubSub =
	process.env.GAE_INSTANCE || process.env.GOOGLE_APPLICATION_CREDENTIALS;

/*
 * There are currently 2 distinct analytics logging methods
 * 1. `stdout`: used in dev and compatible with https://github.com/meetup/blt-fluentd
 *    in k8s-based applications in GCP
 * 2. Google Pub/Sub, which is used in GAE and any environment with GOOGLE_APPLICATION_CREDENTIALS
 *    environment variable set to point toward Google JSON client credentials file
 *
 * @see https://meetup.atlassian.net/wiki/display/MUP/Analytics+Logging#AnalyticsLogging-Theinputmechanism
 */
const getPlatformAnalyticsLog = (
	usePubSub: ?string = canUsePubSub
): (string => void) => {
	if (usePubSub) {
		const pubsub = require('@google-cloud/pubsub')({
			projectId: 'meetup-prod',
		});
		const publisher = pubsub.topic('analytics-log-json').publisher();
		return (serializedRecord: string) => {
			publisher
				.publish(new Buffer(serializedRecord))
				.catch(err => log.error(err));
		};
	}

	// stdout log - can be used with https://github.com/meetup/blt-fluentd or generic environment
	return (serializedRecord: string) => {
		process.stdout.write(`analytics=${serializedRecord}\n`);
	};
};

const analyticsLog = getPlatformAnalyticsLog();
const debugLog = deserializedRecord =>
	console.log(JSON.stringify(deserializedRecord));

// currently the schema is manually copied from
// https://github.dev.meetup.com/meetup/meetup/blob/master/modules/base/src/main/versioned_avro/Click_v2.avsc
const click = {
	namespace: 'com.meetup.base.avro',
	type: 'record',
	name: 'Click',
	doc: 'v2',
	fields: [
		{ name: 'timestamp', type: 'string' },
		{ name: 'requestId', type: 'string' },
		{ name: 'memberId', type: 'int' },
		{ name: 'lineage', type: 'string' },
		{ name: 'linkText', type: 'string' },
		{ name: 'coordX', type: 'int' },
		{ name: 'coordY', type: 'int' },
		{ name: 'tag', type: 'string', default: '' },
	],
};

// currently the schema is manually copied from
// https://github.com/meetup/meetup/blob/master/modules/base/src/main/versioned_avro/Activity_v8.avsc
const activity = {
	namespace: 'com.meetup.base.avro',
	type: 'record',
	name: 'Activity',
	doc: 'v8',
	fields: [
		{ name: 'requestId', type: 'string' },
		{ name: 'timestamp', type: 'string' },
		{ name: 'url', type: 'string' },
		{ name: 'aggregratedUrl', type: 'string', default: '' }, // it's misspelled in the original spec
		{ name: 'ip', type: 'string', default: '' },
		{ name: 'agent', type: 'string', default: '' },
		{ name: 'memberId', type: 'int' },
		{ name: 'trackId', type: 'string' },
		{ name: 'mobileWeb', type: 'boolean' },
		{ name: 'platform', type: 'string' },
		{ name: 'referer', type: 'string' }, // it's misspelled in the original spec
		{ name: 'trax', type: { type: 'map', values: 'string' } },
		{
			name: 'platformAgent',
			type: {
				type: 'enum',
				name: 'PlatformAgent',
				symbols: [
					'WEB',
					'MUP_WEB',
					'PRO_WEB',
					'NATIVE',
					'NATIVE_APP_WEB_VIEW',
					'THIRD_PARTY_UNKNOWN',
					'UNKNOWN',
				],
			},
			default: 'UNKNOWN',
		},
		{ name: 'isUserActivity', type: 'boolean', default: true },
		{ name: 'browserId', type: 'string', default: '' },
		{ name: 'parentRequestId', type: ['null', 'string'], default: null },
		{ name: 'oauthConsumerId', type: ['null', 'int'], default: null },
		{ name: 'apiVersion', type: ['null', 'string'], default: null },
		{ name: 'viewName', type: ['null', 'string'], default: null },
		{ name: 'subViewName', type: ['null', 'string'], default: null },
	],
};

type Serializer = Object => string;
type Deserializer = string => Object;

const avroSerializer: Object => Serializer = schema => {
	const codec = avro.parse(schema);
	const schemaPath = `gs://meetup-logs/avro_schemas/${schema.name}_${schema.doc}.avsc`;
	return data => {
		const record = codec.toBuffer(data);
		// data.timestamp _must_ be ISOString if it exists
		const timestamp = data.timestamp || new Date().toISOString();
		const analytics = {
			record: record.toString('base64'),
			schema: schemaPath,
			date: timestamp.substr(0, 10), // YYYY-MM-DD
		};
		return JSON.stringify(analytics);
	};
};

const avroDeserializer: Object => Deserializer = schema => {
	const codec = avro.parse(schema);
	return serialized => {
		const { record } = JSON.parse(serialized);
		const avroBuffer = new Buffer(record, 'base64');
		return codec.fromBuffer(avroBuffer);
	};
};

const logger = (serializer: Serializer, deserializer: Deserializer) => (
	record: Object
) => {
	const serializedRecord = serializer(record);
	const deserializedRecord = deserializer(serializedRecord);
	analyticsLog(serializedRecord);
	if (process.argv.includes('--debug')) {
		debugLog(deserializedRecord);
	}
};

const schemas = {
	activity,
	click,
};
const serializers = {
	avro: avroSerializer,
	activity: avroSerializer(schemas.activity),
	click: avroSerializer(schemas.click),
};
const deserializers = {
	avro: avroDeserializer,
	activity: avroDeserializer(schemas.activity),
	click: avroDeserializer(schemas.click),
};
const loggers = {
	activity: logger(serializers.activity, deserializers.activity),
	click: logger(serializers.click, deserializers.click),
};

module.exports = {
	avroSerializer,
	getPlatformAnalyticsLog,
	schemas,
	serializers,
	deserializers,
	loggers,
};
<|endoftext|>


<commit_before>'use strict';
//|| 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean'
// {
//     uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI,
//     options: {
//       user: '',
//       pass: ''
//     },
//     // Enable mongoose debug mode
//     debug: process.env.MONGODB_DEBUG || false
//   }
module.exports = {
  secure: {
    ssl: true,
    privateKey: './config/sslcerts/key.pem',
    certificate: './config/sslcerts/cert.pem'
  },
  port: process.env.PORT || 8443,
  db: ,
  log: {
    // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
    format: 'combined',
    // Stream defaults to process.stdout
    // Uncomment to enable logging to a log on the file system
    options: {
      stream: 'access.log'
    }
  },
  facebook: {
    clientID: process.env.FACEBOOK_ID || 'APP_ID',
    clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
    callbackURL: '/api/auth/facebook/callback'
  },
  twitter: {
    clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
    clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
    callbackURL: '/api/auth/twitter/callback'
  },
  google: {
    clientID: process.env.GOOGLE_ID || 'APP_ID',
    clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
    callbackURL: '/api/auth/google/callback'
  },
  linkedin: {
    clientID: process.env.LINKEDIN_ID || 'APP_ID',
    clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
    callbackURL: '/api/auth/linkedin/callback'
  },
  github: {
    clientID: process.env.GITHUB_ID || 'APP_ID',
    clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
    callbackURL: '/api/auth/github/callback'
  },
  paypal: {
    clientID: process.env.PAYPAL_ID || 'CLIENT_ID',
    clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET',
    callbackURL: '/api/auth/paypal/callback',
    sandbox: false
  },
  mailer: {
    from: process.env.MAILER_FROM || 'MAILER_FROM',
    options: {
      service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER',
      auth: {
        user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID',
        pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD'
      }
    }
  },
  seedDB: process.env.MONGO_SEED || false
};
<commit_msg>heroku-2<commit_after>'use strict';
//|| 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean'
module.exports = {
  secure: {
    ssl: true,
    privateKey: './config/sslcerts/key.pem',
    certificate: './config/sslcerts/cert.pem'
  },
  port: process.env.PORT || 8443,
  db: {
    uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI,
    options: {
      user: '',
      pass: ''
    },
    // Enable mongoose debug mode
    debug: process.env.MONGODB_DEBUG || false
  },
  log: {
    // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
    format: 'combined',
    // Stream defaults to process.stdout
    // Uncomment to enable logging to a log on the file system
    options: {
      stream: 'access.log'
    }
  },
  facebook: {
    clientID: process.env.FACEBOOK_ID || 'APP_ID',
    clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
    callbackURL: '/api/auth/facebook/callback'
  },
  twitter: {
    clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
    clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
    callbackURL: '/api/auth/twitter/callback'
  },
  google: {
    clientID: process.env.GOOGLE_ID || 'APP_ID',
    clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
    callbackURL: '/api/auth/google/callback'
  },
  linkedin: {
    clientID: process.env.LINKEDIN_ID || 'APP_ID',
    clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
    callbackURL: '/api/auth/linkedin/callback'
  },
  github: {
    clientID: process.env.GITHUB_ID || 'APP_ID',
    clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
    callbackURL: '/api/auth/github/callback'
  },
  paypal: {
    clientID: process.env.PAYPAL_ID || 'CLIENT_ID',
    clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET',
    callbackURL: '/api/auth/paypal/callback',
    sandbox: false
  },
  mailer: {
    from: process.env.MAILER_FROM || 'MAILER_FROM',
    options: {
      service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER',
      auth: {
        user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID',
        pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD'
      }
    }
  },
  seedDB: process.env.MONGO_SEED || false
};
<|endoftext|>


<commit_before>(function() {

  'use strict';

  /**
   * @ngdoc function
   * @name atacamaApp.controller:WidgetCtrl
   * @description
   * # WidgetCtrl
   * Controller of the atacamaApp
   */
  angular.module('atacamaApp')
      .controller('OhlcWidgetCtrl', OhlcWidgetController);

  function OhlcWidgetController(
    $scope, $uibModal, ngstomp, $resource, $log, Restangular,
    elasticsearchService, chartService, turbineService, utilService) {
      var vm = this;

      vm.isLoaded = false;
      vm.hasError = false;
      vm.selectedSymbol = "...";

      // var url = 'http://localhost:48002';
      var sod = moment(0, "HH").format("x");
      // var subscription;

      // TODO select market in UI
      var market = 'FTSE100';

      var topic = '';

      var esError = '';
      var stompError = '';

      // TODO centralise the rest call data more so widgets can share the results...
      // {"stocks":[{"market":"FTSE100","symbol":"ABC"},{"market":"FTSE100","symbol":"DEF"}]}
      // {"indicators":[{"overlay":true,"name":"BollingerBands"},{"overlay":false,"name":"SMA12"}]}
      // {"strategies":[{"name":"SMAStrategy"},{"name":"CCICorrectionStrategy"}]}

      reset();

      fetchStocks();

      // $scope.strategies = [
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'},
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'},
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'},
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'},
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'}
      // ];

      // set the size of the chart as the widget is resized...
      $scope.$on('gridster-item-resized', function(item) {
        $log.debug('gridster-item-resized');
        //vm.options.chart.height = firstHeight + ((item.targetScope.gridsterItem.sizeY - 1) * nextHeight);
        //vm.options.chart.width = firstWidth + ((item.targetScope.gridsterItem.sizeX - 1) * nextWidth);
        //console.log('offsetHeight:' + $scope.offsetParent.prop('offsetHeight'));
        //console.log('offsetWidth:' + $scope.offsetParent.prop('offsetWidth'));

        // TODO this is inconsistent... needs work!
        vm.options.chart.height = $scope.offsetParent.prop('offsetHeight');
        vm.options.chart.width = $scope.offsetParent.prop('offsetWidth')
        // TODO now causes an error... is this needed?
        // $scope.api.update();
      });

      function reset() {
        $log.debug('reset()');
        vm.isLoaded = false;
        vm.hasError = false;

        vm.options = {
            chart: {
                // TODO error message appears in console...
            }
        };

        vm.config = {
            deepWatchData: true,
            // deepWatchDataDepth: 1,
            refreshDataOnly: false,
            disabled: true
        };

        vm.data = [{
            key: vm.selectedSymbol,
            values: [{}]
        }];
      }

      function initialise() {
        vm.config = {
          deepWatchData: true,
          // deepWatchDataDepth: 1,
          refreshDataOnly: false,
          disabled: false
        };

        vm.options = {
            chart: {
                type: 'candlestickBarChart',
                // type: 'ohlcBarChart',
                height: $scope.offsetParent.prop('offsetHeight'),
                width: $scope.offsetParent.prop('offsetWidth'),
                margin: {
                    top: 20,
                    right: 40,
                    bottom: 40,
                    left: 40
                },
                x: function(d) {
                    return d['date'];
                },
                y: function(d) {
                    return d['close'];
                },
                showValues: true,
                transitionDuration: 500,
                xAxis: {
                    // axisLabel: 'Dates',
                    tickFormat: function(d) {
                        return d3.time.format('%X')(new Date(d));
                    },
                },
                yAxis: {
                    // axisLabel: 'Stock Price',
                    tickFormat: function(d, i) {
                        return '$' + d3.format(',.1f')(d);
                    }
                }
            }
        };
      }

      vm.selectSymbol = function(selectedSymbol) {
        vm.selectedSymbol = selectedSymbol;
        $log.log('select symbol: ', vm.selectedSymbol);
        addOHLC($scope.item);
      };

      // for smart-table...
      // $scope.addStrategies = function(widget) {
      //    console.log("widget.js::addStrategies");
      //    reset();
      //    $scope.typeStrategies = true;
      //    unsubscribeTopic();
      //  };

      // I don't think this is necessary anymore...
      // $scope.$on('$destroy', function() {
      //   utilService.unsubscribeTopic(topic);
      // });

      function fetchStocks() {
        turbineService.symbols(market).then(function(response) {
        // console.log(JSON.stringify(response.stocks));
        vm.symbols = _.pluck(response.stocks, 'symbol');
        // $scope.selectedSymbol = $scope.symbols[0];
        }, function (err) {
          esError = 'unable to load symbols: {0}'.format(err.message);
          console.trace(esError);
        });
      }

      function fetchHistoricDataFromES() {
        var promise = elasticsearchService.getTicksAfter(vm.selectedSymbol, sod);

        promise.then(function (response) {
          vm.data[0].values = elasticsearchService.parseResults(response);
        }, function (err) {
          esError = 'unable to load data: {0}:{1}'.format(vm.selectedSymbol, err.message);
          console.trace(esError);
        });
      }

      function subscribeToStompUpdates() {
        topic = '/topic/ticks.' + market + '.' + vm.selectedSymbol;
        //ngstomp.subscribe(topic, onMessage, {}, $scope);

        try {
          ngstomp
            .subscribeTo(topic)
            .callback(onMessage)
            .withBodyInJson()
            .bindTo($scope)
            .connect();
          // throw new Error("unable to subscribe to topic: " + topic);
        } catch (err) {
          stompError = 'unable to connect to: {0}:{1}'.format(topic, err.message);
          console.trace(stompError);
        }
      }

      function onMessage(message) {
        utilService.traceLog(item, "rabbit");
        // vm.data[0].values.push(JSON.parse(message.body));
        vm.data[0].values.push(message.body);
        $scope.$apply();
      }

      function addOHLC(item) {
          console.log("ohlcController.js::addOHLC");
          // $scope.item = item;
          item.name = vm.selectedSymbol;
          reset();
          vm.typeOHLC = true;
          utilService.unsubscribeTopic(topic);

          if ( vm.selectedSymbol === "..." ) {
            return;
          }

          initialise();

          // $scope.message = moment();

          // var ticks = Restangular.one('tick').one($scope.selectedSymbol).one(sod);

          // ticks.get().then(function(response) {
          //     vm.data[0].values = response.ticks;
          // });

          utilService.traceLog(item, "elasticsearch");
          fetchHistoricDataFromES();

          subscribeToStompUpdates();

          vm.isLoaded = true;

          // TODO catch all errors and set the property and message as required
          if (esError !== '' || stompError !== '') {
            vm.hasError = true;
          }

      };

  }

})();
<commit_msg>[Style Y033] Bindable Members Up Top<commit_after>(function() {

  'use strict';

  /**
   * @ngdoc function
   * @name atacamaApp.controller:WidgetCtrl
   * @description
   * # WidgetCtrl
   * Controller of the atacamaApp
   */
  angular.module('atacamaApp')
      .controller('OhlcWidgetCtrl', OhlcWidgetController);

  function OhlcWidgetController(
    $scope, $uibModal, ngstomp, $resource, $log, Restangular,
    elasticsearchService, chartService, turbineService, utilService) {
      var vm = this;

      vm.isLoaded = false;
      vm.hasError = false;
      vm.selectedSymbol = "...";

      vm.selectSymbol = function(selectedSymbol) {
        vm.selectedSymbol = selectedSymbol;
        $log.log('select symbol: ', vm.selectedSymbol);
        addOHLC($scope.item);
      };

      // var url = 'http://localhost:48002';
      var sod = moment(0, "HH").format("x");
      // var subscription;

      // TODO select market in UI
      var market = 'FTSE100';

      var topic = '';

      var esError = '';
      var stompError = '';

      // TODO centralise the rest call data more so widgets can share the results...
      // {"stocks":[{"market":"FTSE100","symbol":"ABC"},{"market":"FTSE100","symbol":"DEF"}]}
      // {"indicators":[{"overlay":true,"name":"BollingerBands"},{"overlay":false,"name":"SMA12"}]}
      // {"strategies":[{"name":"SMAStrategy"},{"name":"CCICorrectionStrategy"}]}

      reset();

      fetchStocks();

      // $scope.strategies = [
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'},
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'},
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'},
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'},
      //   { action: 'sell', symbol: 'ABC', market: 'FTSE100'},
      //   { action: 'buy', symbol: 'DEF', market: 'FTSE100'}
      // ];

      // set the size of the chart as the widget is resized...
      $scope.$on('gridster-item-resized', function(item) {
        $log.debug('gridster-item-resized');
        //vm.options.chart.height = firstHeight + ((item.targetScope.gridsterItem.sizeY - 1) * nextHeight);
        //vm.options.chart.width = firstWidth + ((item.targetScope.gridsterItem.sizeX - 1) * nextWidth);
        //console.log('offsetHeight:' + $scope.offsetParent.prop('offsetHeight'));
        //console.log('offsetWidth:' + $scope.offsetParent.prop('offsetWidth'));

        // TODO this is inconsistent... needs work!
        vm.options.chart.height = $scope.offsetParent.prop('offsetHeight');
        vm.options.chart.width = $scope.offsetParent.prop('offsetWidth')
        // TODO now causes an error... is this needed?
        // $scope.api.update();
      });

      function reset() {
        $log.debug('reset()');
        vm.isLoaded = false;
        vm.hasError = false;

        vm.options = {
            chart: {
                // TODO error message appears in console...
            }
        };

        vm.config = {
            deepWatchData: true,
            // deepWatchDataDepth: 1,
            refreshDataOnly: false,
            disabled: true
        };

        vm.data = [{
            key: vm.selectedSymbol,
            values: [{}]
        }];
      }

      function initialise() {
        vm.config = {
          deepWatchData: true,
          // deepWatchDataDepth: 1,
          refreshDataOnly: false,
          disabled: false
        };

        vm.options = {
            chart: {
                type: 'candlestickBarChart',
                // type: 'ohlcBarChart',
                height: $scope.offsetParent.prop('offsetHeight'),
                width: $scope.offsetParent.prop('offsetWidth'),
                margin: {
                    top: 20,
                    right: 40,
                    bottom: 40,
                    left: 40
                },
                x: function(d) {
                    return d['date'];
                },
                y: function(d) {
                    return d['close'];
                },
                showValues: true,
                transitionDuration: 500,
                xAxis: {
                    // axisLabel: 'Dates',
                    tickFormat: function(d) {
                        return d3.time.format('%X')(new Date(d));
                    },
                },
                yAxis: {
                    // axisLabel: 'Stock Price',
                    tickFormat: function(d, i) {
                        return '$' + d3.format(',.1f')(d);
                    }
                }
            }
        };
      }

      // for smart-table...
      // $scope.addStrategies = function(widget) {
      //    console.log("widget.js::addStrategies");
      //    reset();
      //    $scope.typeStrategies = true;
      //    unsubscribeTopic();
      //  };

      // I don't think this is necessary anymore...
      // $scope.$on('$destroy', function() {
      //   utilService.unsubscribeTopic(topic);
      // });

      function fetchStocks() {
        turbineService.symbols(market).then(function(response) {
        // console.log(JSON.stringify(response.stocks));
        vm.symbols = _.pluck(response.stocks, 'symbol');
        // $scope.selectedSymbol = $scope.symbols[0];
        }, function (err) {
          esError = 'unable to load symbols: {0}'.format(err.message);
          console.trace(esError);
        });
      }

      function fetchHistoricDataFromES() {
        var promise = elasticsearchService.getTicksAfter(vm.selectedSymbol, sod);

        promise.then(function (response) {
          vm.data[0].values = elasticsearchService.parseResults(response);
        }, function (err) {
          esError = 'unable to load data: {0}:{1}'.format(vm.selectedSymbol, err.message);
          console.trace(esError);
        });
      }

      function subscribeToStompUpdates() {
        topic = '/topic/ticks.' + market + '.' + vm.selectedSymbol;
        //ngstomp.subscribe(topic, onMessage, {}, $scope);

        try {
          ngstomp
            .subscribeTo(topic)
            .callback(onMessage)
            .withBodyInJson()
            .bindTo($scope)
            .connect();
          // throw new Error("unable to subscribe to topic: " + topic);
        } catch (err) {
          stompError = 'unable to connect to: {0}:{1}'.format(topic, err.message);
          console.trace(stompError);
        }
      }

      function onMessage(message) {
        utilService.traceLog(item, "rabbit");
        // vm.data[0].values.push(JSON.parse(message.body));
        vm.data[0].values.push(message.body);
        $scope.$apply();
      }

      function addOHLC(item) {
          console.log("ohlcController.js::addOHLC");
          // $scope.item = item;
          item.name = vm.selectedSymbol;
          reset();
          vm.typeOHLC = true;
          utilService.unsubscribeTopic(topic);

          if ( vm.selectedSymbol === "..." ) {
            return;
          }

          initialise();

          // $scope.message = moment();

          // var ticks = Restangular.one('tick').one($scope.selectedSymbol).one(sod);

          // ticks.get().then(function(response) {
          //     vm.data[0].values = response.ticks;
          // });

          utilService.traceLog(item, "elasticsearch");
          fetchHistoricDataFromES();

          subscribeToStompUpdates();

          vm.isLoaded = true;

          // TODO catch all errors and set the property and message as required
          if (esError !== '' || stompError !== '') {
            vm.hasError = true;
          }

      };

  }

})();
<|endoftext|>


<commit_before>import Checkbox       from 'material-ui/Checkbox';
import connectField   from 'uniforms/connectField';
import filterDOMProps from 'uniforms/filterDOMProps';
import MenuItem       from 'material-ui/MenuItem';
import RadioButton    from 'material-ui/RadioButton';
import React          from 'react';
import SelectField    from 'material-ui/SelectField';
import Subheader      from 'material-ui/Subheader';
import {List}         from 'material-ui/List';

const xor = (item, array) => {
    const index = array.indexOf(item);
    if (index === -1) {
        return array.concat([item]);
    }

    return array.slice(0, index).concat(array.slice(index + 1));
};

const renderCheckboxes = ({
    allowedValues,
    disabled,
    fieldType,
    id,
    label,
    labelPosition,
    name,
    onChange,
    transform,
    value,
    ...props
}) =>
    <List {...filterDOMProps(props)}>
        {!!label && <Subheader children={label} style={{paddingLeft: 0}} />}

        {allowedValues.map(item => fieldType === Array ? (
            <Checkbox
                checked={value.includes(item)}
                disabled={disabled}
                id={`${id}-${item}`}
                key={item}
                label={transform ? transform(item) : item}
                labelPosition={labelPosition}
                name={name}
                onCheck={() => onChange(xor(item, value))}
            />
        ) : (
            <RadioButton
                checked={value === item}
                disabled={disabled}
                id={`${id}-${item}`}
                key={item}
                label={transform ? transform(item) : item}
                labelPosition={labelPosition}
                name={name}
                onCheck={() => onChange(item)}
            />
        ))}
    </List>
;

const renderSelect = ({
    allowedValues,
    disabled,
    errorMessage,
    fullWidth = true,
    id,
    inputRef,
    label,
    name,
    onChange,
    placeholder,
    transform,
    value,
    ...props
}) =>
    <SelectField
        disabled={disabled}
        errorText={errorMessage}
        floatingLabelText={label}
        fullWidth={fullWidth}
        hintText={placeholder}
        id={id}
        name={name}
        onChange={(event, index, value) => onChange(value)}
        ref={inputRef}
        value={value}
        {...filterDOMProps(props)}
    >
        {allowedValues.map(value =>
            <MenuItem
                key={value}
                value={value}
                primaryText={transform ? transform(value) : value}
            />
        )}
    </SelectField>
;

const Select = props =>
    props.checkboxes || props.fieldType === Array
        ? renderCheckboxes(props)
        : renderSelect    (props)
;

export default connectField(Select, {ensureValue: false});
<commit_msg>Added multiple in SelectField in material ui (closes #236).<commit_after>import Checkbox       from 'material-ui/Checkbox';
import connectField   from 'uniforms/connectField';
import filterDOMProps from 'uniforms/filterDOMProps';
import MenuItem       from 'material-ui/MenuItem';
import RadioButton    from 'material-ui/RadioButton';
import React          from 'react';
import SelectField    from 'material-ui/SelectField';
import Subheader      from 'material-ui/Subheader';
import {List}         from 'material-ui/List';

const xor = (item, array) => {
    const index = array.indexOf(item);
    if (index === -1) {
        return array.concat([item]);
    }

    return array.slice(0, index).concat(array.slice(index + 1));
};

const renderCheckboxes = ({
    allowedValues,
    disabled,
    fieldType,
    id,
    label,
    labelPosition,
    name,
    onChange,
    transform,
    value,
    ...props
}) =>
    <List {...filterDOMProps(props)}>
        {!!label && <Subheader children={label} style={{paddingLeft: 0}} />}

        {allowedValues.map(item => fieldType === Array ? (
            <Checkbox
                checked={value.includes(item)}
                disabled={disabled}
                id={`${id}-${item}`}
                key={item}
                label={transform ? transform(item) : item}
                labelPosition={labelPosition}
                name={name}
                onCheck={() => onChange(xor(item, value))}
            />
        ) : (
            <RadioButton
                checked={value === item}
                disabled={disabled}
                id={`${id}-${item}`}
                key={item}
                label={transform ? transform(item) : item}
                labelPosition={labelPosition}
                name={name}
                onCheck={() => onChange(item)}
            />
        ))}
    </List>
;

const renderSelect = ({
    allowedValues,
    disabled,
    errorMessage,
    fieldType,
    fullWidth = true,
    id,
    inputRef,
    label,
    name,
    onChange,
    placeholder,
    transform,
    value,
    ...props
}) =>
    <SelectField
        disabled={disabled}
        errorText={errorMessage}
        floatingLabelText={label}
        fullWidth={fullWidth}
        hintText={placeholder}
        id={id}
        multiple={fieldType === Array}
        name={name}
        onChange={(event, index, value) => onChange(value)}
        ref={inputRef}
        value={value}
        {...filterDOMProps(props)}
    >
        {allowedValues.map(allowedValue =>
            <MenuItem
                insetChildren={fieldType === Array || undefined}
                checked={fieldType === Array && value && value.includes(allowedValue) || undefined}
                key={allowedValue}
                primaryText={transform ? transform(allowedValue) : allowedValue}
                value={allowedValue}
            />
        )}
    </SelectField>
;

const Select = ({checkboxes, ...props}) =>
    checkboxes
        ? renderCheckboxes(props)
        : renderSelect    (props)
;

export default connectField(Select, {ensureValue: false});
<|endoftext|>


<commit_before>import _forOwn from 'lodash/forOwn';

import LocalClient from '../../stores/local-client.stores';

import FontPrecursor from '../precursor/FontPrecursor';

import WorkerPool from '../../worker/worker-pool';

const MERGE_URL = process.env.MERGE ? 'http://localhost:3000' : 'https://merge.prototypo.io/v1';
const GLYPHR_URL = 'http://www.glyphrstudio.com/online';

const oldFont = {};
let localClient;
let mergeTimeoutRef;
let instance;

window.addEventListener('fluxServer.setup', () => {
	localClient = LocalClient.instance();
});

window.fontResult = undefined;
window.glyph = undefined;

function getUuid(email, familyName, styleName) {
	const stringForId = `${new Date().getTime()}${familyName}${email}${styleName}`;
	let id = '';

	for (let i = 0; i < 16; i++) {
		if (i < stringForId.length) {
			id += (stringForId.charCodeAt(i) * Math.random() * 32).toFixed(0).toString(16).padStart(2, '0');
		}
		else {
			id += (Math.random() * 100).toFixed(0).toString(16).padStart(2, '0');
		}

		if (i === 3 || i === 5 || i === 7 || i === 9) {
			id += '-';
		}
	}

	return id;
}

function getComponentIdAndGlyphPerClass(typedata) {
	const fontComponentIdAndGlyphPerClass = {};

	Object.keys(typedata.json.glyphs).forEach((key) => {
		const glyph = typedata.json.glyphs[key];

		if (glyph.outline.component) {
			glyph.outline.component.forEach((component) => {
				if (component.class) {
					fontComponentIdAndGlyphPerClass[component.class] = [
						...(fontComponentIdAndGlyphPerClass[component.class] || []),
						[glyph.name, component.id],
					];
				}
			});
		}
	});

	return fontComponentIdAndGlyphPerClass;
}

async function mergeFont(url, action, params, arrayBuffer, mime = 'otf') {
	const response = await fetch([
		url,
		action,
		...params,
	].join('/'), {
		method: 'POST',
		headers: {'Content-Type': `application/${mime}`},
		body: arrayBuffer,
	});

	return response.arrayBuffer();
}

export default class FontMediator {
	static async init(typedatas, workerPoolSize, noCanvas) {
		instance = new FontMediator(workerPoolSize, noCanvas);

		await instance.workerPool.workerReady;

		if (typedatas) {
			return instance.addTemplate(typedatas).then((componentIdAndGlyphPerClass) => {
				if (!process.env.LIBRARY) {
					localClient.dispatchAction('/store-value-font', {
						componentIdAndGlyphPerClass,
					});
				}
			});
		}

		return Promise.resolve();
	}

	static instance() {
		if (!instance) {
			throw new Error('cannot return an instance before init');
		}

		return instance;
	}

	constructor(workerPoolSize, noCanvas = false) {
		this.noCanvas = noCanvas;
		this.workerPool = new WorkerPool(workerPoolSize);
	}

	addTemplate(typedatas) {
		return new Promise((resolve) => {
			this.workerPool.eachJob({
				action: {
					type: 'createFont',
					data: typedatas,
				},
				callback: () => {
					this.initValues = {};
					this.glyphList = {};
					this.fontMakers = {};
					const componentIdAndGlyphPerClass = {};

					if (!noCanvas) {
						typedatas.forEach((typedata) => {
							if (!process.env.LIBRARY) {
								const font = new FontPrecursor(typedata.json);

								this.fontMakers[typedata.name] = font;
							}

							this.glyphList[typedata.name] = typedata.json.glyphs;

							componentIdAndGlyphPerClass[typedata.name] = getComponentIdAndGlyphPerClass(typedata);

							const initValues = {};

							typedata.json.controls.forEach((group) => {
								group.parameters.forEach((param) => {
									initValues[param.name] = param.init;
								});
							});

							this.initValues[typedata.name] = initValues;
						});
						resolve(componentIdAndGlyphPerClass);
					}
					else {
						resolve();
					}
				},
			});
		});
	}

	setupInfo({
		family, style, email, template,
	}) {
		if (!instance) {
			throw new Error('cannot return an instance before init');
		}

		this.family = family || this.family;
		this.style = style || this.style;
		this.email = email || this.email;
		this.template = template || this.template;
	}

	reset(fontName, template, subset, glyphCanvasUnicode) {
		return this.getFont(fontName, template, this.initValues[template], subset, glyphCanvasUnicode);
	}

	reloadFont(fontName, json) {
		return new Promise((resolve) => {
			this.workerPool.eachJob({
				action: {
					type: 'reloadFont',
					data: {
						name: fontName,
						json,
					},
				},
				callback: () => {
					this.fontMakers[fontName] = new FontPrecursor(json);
					resolve(this);
				},
			});
		});
	}

	addToFont(buffer, fontName) {
		const fontFace = new FontFace(
			fontName,
			buffer,
		);

		if (fontFace.status === 'error') {
			console.warn(`error in fontface ${fontName}`); // eslint-disable-line no-console
			return;
		}

		if (oldFont[fontName]) {
			document.fonts.delete(oldFont[fontName]);
		}

		document.fonts.add(fontFace);
		oldFont[fontName] = fontFace;
	}

	getFontFile(fontName, template, params, subset) {
		if (!this.workerPool) {
			return undefined;
		}
		const familyName = this.family.name;
		const styleName = this.style.name || 'REGULAR';

		return new Promise((resolve) => {
			const job = {
				action: {
					type: 'constructFont',
					data: {
						name: template,
						familyName,
						styleName,
						params: {
							...params,
						},
						subset,
						forExport: true,
					},
				},
				callback: async (arrayBuffer) => {
					const id = getUuid(this.email, familyName, styleName);

					const mergedFont = await mergeFont(
						MERGE_URL,
						'fontfile',
						[
							id,
							familyName,
							styleName,
							true,
						],
						arrayBuffer,
					);

					await mergeFont(
						MERGE_URL,
						'fontinfo',
						[id],
						JSON.stringify({
							template: this.template,
							family: familyName,
							style: styleName,
							date: new Date().getTime(),
							email: this.email,
							params,
						}),
						'json',
					);

					resolve(mergedFont);
				},
			};

			this.workerPool.doJob(job, fontName);
		});
	}

	async mergeFont(arrayBuffer) {
		const buffer = await mergeFont(
			MERGE_URL,
			'mergefont',
			[
				this.email,
			],
			arrayBuffer,
		);

		return buffer;
	}

	mergeFontWithoutTimeout(arrayBuffer) {
		return this.mergeFont(arrayBuffer);
	}

	mergeFontWithTimeout(arrayBuffer) {
		clearTimeout(mergeTimeoutRef);

		return new Promise((resolve) => {
			const timeout = setTimeout(() => {
				resolve(this.mergeFont(arrayBuffer));
			}, 300);

			mergeTimeoutRef = timeout;
		});
	}

	openInGlyphr(fontName, template, params, subset) {
		return this.getFontObject(
			fontName,
			template,
			params,
			subset,
		).then(({fontBuffer}) => new Promise((resolve) => {
			window.open(GLYPHR_URL);
			window.addEventListener('message', (e) => {
				e.source.postMessage(fontBuffer, e.origin, [fontBuffer]);
				resolve();
			}, {once: true});
		}));
	}

	getFontObject(fontName, template, params, subset) {
		if (!this.workerPool) {
			return false;
		}

		return new Promise((resolve) => {
			const job = {
				action: {
					type: 'constructFont',
					data: {
						name: template,
						params: {
							...params,
						},
						subset,
					},
				},
				callback: (arrayBuffer) => {
					const glyphsListLengthView = new DataView(arrayBuffer, 0, 4);
					const glyphsListLength = glyphsListLengthView.getInt32(0, true);

					const glyphListArray = new Int32Array(
						arrayBuffer,
						4,
						glyphsListLength * 6, // each value is 32bits and there is 4
					);
					const glyphValues = [];

					for (let i = 0; i < glyphsListLength * 6; i += 6) {
						const unicode = glyphListArray[i];
						const advanceWidth = glyphListArray[i + 1];
						const spacingLeft = glyphListArray[i + 2];
						const baseSpacingLeft = glyphListArray[i + 3];
						const spacingRight = glyphListArray[i + 4];
						const baseSpacingRight = glyphListArray[i + 5];

						glyphValues.push({
							unicode,
							advanceWidth,
							spacingLeft,
							spacingRight,
							baseSpacingLeft,
							baseSpacingRight,
						});
					}
					const fontBuffer = arrayBuffer.slice(
						4 + (glyphsListLength * 4 * 6),
						arrayBuffer.length,
					);

					resolve({
						glyphValues,
						fontBuffer,
					});
				},
			};

			this.workerPool.doJob(job, fontName);
		});
	}

	getFont(fontName, template, params, subset, glyphCanvasUnicode) {
		if (glyphCanvasUnicode && !this.noCanvas) {
			const glyphForCanvas = this.fontMakers[template].constructFont({
				...params,
			}, [glyphCanvasUnicode]);

			[window.glyph] = glyphForCanvas.glyphs;
			localClient.dispatchAction('/store-value-font', {
				glyph: Math.random(),
			});
		}

		return this.getFontObject(
			fontName,
			template,
			params,
			subset,
		).then(({glyphValues, fontBuffer}) => {
			this.addToFont(
				fontBuffer,
				fontName,
			);

			window.fontResult = {glyphs: glyphValues};
			localClient.dispatchAction('/store-value-font', {
				font: Math.random(),
			});

			return this.mergeFontWithTimeout(fontBuffer, fontName);
		}).then((mergedBuffer) => {
			this.addToFont(mergedBuffer, fontName);
		});
	}

	getAllGlyphForCanvas(template, params = this.initValues[template]) {
		const glyphArray = [];

		_forOwn(this.glyphList[template], (glyph) => {
			if (glyph.unicode && !this.noCanvas) {
				try {
					const constructedGlyph = this.fontMakers[template].constructFont({
						...params,
					}, [glyph.unicode]).glyphs[0];

					glyphArray.push(constructedGlyph);
				}
				catch (error) {
					glyphArray.push({error, unicode: glyph.unicode});
				}
			}
		});

		window.glyphArray = glyphArray;
		localClient.dispatchAction('/store-value-font', {
			glyphArray: Math.random(),
		});
	}
}
<commit_msg>fix no canvas<commit_after>import _forOwn from 'lodash/forOwn';

import LocalClient from '../../stores/local-client.stores';

import FontPrecursor from '../precursor/FontPrecursor';

import WorkerPool from '../../worker/worker-pool';

const MERGE_URL = process.env.MERGE ? 'http://localhost:3000' : 'https://merge.prototypo.io/v1';
const GLYPHR_URL = 'http://www.glyphrstudio.com/online';

const oldFont = {};
let localClient;
let mergeTimeoutRef;
let instance;

window.addEventListener('fluxServer.setup', () => {
	localClient = LocalClient.instance();
});

window.fontResult = undefined;
window.glyph = undefined;

function getUuid(email, familyName, styleName) {
	const stringForId = `${new Date().getTime()}${familyName}${email}${styleName}`;
	let id = '';

	for (let i = 0; i < 16; i++) {
		if (i < stringForId.length) {
			id += (stringForId.charCodeAt(i) * Math.random() * 32).toFixed(0).toString(16).padStart(2, '0');
		}
		else {
			id += (Math.random() * 100).toFixed(0).toString(16).padStart(2, '0');
		}

		if (i === 3 || i === 5 || i === 7 || i === 9) {
			id += '-';
		}
	}

	return id;
}

function getComponentIdAndGlyphPerClass(typedata) {
	const fontComponentIdAndGlyphPerClass = {};

	Object.keys(typedata.json.glyphs).forEach((key) => {
		const glyph = typedata.json.glyphs[key];

		if (glyph.outline.component) {
			glyph.outline.component.forEach((component) => {
				if (component.class) {
					fontComponentIdAndGlyphPerClass[component.class] = [
						...(fontComponentIdAndGlyphPerClass[component.class] || []),
						[glyph.name, component.id],
					];
				}
			});
		}
	});

	return fontComponentIdAndGlyphPerClass;
}

async function mergeFont(url, action, params, arrayBuffer, mime = 'otf') {
	const response = await fetch([
		url,
		action,
		...params,
	].join('/'), {
		method: 'POST',
		headers: {'Content-Type': `application/${mime}`},
		body: arrayBuffer,
	});

	return response.arrayBuffer();
}

export default class FontMediator {
	static async init(typedatas, workerPoolSize, noCanvas) {
		instance = new FontMediator(workerPoolSize, noCanvas);

		await instance.workerPool.workerReady;

		if (typedatas) {
			return instance.addTemplate(typedatas).then((componentIdAndGlyphPerClass) => {
				if (!process.env.LIBRARY) {
					localClient.dispatchAction('/store-value-font', {
						componentIdAndGlyphPerClass,
					});
				}
			});
		}

		return Promise.resolve();
	}

	static instance() {
		if (!instance) {
			throw new Error('cannot return an instance before init');
		}

		return instance;
	}

	constructor(workerPoolSize, noCanvas = false) {
		this.noCanvas = noCanvas;
		this.workerPool = new WorkerPool(workerPoolSize);
	}

	addTemplate(typedatas) {
		return new Promise((resolve) => {
			this.workerPool.eachJob({
				action: {
					type: 'createFont',
					data: typedatas,
				},
				callback: () => {
					this.initValues = {};
					this.glyphList = {};
					this.fontMakers = {};
					const componentIdAndGlyphPerClass = {};

					if (!this.noCanvas) {
						typedatas.forEach((typedata) => {
							if (!process.env.LIBRARY) {
								const font = new FontPrecursor(typedata.json);

								this.fontMakers[typedata.name] = font;
							}

							this.glyphList[typedata.name] = typedata.json.glyphs;

							componentIdAndGlyphPerClass[typedata.name] = getComponentIdAndGlyphPerClass(typedata);

							const initValues = {};

							typedata.json.controls.forEach((group) => {
								group.parameters.forEach((param) => {
									initValues[param.name] = param.init;
								});
							});

							this.initValues[typedata.name] = initValues;
						});
						resolve(componentIdAndGlyphPerClass);
					}
					else {
						resolve();
					}
				},
			});
		});
	}

	setupInfo({
		family, style, email, template,
	}) {
		if (!instance) {
			throw new Error('cannot return an instance before init');
		}

		this.family = family || this.family;
		this.style = style || this.style;
		this.email = email || this.email;
		this.template = template || this.template;
	}

	reset(fontName, template, subset, glyphCanvasUnicode) {
		return this.getFont(fontName, template, this.initValues[template], subset, glyphCanvasUnicode);
	}

	reloadFont(fontName, json) {
		return new Promise((resolve) => {
			this.workerPool.eachJob({
				action: {
					type: 'reloadFont',
					data: {
						name: fontName,
						json,
					},
				},
				callback: () => {
					this.fontMakers[fontName] = new FontPrecursor(json);
					resolve(this);
				},
			});
		});
	}

	addToFont(buffer, fontName) {
		const fontFace = new FontFace(
			fontName,
			buffer,
		);

		if (fontFace.status === 'error') {
			console.warn(`error in fontface ${fontName}`); // eslint-disable-line no-console
			return;
		}

		if (oldFont[fontName]) {
			document.fonts.delete(oldFont[fontName]);
		}

		document.fonts.add(fontFace);
		oldFont[fontName] = fontFace;
	}

	getFontFile(fontName, template, params, subset) {
		if (!this.workerPool) {
			return undefined;
		}
		const familyName = this.family.name;
		const styleName = this.style.name || 'REGULAR';

		return new Promise((resolve) => {
			const job = {
				action: {
					type: 'constructFont',
					data: {
						name: template,
						familyName,
						styleName,
						params: {
							...params,
						},
						subset,
						forExport: true,
					},
				},
				callback: async (arrayBuffer) => {
					const id = getUuid(this.email, familyName, styleName);

					const mergedFont = await mergeFont(
						MERGE_URL,
						'fontfile',
						[
							id,
							familyName,
							styleName,
							true,
						],
						arrayBuffer,
					);

					await mergeFont(
						MERGE_URL,
						'fontinfo',
						[id],
						JSON.stringify({
							template: this.template,
							family: familyName,
							style: styleName,
							date: new Date().getTime(),
							email: this.email,
							params,
						}),
						'json',
					);

					resolve(mergedFont);
				},
			};

			this.workerPool.doJob(job, fontName);
		});
	}

	async mergeFont(arrayBuffer) {
		const buffer = await mergeFont(
			MERGE_URL,
			'mergefont',
			[
				this.email,
			],
			arrayBuffer,
		);

		return buffer;
	}

	mergeFontWithoutTimeout(arrayBuffer) {
		return this.mergeFont(arrayBuffer);
	}

	mergeFontWithTimeout(arrayBuffer) {
		clearTimeout(mergeTimeoutRef);

		return new Promise((resolve) => {
			const timeout = setTimeout(() => {
				resolve(this.mergeFont(arrayBuffer));
			}, 300);

			mergeTimeoutRef = timeout;
		});
	}

	openInGlyphr(fontName, template, params, subset) {
		return this.getFontObject(
			fontName,
			template,
			params,
			subset,
		).then(({fontBuffer}) => new Promise((resolve) => {
			window.open(GLYPHR_URL);
			window.addEventListener('message', (e) => {
				e.source.postMessage(fontBuffer, e.origin, [fontBuffer]);
				resolve();
			}, {once: true});
		}));
	}

	getFontObject(fontName, template, params, subset) {
		if (!this.workerPool) {
			return false;
		}

		return new Promise((resolve) => {
			const job = {
				action: {
					type: 'constructFont',
					data: {
						name: template,
						params: {
							...params,
						},
						subset,
					},
				},
				callback: (arrayBuffer) => {
					const glyphsListLengthView = new DataView(arrayBuffer, 0, 4);
					const glyphsListLength = glyphsListLengthView.getInt32(0, true);

					const glyphListArray = new Int32Array(
						arrayBuffer,
						4,
						glyphsListLength * 6, // each value is 32bits and there is 4
					);
					const glyphValues = [];

					for (let i = 0; i < glyphsListLength * 6; i += 6) {
						const unicode = glyphListArray[i];
						const advanceWidth = glyphListArray[i + 1];
						const spacingLeft = glyphListArray[i + 2];
						const baseSpacingLeft = glyphListArray[i + 3];
						const spacingRight = glyphListArray[i + 4];
						const baseSpacingRight = glyphListArray[i + 5];

						glyphValues.push({
							unicode,
							advanceWidth,
							spacingLeft,
							spacingRight,
							baseSpacingLeft,
							baseSpacingRight,
						});
					}
					const fontBuffer = arrayBuffer.slice(
						4 + (glyphsListLength * 4 * 6),
						arrayBuffer.length,
					);

					resolve({
						glyphValues,
						fontBuffer,
					});
				},
			};

			this.workerPool.doJob(job, fontName);
		});
	}

	getFont(fontName, template, params, subset, glyphCanvasUnicode) {
		if (glyphCanvasUnicode && !this.noCanvas) {
			const glyphForCanvas = this.fontMakers[template].constructFont({
				...params,
			}, [glyphCanvasUnicode]);

			[window.glyph] = glyphForCanvas.glyphs;
			localClient.dispatchAction('/store-value-font', {
				glyph: Math.random(),
			});
		}

		return this.getFontObject(
			fontName,
			template,
			params,
			subset,
		).then(({glyphValues, fontBuffer}) => {
			this.addToFont(
				fontBuffer,
				fontName,
			);

			window.fontResult = {glyphs: glyphValues};
			localClient.dispatchAction('/store-value-font', {
				font: Math.random(),
			});

			return this.mergeFontWithTimeout(fontBuffer, fontName);
		}).then((mergedBuffer) => {
			this.addToFont(mergedBuffer, fontName);
		});
	}

	getAllGlyphForCanvas(template, params = this.initValues[template]) {
		const glyphArray = [];

		_forOwn(this.glyphList[template], (glyph) => {
			if (glyph.unicode && !this.noCanvas) {
				try {
					const constructedGlyph = this.fontMakers[template].constructFont({
						...params,
					}, [glyph.unicode]).glyphs[0];

					glyphArray.push(constructedGlyph);
				}
				catch (error) {
					glyphArray.push({error, unicode: glyph.unicode});
				}
			}
		});

		window.glyphArray = glyphArray;
		localClient.dispatchAction('/store-value-font', {
			glyphArray: Math.random(),
		});
	}
}
<|endoftext|>


<commit_before>var helper = require(__dirname + "/test-helper");

test('executing query', function() {

  test("queing query", function() {

    test('when connection is ready', function() {
      var client = helper.client();
      assert.empty(client.connection.queries);
      client.connection.emit('readyForQuery');
      client.query('yes');
      assert.lengthIs(client.connection.queries, 1);
      assert.equal(client.connection.queries, 'yes');
    });

    test('when connection is not ready', function() {
      var client = helper.client();

      test('query is not sent', function() {
        client.query('boom');
        assert.empty(client.connection.queries);
      });

      test('sends query to connection once ready', function() {
        assert.ok(client.connection.emit('readyForQuery'));
        assert.lengthIs(client.connection.queries, 1);
        assert.equal(client.connection.queries[0], "boom");
      });

    });

    test("multiple in the queue", function() {
      var client = helper.client();
      var connection = client.connection;
      var queries = connection.queries;
      client.query('one');
      client.query('two');
      client.query('three');
      assert.empty(queries);

      test("after one ready for query",function() {
        connection.emit('readyForQuery');
        assert.lengthIs(queries, 1);
        assert.equal(queries[0], "one");
      });

      test('after two ready for query', function() {
        connection.emit('readyForQuery');
        assert.lengthIs(queries, 2);
      });

      test("after a bunch more", function() {
        connection.emit('readyForQuery');
        connection.emit('readyForQuery');
        connection.emit('readyForQuery');
        assert.lengthIs(queries, 3);
        assert.equal(queries[0], "one");
        assert.equal(queries[1], 'two');
        assert.equal(queries[2], 'three');
      });
    });
  });

  test("query event binding and flow", function() {
    var client = helper.client();
    var con = client.connection;
    var query = client.query('whatever');

    test("has no queries sent before ready", function() {
      assert.empty(con.queries);
    });

    test('sends query on readyForQuery event', function() {
      con.emit('readyForQuery');
      assert.lengthIs(con.queries, 1);
      assert.equal(con.queries[0], 'whatever');
    });

    test('handles rowDescription message', function() {
      var handled = con.emit('rowDescription',{
        fields: [{
          name: 'boom'
        }]
      });
      assert.ok(handled, "should have handlded rowDescritpion");
    });

    test('handles dataRow messages', function() {
      assert.emits(query, 'row', function(row) {
        assert.equal(row['boom'], "hi");
      });

      var handled = con.emit('dataRow', { fields: ["hi"] });
      assert.ok(handled, "should have handled first data row message");

      assert.emits(query, 'row', function(row) {
        assert.equal(row['boom'], "bye");
      });

      var handledAgain = con.emit('dataRow', { fields: ["bye"] });
      assert.ok(handledAgain, "should have handled seciond data row message");
    });

    //multiple command complete messages will be sent
    //when multiple queries are in a simple command
    test('handles command complete messages', function() {
      con.emit('commandComplete', {
        text: 'INSERT 31 1'
      });
    });

    test('removes itself after another readyForQuery message', function() {
      return false;
      assert.emits(query, "end", function(msg) {
        //TODO do we want to check the complete messages?
      });
      con.emit("readyForQuery");
      //this would never actually happen
      ['dataRow','rowDescritpion', 'commandComplete'].forEach(function(msg) {
        assert.equal(con.emit(msg), false, "Should no longer be picking up '"+ msg +"' messages");
      });
    });

  });

});

<commit_msg>Fix typos in simple-query-tests.js<commit_after>var helper = require(__dirname + "/test-helper");

test('executing query', function() {

  test("queing query", function() {

    test('when connection is ready', function() {
      var client = helper.client();
      assert.empty(client.connection.queries);
      client.connection.emit('readyForQuery');
      client.query('yes');
      assert.lengthIs(client.connection.queries, 1);
      assert.equal(client.connection.queries, 'yes');
    });

    test('when connection is not ready', function() {
      var client = helper.client();

      test('query is not sent', function() {
        client.query('boom');
        assert.empty(client.connection.queries);
      });

      test('sends query to connection once ready', function() {
        assert.ok(client.connection.emit('readyForQuery'));
        assert.lengthIs(client.connection.queries, 1);
        assert.equal(client.connection.queries[0], "boom");
      });

    });

    test("multiple in the queue", function() {
      var client = helper.client();
      var connection = client.connection;
      var queries = connection.queries;
      client.query('one');
      client.query('two');
      client.query('three');
      assert.empty(queries);

      test("after one ready for query",function() {
        connection.emit('readyForQuery');
        assert.lengthIs(queries, 1);
        assert.equal(queries[0], "one");
      });

      test('after two ready for query', function() {
        connection.emit('readyForQuery');
        assert.lengthIs(queries, 2);
      });

      test("after a bunch more", function() {
        connection.emit('readyForQuery');
        connection.emit('readyForQuery');
        connection.emit('readyForQuery');
        assert.lengthIs(queries, 3);
        assert.equal(queries[0], "one");
        assert.equal(queries[1], 'two');
        assert.equal(queries[2], 'three');
      });
    });
  });

  test("query event binding and flow", function() {
    var client = helper.client();
    var con = client.connection;
    var query = client.query('whatever');

    test("has no queries sent before ready", function() {
      assert.empty(con.queries);
    });

    test('sends query on readyForQuery event', function() {
      con.emit('readyForQuery');
      assert.lengthIs(con.queries, 1);
      assert.equal(con.queries[0], 'whatever');
    });

    test('handles rowDescription message', function() {
      var handled = con.emit('rowDescription',{
        fields: [{
          name: 'boom'
        }]
      });
      assert.ok(handled, "should have handlded rowDescription");
    });

    test('handles dataRow messages', function() {
      assert.emits(query, 'row', function(row) {
        assert.equal(row['boom'], "hi");
      });

      var handled = con.emit('dataRow', { fields: ["hi"] });
      assert.ok(handled, "should have handled first data row message");

      assert.emits(query, 'row', function(row) {
        assert.equal(row['boom'], "bye");
      });

      var handledAgain = con.emit('dataRow', { fields: ["bye"] });
      assert.ok(handledAgain, "should have handled seciond data row message");
    });

    //multiple command complete messages will be sent
    //when multiple queries are in a simple command
    test('handles command complete messages', function() {
      con.emit('commandComplete', {
        text: 'INSERT 31 1'
      });
    });

    test('removes itself after another readyForQuery message', function() {
      return false;
      assert.emits(query, "end", function(msg) {
        //TODO do we want to check the complete messages?
      });
      con.emit("readyForQuery");
      //this would never actually happen
      ['dataRow','rowDescription', 'commandComplete'].forEach(function(msg) {
        assert.equal(con.emit(msg), false, "Should no longer be picking up '"+ msg +"' messages");
      });
    });

  });

});

<|endoftext|>