<commit_before>/* global describe, it, xit */
/* jslint node: true, esnext: true */

"use strict";

const chai = require('chai'),
  assert = chai.assert,
  expect = chai.expect,
  should = chai.should();

chai.use(require('chai-as-promised'));

const uti = require('uti'),
  flow = require('kronos-flow'),
  kronos = require('../lib/manager.js'),
  someStepFactory = require('./fixtures/steps1/someStep');

const flowDecl = {
  "name": "flow1",
  "type": "kronos-flow",
  "steps": {
    "s1": {
      "type": "some-step"
    }
  }
};

describe('service manager', function () {
  describe('std attributes', function () {
    it('should have a name', function (done) {
      kronos.manager({
        name: 'myName',
        logLevel: 'trace'
      }).then(function (manager) {
        try {
          assert.equal(manager.name, 'myName');
          assert.equal(manager.toString(), 'myName');

          // test if there is a log level entry point
          manager.info(level => 'level ');
          assert.equal(manager.logLevel, 'trace');
          done();
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });
  });

  describe('uti definitions', function () {
    it('should be present', function (done) {
      kronos.manager().then(function (manager) {
        try {
          //console.log(`** ${manager.uti.conformsTo('org.kronos.flow','public.json')}`);
          assert(uti.conformsTo('org.kronos.flow', 'public.json'),
            'org.kronos.flow conformsTo public.json');
          done();
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });
  });

  describe('shutdown', function () {
    it('empty', function (done) {
      kronos.manager().then(function (manager) {
        try {
          manager.shutdown().then(
            function (manager) {
              done();
            }, done
          );
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

    it('with flows', function (done) {
      kronos.manager().then(function (manager) {
        try {
          manager.registerStep(someStepFactory);
          flow.registerWithManager(manager);
          const aFlow = manager.getStepInstance(flowDecl);
          manager.registerFlow(aFlow);
          aFlow.start().then(function () {
            manager.shutdown().then(
              function (manager) {
                done();
              }, done
            );
          });
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

  });

  describe('step registration', function () {
    it('registers steps present', function (done) {
      kronos.manager().then(function (manager) {
        try {
          let stepFromEvent;
          manager.addListener('stepRegistered', step => stepFromEvent = step);

          manager.registerStep(someStepFactory);
          const myStep = manager.steps['some-step'];
          expect(myStep.name, 'step name').to.equal('some-step');
          assert.equal(stepFromEvent, myStep);

          // do not fire a 2nd. time stepRegistered
          stepFromEvent = undefined;
          manager.registerStep(someStepFactory);
          assert.equal(stepFromEvent, undefined);

          done();
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

    describe('getStepInstance', function () {
      it('not registerd should throw', function (done) {
        kronos.manager().then(function (manager) {
          try {
            assert.throws(function () {
              manager.getStepInstance({
                type: "not-already-registered"
              })
            });
            done();
          } catch (e) {
            done(e);
          }
        }, function () {
          done("Manager not created");
        });
      });
    });
  });

  describe('flows', function () {
    const flowName = 'flow1';

    it('registered should be present', function (done) {
      kronos.manager().then(function (myManager) {
        try {
          let flowFromEvent;
          myManager.addListener('flowRegistered', flow => flowFromEvent = flow);

          flow.registerWithManager(myManager);
          myManager.registerStep(someStepFactory);
          myManager.registerFlow(myManager.getStepInstance(flowDecl));

          const aFlow = myManager.flows[flowName];
          should.exist(aFlow);
          expect(aFlow.name).to.equal(flowName);
          expect(aFlow.state).to.equal("stopped");

          assert.equal(flowFromEvent, aFlow);
          done();
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

    it('can be removed again', function (done) {
      kronos.manager().then(function (myManager) {
        try {
          let removedStepFromEventDone = false;

          myManager.addListener('stepStateChanged', (step, oldState, newState) => {
            if (newState === 'removed' && step.name === 'flow1') {
              removedStepFromEventDone = true;
            }
          });

          flow.registerWithManager(myManager);
          myManager.registerStep(someStepFactory);
          myManager.registerFlow(myManager.getStepInstance(flowDecl));

          myManager.deleteFlow(flowName).then(function () {
            try {
              assert.equal(myManager.flows.flow1, undefined);

              // stepStateChanged may get fired late ??
              setTimeout(function () {
                assert.isTrue(removedStepFromEventDone);
                done();
              }, 10);
            } catch (e) {
              done(e);
            }
          }, done);
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

    it('deleting unknown flow rejects', function (done) {
      kronos.manager().then(function (myManager) {
        try {
          myManager.deleteFlow("unknownFlow").then(function () {
            done(new Error("shouldn fullfill: deletion of an unknown flow"));
          }, function (reject) {
            done();
          });
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });
  });
});
<commit_msg>test: fix #57<commit_after>/* global describe, it, xit */
/* jslint node: true, esnext: true */

"use strict";

const chai = require('chai'),
  assert = chai.assert,
  expect = chai.expect,
  should = chai.should();

chai.use(require('chai-as-promised'));

const uti = require('uti'),
  flow = require('kronos-flow'),
  kronos = require('../lib/manager.js'),
  someStepFactory = require('./fixtures/steps1/someStep');

const flowDecl = {
  "name": "flow1",
  "type": "kronos-flow",
  "steps": {
    "s1": {
      "type": "some-step"
    }
  }
};

describe('service manager', function () {
  describe('std attributes', function () {
    it('should have a name', function (done) {
      kronos.manager({
        name: 'myName',
        logLevel: 'trace'
      }).then(function (manager) {
        try {
          assert.equal(manager.name, 'myName');
          assert.equal(manager.toString(), 'myName');

          // test if there is a log level entry point
          manager.info(level => 'level ');
          assert.equal(manager.logLevel, 'trace');
          done();
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });
  });

  describe('uti definitions', function () {
    it('should be present', function (done) {
      kronos.manager().then(function (manager) {
        try {
          //console.log(`** ${manager.uti.conformsTo('org.kronos.flow','public.json')}`);
          assert(uti.conformsTo('org.kronos.flow', 'public.json'),
            'org.kronos.flow conformsTo public.json');
          done();
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });
  });

  describe('shutdown', function () {
    it('empty', function (done) {
      kronos.manager().then(function (manager) {
        try {
          manager.shutdown().then(
            function (manager) {
              done();
            }, done
          );
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

    it('with flows', function (done) {
      kronos.manager().then(function (manager) {
        try {
          manager.registerStep(someStepFactory);
          flow.registerWithManager(manager);
          const aFlow = manager.getStepInstance(flowDecl);
          manager.registerFlow(aFlow);
          aFlow.start().then(function () {
            manager.shutdown().then(
              function (manager) {
                done();
              }, done
            );
          });
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

  });

  describe('step registration', function () {
    it('registers steps present', function (done) {
      kronos.manager().then(function (manager) {
        try {
          let stepFromEvent;
          manager.addListener('stepRegistered', step => stepFromEvent = step);

          manager.registerStep(someStepFactory);
          const myStep = manager.steps['some-step'];
          expect(myStep.name, 'step name').to.equal('some-step');
          assert.equal(stepFromEvent, myStep);

          // do not fire a 2nd. time stepRegistered
          stepFromEvent = undefined;
          manager.registerStep(someStepFactory);
          assert.equal(stepFromEvent, undefined);

          done();
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

    describe('getStepInstance', function () {
      it('not registerd should throw', function (done) {
        kronos.manager().then(function (manager) {
          try {
            assert.throws(function () {
              manager.getStepInstance({
                type: "not-already-registered"
              });
            });
            done();
          } catch (e) {
            done(e);
          }
        }, function () {
          done("Manager not created");
        });
      });
    });
  });

  describe('flows', function () {
    const flowName = 'flow1';

    it('registered should be present', function (done) {
      kronos.manager().then(function (myManager) {
        try {
          let flowFromEvent;
          myManager.addListener('flowRegistered', flow => flowFromEvent = flow);

          flow.registerWithManager(myManager);
          myManager.registerStep(someStepFactory);
          myManager.registerFlow(myManager.getStepInstance(flowDecl));

          const aFlow = myManager.flows[flowName];
          should.exist(aFlow);
          expect(aFlow.name).to.equal(flowName);
          expect(aFlow.state).to.equal("stopped");

          assert.equal(flowFromEvent, aFlow);
          done();
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

    it('can be removed again', function (done) {
      kronos.manager().then(function (myManager) {
        try {
          let removedStepFromEventDone = false;

          myManager.addListener('stepStateChanged', (step, oldState, newState) => {
            if (newState === 'removed' && step.name === 'flow1') {
              removedStepFromEventDone = true;
            }
          });

          flow.registerWithManager(myManager);
          myManager.registerStep(someStepFactory);
          myManager.registerFlow(myManager.getStepInstance(flowDecl));

          myManager.deleteFlow(flowName).then(function () {
            try {
              assert.equal(myManager.flows.flow1, undefined);

              // stepStateChanged may get fired late ??
              setTimeout(function () {
                assert.isTrue(removedStepFromEventDone);
                done();
              }, 10);
            } catch (e) {
              done(e);
            }
          }, done);
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });

    it('deleting unknown flow rejects', function (done) {
      kronos.manager().then(function (myManager) {
        try {
          myManager.deleteFlow("unknownFlow").then(function () {
            done(new Error("shouldn fullfill: deletion of an unknown flow"));
          }, function (reject) {
            done();
          });
        } catch (e) {
          done(e);
        }
      }, function () {
        done("Manager not created");
      });
    });
  });
});
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import AlertsTable from '../components/AlertsTable';
import {getAlerts} from '../apis';
import _ from 'lodash';

// Kevin: because we were getting strange errors saying
// "Failed prop type: Required prop `source` was not specified in `AlertsApp`."
// Tim and I decided to make the source and addFlashMessage props not required.
// FIXME: figure out why that wasn't working
const AlertsApp = React.createClass({
  propTypes: {
    source: PropTypes.shape({
      id: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      type: PropTypes.string, // 'influx-enterprise'
      links: PropTypes.shape({
        proxy: PropTypes.string.isRequired,
      }).isRequired,
    }), // .isRequired,
    addFlashMessage: PropTypes.func, // .isRequired,
  },

  getInitialState() {
    return {
      alerts: [],
    };
  },

  componentDidMount() {
    return getAlerts(this.props.source.links.proxy).then((resp) => {
      const results = [];

      const alertSeries = _.get(resp, ['data', 'results', '0', 'series'], []);

      const timeIndex = alertSeries[0].columns.findIndex((col) => col === 'time');
      const hostIndex = alertSeries[0].columns.findIndex((col) => col === 'host');
      const valueIndex = alertSeries[0].columns.findIndex((col) => col === 'value');
      const levelIndex = alertSeries[0].columns.findIndex((col) => col === 'level');
      const nameIndex = alertSeries[0].columns.findIndex((col) => col === 'name');

      alertSeries[0].values.forEach((s) => {
        results.push({
          time: `${s[timeIndex]}`,
          host: s[hostIndex],
          value: `${s[valueIndex]}`,
          level: s[levelIndex],
          name: `${s[nameIndex]}`,
        });
      });
      this.setState({alerts: results});
    });
  },

  render() {
    return (
      // I stole this from the Hosts page.
      // Perhaps we should create an abstraction?

                Alerts

              <AlertsTable source={this.props.source} alerts={this.state.alerts} />

    );
  },

});

export default AlertsApp;
<commit_msg>As a user when viewing the alerts panel, if my source does not have a kapacitor i will be prompted to configure one<commit_after>import React, {PropTypes} from 'react';
import {Link} from 'react-router';
import AlertsTable from '../components/AlertsTable';
import {getAlerts} from '../apis';
import AJAX from 'utils/ajax';
import _ from 'lodash';

// Kevin: because we were getting strange errors saying
// "Failed prop type: Required prop `source` was not specified in `AlertsApp`."
// Tim and I decided to make the source and addFlashMessage props not required.
// FIXME: figure out why that wasn't working
const AlertsApp = React.createClass({
  propTypes: {
    source: PropTypes.shape({
      id: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      type: PropTypes.string, // 'influx-enterprise'
      links: PropTypes.shape({
        proxy: PropTypes.string.isRequired,
      }).isRequired,
    }), // .isRequired,
    addFlashMessage: PropTypes.func, // .isRequired,
  },

  getInitialState() {
    return {
      loading: true,
      hasKapacitor: false,
      alerts: [],
    };
  },
  // TODO: show a loading screen until we figure out if there is a kapacitor and fetch the alerts
  componentDidMount() {
    const {source} = this.props;
    AJAX({
      url: source.links.kapacitors,
      method: 'GET',
    }).then(({data}) => {
      if (data.kapacitors[0]) {
        this.setState({hasKapacitor: true});

        this.fetchAlerts();
      } else {
        this.setState({loading: false});
      }
    });
  },

  fetchAlerts() {
    getAlerts(this.props.source.links.proxy).then((resp) => {
      const results = [];

      const alertSeries = _.get(resp, ['data', 'results', '0', 'series'], []);

      const timeIndex = alertSeries[0].columns.findIndex((col) => col === 'time');
      const hostIndex = alertSeries[0].columns.findIndex((col) => col === 'host');
      const valueIndex = alertSeries[0].columns.findIndex((col) => col === 'value');
      const levelIndex = alertSeries[0].columns.findIndex((col) => col === 'level');
      const nameIndex = alertSeries[0].columns.findIndex((col) => col === 'name');

      alertSeries[0].values.forEach((s) => {
        results.push({
          time: `${s[timeIndex]}`,
          host: s[hostIndex],
          value: `${s[valueIndex]}`,
          level: s[levelIndex],
          name: `${s[nameIndex]}`,
        });
      });
      this.setState({loading: false, alerts: results});
    });
  },

  renderSubComponents() {
    let component;
    if (this.state.loading) {
      component = (
Loading...
);
    } else {
      const {source} = this.props;
      if (this.state.hasKapacitor) {
        component = (
          <AlertsTable source={this.props.source} alerts={this.state.alerts} />
        );
      } else {
        const path = `/sources/${source.id}/kapacitor-config`;
        component = (

The current source does not have an associated Kapacitor instance, please configure one.

            <Link to={path}>Add Kapacitor</Link>

        );
      }
    }
    return component;
  },

  render() {
    return (
      // I stole this from the Hosts page.
      // Perhaps we should create an abstraction?

                Alerts

              { this.renderSubComponents() }

    );
  },

});

export default AlertsApp;
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import Dygraph from './Dygraph';
import classNames from 'classnames';
import shallowCompare from 'react-addons-shallow-compare';
import _ from 'lodash';

import timeSeriesToDygraph from 'utils/timeSeriesToDygraph';
import lastValues from 'src/shared/parsing/lastValues';

const {
  array,
  arrayOf,
  bool,
  func,
  number,
  shape,
  string,
} = PropTypes;

export default React.createClass({
  displayName: 'LineGraph',
  propTypes: {
    data: arrayOf(shape({}).isRequired).isRequired,
    ranges: shape({
      y: arrayOf(number),
      y2: arrayOf(number),
    }),
    title: string,
    isFetchingInitially: bool,
    isRefreshing: bool,
    underlayCallback: func,
    isGraphFilled: bool,
    overrideLineColors: array,
    queries: arrayOf(shape({}).isRequired).isRequired,
    showSingleStat: bool,
    activeQueryIndex: number,
    ruleValues: shape({}),
    isInDataExplorer: bool,
  },

  getDefaultProps() {
    return {
      underlayCallback: () => {},
      isGraphFilled: true,
      overrideLineColors: null,
    };
  },

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  },

  componentWillMount() {
    const {data, activeQueryIndex, isInDataExplorer} = this.props;
    this._timeSeries = timeSeriesToDygraph(data, activeQueryIndex, isInDataExplorer);
  },

  componentWillUpdate(nextProps) {
    const {data, activeQueryIndex} = this.props;
    if (data !== nextProps.data || activeQueryIndex !== nextProps.activeQueryIndex) {
      this._timeSeries = timeSeriesToDygraph(nextProps.data, nextProps.activeQueryIndex, nextProps.isInDataExplorer);
    }
  },

  render() {
    const {data, ranges, isFetchingInitially, isRefreshing, isGraphFilled, overrideLineColors, title, underlayCallback, queries, showSingleStat, ruleValues} = this.props;
    const {labels, timeSeries, dygraphSeries} = this._timeSeries;

    // If data for this graph is being fetched for the first time, show a graph-wide spinner.
    if (isFetchingInitially) {
      return (

      );
    }

    const options = {
      labels,
      connectSeparatedPoints: true,
      labelsKMB: true,
      axisLineColor: '#383846',
      gridLineColor: '#383846',
      title,
      rightGap: 0,
      yRangePad: 10,
      axisLabelWidth: 38,
      drawAxesAtZero: true,
      underlayCallback,
      ylabel: _.get(queries, ['0', 'label'], ''),
      y2label: _.get(queries, ['1', 'label'], ''),
    };

    let roundedValue;
    if (showSingleStat) {
      const lastValue = lastValues(data)[1];

      const precision = 100.0;
      roundedValue = Math.round(+lastValue * precision) / precision;
    }

    return (

        {this.renderSpinner()}
        <Dygraph
          containerStyle={{width: '100%', height: '100%'}}
          overrideLineColors={overrideLineColors}
          isGraphFilled={isGraphFilled}
          timeSeries={timeSeries}
          labels={labels}
          options={options}
          dygraphSeries={dygraphSeries}
          ranges={ranges || this.getRanges()}
          ruleValues={ruleValues}
        />
        {showSingleStat ?
{roundedValue}
 : null}

    );
  },

  renderSpinner() {
    return (

    );
  },

  getRanges() {
    const {queries} = this.props;
    if (!queries) {
      return {};
    }

    const ranges = {};
    const q0 = queries[0];
    const q1 = queries[1];

    if (q0 && q0.range) {
      ranges.y = [q0.range.lower, q0.range.upper];
    }

    if (q1 && q1.range) {
      ranges.y2 = [q1.range.lower, q1.range.upper];
    }

    return ranges;
  },
});
<commit_msg>Undo debug mode on graph spinner<commit_after>import React, {PropTypes} from 'react';
import Dygraph from './Dygraph';
import classNames from 'classnames';
import shallowCompare from 'react-addons-shallow-compare';
import _ from 'lodash';

import timeSeriesToDygraph from 'utils/timeSeriesToDygraph';
import lastValues from 'src/shared/parsing/lastValues';

const {
  array,
  arrayOf,
  bool,
  func,
  number,
  shape,
  string,
} = PropTypes;

export default React.createClass({
  displayName: 'LineGraph',
  propTypes: {
    data: arrayOf(shape({}).isRequired).isRequired,
    ranges: shape({
      y: arrayOf(number),
      y2: arrayOf(number),
    }),
    title: string,
    isFetchingInitially: bool,
    isRefreshing: bool,
    underlayCallback: func,
    isGraphFilled: bool,
    overrideLineColors: array,
    queries: arrayOf(shape({}).isRequired).isRequired,
    showSingleStat: bool,
    activeQueryIndex: number,
    ruleValues: shape({}),
    isInDataExplorer: bool,
  },

  getDefaultProps() {
    return {
      underlayCallback: () => {},
      isGraphFilled: true,
      overrideLineColors: null,
    };
  },

  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  },

  componentWillMount() {
    const {data, activeQueryIndex, isInDataExplorer} = this.props;
    this._timeSeries = timeSeriesToDygraph(data, activeQueryIndex, isInDataExplorer);
  },

  componentWillUpdate(nextProps) {
    const {data, activeQueryIndex} = this.props;
    if (data !== nextProps.data || activeQueryIndex !== nextProps.activeQueryIndex) {
      this._timeSeries = timeSeriesToDygraph(nextProps.data, nextProps.activeQueryIndex, nextProps.isInDataExplorer);
    }
  },

  render() {
    const {data, ranges, isFetchingInitially, isRefreshing, isGraphFilled, overrideLineColors, title, underlayCallback, queries, showSingleStat, ruleValues} = this.props;
    const {labels, timeSeries, dygraphSeries} = this._timeSeries;

    // If data for this graph is being fetched for the first time, show a graph-wide spinner.
    if (isFetchingInitially) {
      return (

      );
    }

    const options = {
      labels,
      connectSeparatedPoints: true,
      labelsKMB: true,
      axisLineColor: '#383846',
      gridLineColor: '#383846',
      title,
      rightGap: 0,
      yRangePad: 10,
      axisLabelWidth: 38,
      drawAxesAtZero: true,
      underlayCallback,
      ylabel: _.get(queries, ['0', 'label'], ''),
      y2label: _.get(queries, ['1', 'label'], ''),
    };

    let roundedValue;
    if (showSingleStat) {
      const lastValue = lastValues(data)[1];

      const precision = 100.0;
      roundedValue = Math.round(+lastValue * precision) / precision;
    }

    return (

        {isRefreshing ? this.renderSpinner() : null}
        <Dygraph
          containerStyle={{width: '100%', height: '100%'}}
          overrideLineColors={overrideLineColors}
          isGraphFilled={isGraphFilled}
          timeSeries={timeSeries}
          labels={labels}
          options={options}
          dygraphSeries={dygraphSeries}
          ranges={ranges || this.getRanges()}
          ruleValues={ruleValues}
        />
        {showSingleStat ?
{roundedValue}
 : null}

    );
  },

  renderSpinner() {
    return (

    );
  },

  getRanges() {
    const {queries} = this.props;
    if (!queries) {
      return {};
    }

    const ranges = {};
    const q0 = queries[0];
    const q1 = queries[1];

    if (q0 && q0.range) {
      ranges.y = [q0.range.lower, q0.range.upper];
    }

    if (q1 && q1.range) {
      ranges.y2 = [q1.range.lower, q1.range.upper];
    }

    return ranges;
  },
});
<|endoftext|>


<commit_before>import _ from 'lodash'

export const MAX_THRESHOLDS = 5
export const MIN_THRESHOLDS = 2

export const COLOR_TYPE_MIN = 'min'
export const DEFAULT_VALUE_MIN = 0
export const COLOR_TYPE_MAX = 'max'
export const DEFAULT_VALUE_MAX = 100
export const COLOR_TYPE_THRESHOLD = 'threshold'

export const THRESHOLD_TYPE_TEXT = 'text'
export const THRESHOLD_TYPE_BG = 'background'
export const THRESHOLD_TYPE_BASE = 'base'

export const THRESHOLD_COLORS = [
  {
    hex: '#BF3D5E',
    name: 'ruby',
  },
  {
    hex: '#DC4E58',
    name: 'fire',
  },
  {
    hex: '#F95F53',
    name: 'curacao',
  },
  {
    hex: '#F48D38',
    name: 'tiger',
  },
  {
    hex: '#FFB94A',
    name: 'pineapple',
  },
  {
    hex: '#FFD255',
    name: 'thunder',
  },
  {
    hex: '#7CE490',
    name: 'honeydew',
  },
  {
    hex: '#4ED8A0',
    name: 'rainforest',
  },
  {
    hex: '#32B08C',
    name: 'viridian',
  },
  {
    hex: '#4591ED',
    name: 'ocean',
  },
  {
    hex: '#22ADF6',
    name: 'pool',
  },
  {
    hex: '#00C9FF',
    name: 'laser',
  },
  {
    hex: '#513CC6',
    name: 'planet',
  },
  {
    hex: '#7A65F2',
    name: 'star',
  },
  {
    hex: '#9394FF',
    name: 'comet',
  },
  {
    hex: '#383846',
    name: 'pepper',
  },
  {
    hex: '#545667',
    name: 'graphite',
  },
  {
    hex: '#ffffff',
    name: 'white',
  },
  {
    hex: '#292933',
    name: 'castle',
  },
]

export const DEFAULT_GAUGE_COLORS = [
  {
    type: COLOR_TYPE_MIN,
    hex: THRESHOLD_COLORS[11].hex,
    id: '0',
    name: THRESHOLD_COLORS[11].name,
    value: DEFAULT_VALUE_MIN,
  },
  {
    type: COLOR_TYPE_MAX,
    hex: THRESHOLD_COLORS[14].hex,
    id: '1',
    name: THRESHOLD_COLORS[14].name,
    value: DEFAULT_VALUE_MAX,
  },
]

export const DEFAULT_THRESHOLDS_LIST_COLORS = [
  {
    type: THRESHOLD_TYPE_TEXT,
    hex: THRESHOLD_COLORS[11].hex,
    id: THRESHOLD_TYPE_BASE,
    name: THRESHOLD_COLORS[11].name,
    value: -999999999999999999,
  },
]

export const validateThresholdsListColors = (colors, type) => {
  if (!colors || colors.length === 0) {
    return DEFAULT_THRESHOLDS_LIST_COLORS
  }

  let containsBaseColor = false

  const formattedColors = colors.map(color => {
    if (color.id === THRESHOLD_TYPE_BASE) {
      // Check for existance of base color
      containsBaseColor = true
      return {...color, value: Number(color.value), type}
    }
    // Single stat colors should all have type of 'text' or 'background'
    return {...color, value: Number(color.value), type}
  })

  const formattedColorsWithBase = [
    ...formattedColors,
    DEFAULT_THRESHOLDS_LIST_COLORS[0],
  ]

  return containsBaseColor ? formattedColors : formattedColorsWithBase
}

export const getThresholdsListType = colors => {
  const type = _.get(colors, ['0', 'type'], false)

  if (type) {
    if (_.includes([THRESHOLD_TYPE_TEXT, THRESHOLD_TYPE_BG], type)) {
      return type
    }
  }

  return THRESHOLD_TYPE_TEXT
}

export const validateGaugeColors = colors => {
  if (!colors || colors.length < MIN_THRESHOLDS) {
    return DEFAULT_GAUGE_COLORS
  }

  // Gauge colors should have a type of min, any number of thresholds, and a max
  const formattedColors = _.sortBy(colors, color => Number(color.value)).map(
    color => ({
      ...color,
      value: Number(color.value),
      type: COLOR_TYPE_THRESHOLD,
    })
  )

  formattedColors[0].type = COLOR_TYPE_MIN
  formattedColors[formattedColors.length - 1].type = COLOR_TYPE_MAX

  return formattedColors
}

export const stringifyColorValues = colors => {
  return colors.map(color => ({...color, value: `${color.value}`}))
}
<commit_msg>Guard against gauges accepting incorrect color types<commit_after>import _ from 'lodash'

export const MAX_THRESHOLDS = 5
export const MIN_THRESHOLDS = 2

export const COLOR_TYPE_MIN = 'min'
export const DEFAULT_VALUE_MIN = 0
export const COLOR_TYPE_MAX = 'max'
export const DEFAULT_VALUE_MAX = 100
export const COLOR_TYPE_THRESHOLD = 'threshold'

export const THRESHOLD_TYPE_TEXT = 'text'
export const THRESHOLD_TYPE_BG = 'background'
export const THRESHOLD_TYPE_BASE = 'base'

export const THRESHOLD_COLORS = [
  {
    hex: '#BF3D5E',
    name: 'ruby',
  },
  {
    hex: '#DC4E58',
    name: 'fire',
  },
  {
    hex: '#F95F53',
    name: 'curacao',
  },
  {
    hex: '#F48D38',
    name: 'tiger',
  },
  {
    hex: '#FFB94A',
    name: 'pineapple',
  },
  {
    hex: '#FFD255',
    name: 'thunder',
  },
  {
    hex: '#7CE490',
    name: 'honeydew',
  },
  {
    hex: '#4ED8A0',
    name: 'rainforest',
  },
  {
    hex: '#32B08C',
    name: 'viridian',
  },
  {
    hex: '#4591ED',
    name: 'ocean',
  },
  {
    hex: '#22ADF6',
    name: 'pool',
  },
  {
    hex: '#00C9FF',
    name: 'laser',
  },
  {
    hex: '#513CC6',
    name: 'planet',
  },
  {
    hex: '#7A65F2',
    name: 'star',
  },
  {
    hex: '#9394FF',
    name: 'comet',
  },
  {
    hex: '#383846',
    name: 'pepper',
  },
  {
    hex: '#545667',
    name: 'graphite',
  },
  {
    hex: '#ffffff',
    name: 'white',
  },
  {
    hex: '#292933',
    name: 'castle',
  },
]

export const DEFAULT_GAUGE_COLORS = [
  {
    type: COLOR_TYPE_MIN,
    hex: THRESHOLD_COLORS[11].hex,
    id: '0',
    name: THRESHOLD_COLORS[11].name,
    value: DEFAULT_VALUE_MIN,
  },
  {
    type: COLOR_TYPE_MAX,
    hex: THRESHOLD_COLORS[14].hex,
    id: '1',
    name: THRESHOLD_COLORS[14].name,
    value: DEFAULT_VALUE_MAX,
  },
]

export const DEFAULT_THRESHOLDS_LIST_COLORS = [
  {
    type: THRESHOLD_TYPE_TEXT,
    hex: THRESHOLD_COLORS[11].hex,
    id: THRESHOLD_TYPE_BASE,
    name: THRESHOLD_COLORS[11].name,
    value: -999999999999999999,
  },
]

export const validateThresholdsListColors = (colors, type) => {
  if (!colors || colors.length === 0) {
    return DEFAULT_THRESHOLDS_LIST_COLORS
  }

  let containsBaseColor = false

  const formattedColors = colors.map(color => {
    if (color.id === THRESHOLD_TYPE_BASE) {
      // Check for existance of base color
      containsBaseColor = true
      return {...color, value: Number(color.value), type}
    }
    // Single stat colors should all have type of 'text' or 'background'
    return {...color, value: Number(color.value), type}
  })

  const formattedColorsWithBase = [
    ...formattedColors,
    DEFAULT_THRESHOLDS_LIST_COLORS[0],
  ]

  return containsBaseColor ? formattedColors : formattedColorsWithBase
}

export const getThresholdsListType = colors => {
  const type = _.get(colors, ['0', 'type'], false)

  if (type) {
    if (_.includes([THRESHOLD_TYPE_TEXT, THRESHOLD_TYPE_BG], type)) {
      return type
    }
  }

  return THRESHOLD_TYPE_TEXT
}

export const validateGaugeColors = colors => {
  if (!colors || colors.length < MIN_THRESHOLDS) {
    return DEFAULT_GAUGE_COLORS
  }

  const colorsAreIncorrectType =
    colors.reduce((acc, color) => {
      if (
        color.type === COLOR_TYPE_MIN ||
        color.type === COLOR_TYPE_MAX ||
        color.type === COLOR_TYPE_THRESHOLD
      ) {
        return (acc += 1)
      }
    }, 0) !== colors.length

  if (colorsAreIncorrectType) {
    return DEFAULT_GAUGE_COLORS
  }

  // Gauge colors should have a type of min, any number of thresholds, and a max
  const formattedColors = _.sortBy(colors, color => Number(color.value)).map(
    color => ({
      ...color,
      value: Number(color.value),
      type: COLOR_TYPE_THRESHOLD,
    })
  )

  formattedColors[0].type = COLOR_TYPE_MIN
  formattedColors[formattedColors.length - 1].type = COLOR_TYPE_MAX

  return formattedColors
}

export const stringifyColorValues = colors => {
  return colors.map(color => ({...color, value: `${color.value}`}))
}
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import {NavBar, NavBlock, NavHeader, NavListItem} from 'src/side_nav/components/NavItems';

const {string} = PropTypes;
const SideNav = React.createClass({
  propTypes: {
    location: string.isRequired,
  },

  render() {
    const {location} = this.props;

    return (
      <NavBar location={location}>

        <NavBlock matcher={'users'} icon={"access-key"} link={`/users`}>
          <NavHeader link={`/users`} title="Web Admin" />
          <NavListItem matcher={'users'} link={`/users`}>Users</NavListItem>
        </NavBlock>
        <NavBlock matcher="overview" icon="crown" link={`/overview`}>
          <NavHeader link={`/overview`} title="Cluster" />
          <NavListItem matcher="overview" link={`/overview`}>Overview</NavListItem>
          <NavListItem matcher="queries" link={`/queries`}>Queries</NavListItem>
          <NavListItem matcher="tasks" link={`/tasks`}>Tasks</NavListItem>
          <NavListItem matcher="roles" link={`/roles`}>Roles</NavListItem>
          <NavListItem matcher="accounts" link={`/accounts`}>Cluster Accounts</NavListItem>
          <NavListItem matcher="manager" link={`/databases/manager/_internal`}>Database Manager</NavListItem>
          <NavListItem matcher="retentionpolicies" link={`/databases/retentionpolicies/_internal`}>Retention Policies</NavListItem>
        </NavBlock>
        <NavBlock matcher="chronograf" icon="graphline" link={`/chronograf/data_explorer`}>
          <NavHeader link={`/chronograf/data_explorer`} title={'Chronograf'} />
          <NavListItem matcher={'data_explorer'} link={`/chronograf/data_explorer`}>Data Explorer</NavListItem>
        </NavBlock>
        <NavBlock matcher="settings" icon="user-outline" link={`/account/settings`}>
          <NavHeader link={`/account/settings`} title="My Account" />
          <NavListItem matcher="settings" link={`/account/settings`}>Settings</NavListItem>
          Logout
        </NavBlock>
      </NavBar>
    );
  },
});

export default SideNav;
<commit_msg>add hosts list to left rail<commit_after>import React, {PropTypes} from 'react';
import {NavBar, NavBlock, NavHeader, NavListItem} from 'src/side_nav/components/NavItems';

const {string} = PropTypes;
const SideNav = React.createClass({
  propTypes: {
    location: string.isRequired,
  },

  render() {
    const {location} = this.props;

    return (
      <NavBar location={location}>

        <NavBlock matcher={'users'} icon={"access-key"} link={`/users`}>
          <NavHeader link={`/users`} title="Web Admin" />
          <NavListItem matcher={'users'} link={`/users`}>Users</NavListItem>
        </NavBlock>
        <NavBlock matcher="overview" icon="crown" link={`/overview`}>
          <NavHeader link={`/overview`} title="Cluster" />
          <NavListItem matcher="overview" link={`/overview`}>Overview</NavListItem>
          <NavListItem matcher="queries" link={`/queries`}>Queries</NavListItem>
          <NavListItem matcher="tasks" link={`/tasks`}>Tasks</NavListItem>
          <NavListItem matcher="roles" link={`/roles`}>Roles</NavListItem>
          <NavListItem matcher="accounts" link={`/accounts`}>Cluster Accounts</NavListItem>
          <NavListItem matcher="manager" link={`/databases/manager/_internal`}>Database Manager</NavListItem>
          <NavListItem matcher="retentionpolicies" link={`/databases/retentionpolicies/_internal`}>Retention Policies</NavListItem>
        </NavBlock>
        <NavBlock matcher="chronograf" icon="graphline" link={`/chronograf/data_explorer`}>
          <NavHeader link={`/chronograf/data_explorer`} title={'Chronograf'} />
          <NavListItem matcher={'data_explorer'} link={`/chronograf/data_explorer`}>Data Explorer</NavListItem>
        </NavBlock>
        <NavBlock matcher="settings" icon="user-outline" link={`/account/settings`}>
          <NavHeader link={`/account/settings`} title="My Account" />
          <NavListItem matcher="settings" link={`/account/settings`}>Settings</NavListItem>
          Logout
        </NavBlock>
        <NavBlock matcher="hosts" icon="crown" link={`/hosts/list`}>
          <NavHeader link={`/hosts/list`} title="Infrastructure" />
          <NavListItem matcher="list" link={`/list`}>Host List</NavListItem>
        </NavBlock>
      </NavBar>
    );
  },
});

export default SideNav;
<|endoftext|>


<commit_before>const fs = require('fs')
const tape = require('tape')
const Kernel = require('ewasm-kernel')
const ethUtil = require('ethereumjs-util')
const compiler = require('../index.js')
const dir = `${__dirname}/opcode`

let testFiles = fs.readdirSync(dir).filter((name) => name.endsWith('.json'))

tape('testing EVM1 Ops', (t) => {
  testFiles.forEach((path) => {
    let opTest = require(`${dir}/${path}`)
    opTest.forEach((test) => {
      const testInstance = buildTest(test.op)

      // populate the stack with predefined values
      t.comment(`testing ${test.description}`)
      test.stack.in.reverse().forEach((item, index) => {
        item = Uint8Array.from(ethUtil.setLength(new Buffer(item.slice(2), 'hex'), 32)).reverse()
        new Uint8Array(testInstance.exports.memory).set(item, index * 32)
      })

      // Runs the opcode. An empty stack must start with the stack pointer at -8.
      // also we have to add 8 to the resulting sp to accommodate for the fact
      // that the sp is pointing to memory segment holding the last stack item
      let sp = testInstance.exports[test.op](test.stack.in.length * 32 - 8) + 8
      t.equal(sp / 32, test.stack.out.length, 'should have corrent number of items on the stack')
      sp = 0

      // compare the output stack against the predefined values
      test.stack.out.forEach((item, index) => {
        const expectedItem = new Uint8Array(ethUtil.setLength(new Buffer(item.slice(2), 'hex'), 32)).reverse()
        const result = new Uint8Array(testInstance.exports.memory).slice(sp, sp = sp + 32)
        t.equals(result.toString(), expectedItem.toString(), 'should have correct item on stack')
      })
    })
  })
  t.end()
})

function buildTest (op) {
  const funcs = compiler.resolveFunctions(new Set([op]))
  const linked = compiler.buildModule(funcs, [], [op])
  const wasm = compiler.compileWAST(linked)
  return Kernel.codeHandler(wasm)
}
<commit_msg>Split out hexToUint8Array helper<commit_after>const fs = require('fs')
const tape = require('tape')
const Kernel = require('ewasm-kernel')
const ethUtil = require('ethereumjs-util')
const compiler = require('../index.js')
const dir = `${__dirname}/opcode`

let testFiles = fs.readdirSync(dir).filter((name) => name.endsWith('.json'))

tape('testing EVM1 Ops', (t) => {
  testFiles.forEach((path) => {
    let opTest = require(`${dir}/${path}`)
    opTest.forEach((test) => {
      const testInstance = buildTest(test.op)

      // populate the stack with predefined values
      t.comment(`testing ${test.description}`)
      test.stack.in.reverse().forEach((item, index) => {
        item = hexToUint8Array(item)
        new Uint8Array(testInstance.exports.memory).set(item, index * 32)
      })

      // Runs the opcode. An empty stack must start with the stack pointer at -8.
      // also we have to add 8 to the resulting sp to accommodate for the fact
      // that the sp is pointing to memory segment holding the last stack item
      let sp = testInstance.exports[test.op](test.stack.in.length * 32 - 8) + 8
      t.equal(sp / 32, test.stack.out.length, 'should have corrent number of items on the stack')
      sp = 0

      // compare the output stack against the predefined values
      test.stack.out.forEach((item, index) => {
        const expectedItem = hexToUint8Array(item)
        const result = new Uint8Array(testInstance.exports.memory).slice(sp, sp = sp + 32)
        t.equals(result.toString(), expectedItem.toString(), 'should have correct item on stack')
      })
    })
  })
  t.end()
})

function buildTest (op) {
  const funcs = compiler.resolveFunctions(new Set([op]))
  const linked = compiler.buildModule(funcs, [], [op])
  const wasm = compiler.compileWAST(linked)
  return Kernel.codeHandler(wasm)
}

function hexToUint8Array(item, length) {
  return new Uint8Array(ethUtil.setLength(new Buffer(item.slice(2), 'hex'), 32)).reverse()
}
<|endoftext|>


<commit_before>$(function() {

    $(".member-remove").click(function() {
        var $this = $(this);

        $("#rtm-email").text($this.data("email"));
        $("#remove-team-member-email").val($this.data("email"));
        $('#remove-team-member-modal').modal("show");

        return false;
    });

});<commit_msg>auto-focus input elements in Account Settings modal dialogs.<commit_after>$(function() {

    $(".member-remove").click(function() {
        var $this = $(this);

        $("#rtm-email").text($this.data("email"));
        $("#remove-team-member-email").val($this.data("email"));
        $('#remove-team-member-modal').modal("show");

        return false;
    });

    $('#invite-team-member-modal').on('shown.bs.modal', function () {
        $('#itm-email').focus();
    })

    $('#set-team-name-modal').on('shown.bs.modal', function () {
        $('#team-name').focus();
    })

});<|endoftext|>


<commit_before>/*
    Copyright 2014 UW Information Technology, University of Washington

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    Changes:

    =================================================================

*/

window.spacescout_reviews = {
    review_char_limit: 300,
    pagination: 5
};

function setupRatingsAndReviews(data) {

    $('.space-ratings-and-reviews').html(Handlebars.compile($('#space_reviews').html())());

    if (data && data.length) {
        showRatingEditorButton();
    }

    $('.space-ratings-and-reviews h4 .write-a-review').on('click', function (e) {
        showRatingEditor();
    });

    $('.space-review-compose textarea').keyup(function (e) {
        var l = $(this).val().length,
            remaining = window.spacescout_reviews.review_char_limit - l,
            span = $('#space-review-remaining');

        if (remaining > 0) {
            span.html(remaining);
            span.removeClass('required');
        } else {
            span.html(0);
            span.addClass('required');
        }

        if (l > 0 && $('.space-review-compose .space-review-stars i.fa-star').length) {
            enableSubmitButton();
        } else {
            disableSubmitButton();
        }
    });

    $('button#space-review-cancel').click(function (e) {
        $('.space-review-compose').hide(400);
        if ($('.space-reviews-none').length) {
            $('.space-reviews-none').show();
        } else {
            showRatingEditorButton();
        }

        tidyUpRatesComposer();
    });

    $('button#space-review-submit').click(function (e) {
        var id = $(e.target).closest('div[id^=detail_container_]').attr('id').match(/_(\d+)$/)[1],
            node = $('.space-review-compose'),
            review = {
                rating: $('.space-review-rating i.fa-star', node).length,
                review: $('textarea', node).val().trim()
            };

        // validate
        if (review.rating < 1 || review.rating > 5 || review.review.length <= 0) {
            return;
        }

        // defer until authenticated
        if (window.spacescout_authenticated_user.length == 0) {
            $.cookie('space_review', JSON.stringify({ id: id, review: review }));
            window.location.href = '/login?next=' + window.location.pathname;
        }

        postRatingAndReview(id, review);
    });

    $('.space-review-submitted a').click(function (e) {
        $('.space-review-submitted').hide(400);

        if ($('.space-reviews-none').length) {
            $('.space-reviews-none').show();
        } else {
            showRatingEditorButton();
        }
    });

    $(document).on('click', '.space-review-stars i', function (e) {
        var target = $(e.target),
            stars = target.prevAll('i'),
            rating = stars.length + 1,
            ratings = ['', gettext('terrible'), gettext('poor'),
                       gettext('average'), gettext('good'), gettext('excellent')];

        stars.switchClass('fa-star-o', 'fa-star');
        target.switchClass('fa-star-o', 'fa-star');
        target.nextAll('i').switchClass('fa-star', 'fa-star-o');
        $('.space-review-stars span:last-child').html(ratings[rating]);
        if ($('.space-review-compose textarea').val().length) {
            enableSubmitButton();
        }
    });

    $('.space-review-compose a').on('click', function () {
        if ($(this).next().is(':visible')) {
            hideReviewGuidelines();
        } else {
            showReviewGuidelines();
        }
    });

    $(document).on('loadSpaceReviews', function (e, id) {
        var review = $.cookie('space_review');

        if (review && window.spacescout_authenticated_user.length > 0) {
            var json_review = review ? JSON.parse(review) : null;

            if (json_review) {
                postRatingAndReview(json_review.id, json_review.review);
                $('.space-reviews-none').hide();
            }

            $.removeCookie('space_review');
        }

        $('#show_reviews').css('cursor', 'pointer');
        $('#show_reviews').click(function () {
            $('.space-detail-body').animate({ scrollTop: $('.space-ratings-and-reviews').offset().top
                                              + $('.space-detail-body').scrollTop()
                                              - $('.space-detail-body').offset().top }, '500');
        });
    });
}

function loadRatingsAndReviews(id, review_container, rating_container) {
    $.ajax({
        url: '/web_api/v1/space/' + id + '/reviews',
        success: function (data) {
            var template = Handlebars.compile($('#space_reviews_review').html()),
                rating_sum = 0,
                total_reviews = (data && data.length) ? data.length : 0,
                html_count = Handlebars.compile($('#space_reviews_count').html())({ count : total_reviews }),
                node;

            review_container.html('');

            $('span#review_count', rating_container).html(html_count);

            if (total_reviews > 0) {

                data.sort(function (a, b) {
                    return new Date(b.date_submitted) - new Date(a.date_submitted);
                });

                $.each(data, function(i) {
                    var review = this,
                        rating = this.rating,
                        date = new Date(this.date_submitted);

                    node = $(template({
                        reviewer: this.reviewer,
                        review: (this.review && this.review.length) ? this.review : 'No review provided',
                        date: date ? monthname_from_month(date.getMonth()) + ' ' + date.getDate() + ', ' + date.getFullYear() : ''
                    }));

                    rating_sum += parseInt(this.rating);

                    $('.space-review-header i', node).each(function(i) {
                        if (i < rating) {
                            $(this).switchClass('fa-star-o', 'fa-star');
                        }
                    });

                    if (i >= window.spacescout_reviews.pagination) {
                        node.hide();
                    }

                    review_container.append(node);
                });

                if (data.length > window.spacescout_reviews.pagination) {
                    node = Handlebars.compile($('#more_space_reviews').html())();
                    review_container.append(node);

                    $('.more-space-reviews a').on('click', function (e) {
                        $('.space-reviews-review:hidden').each(function (i) {
                            if (i < window.spacescout_reviews.pagination) {
                                $(this).slideDown(400);
                            }
                        });

                        if ($('.space-reviews-review:hidden').length <= 0) {
                            $(this).parent().hide();
                        }
                    });
                }

                if (rating_sum) {
                    var avg = Math.ceil((rating_sum) / data.length);

                    $('i', rating_container).each(function(i) {
                        if (i < avg) {
                            $(this).switchClass('fa-star-o', 'fa-star');
                        }
                    });
                }

                showRatingEditorButton();
            } else if ($('.space-review-compose').length) {
                hideRatingEditorButton();
                review_container.html(Handlebars.compile($('#no_space_reviews').html())());
                $('.write-a-review', review_container).on('click', function (e) {
                    showRatingEditor();
                });
            } else {
                review_container.remove();
            }

            $.event.trigger('loadSpaceReviews', [ id ]);
        },
        error: function (xhr, textStatus, errorThrown) {
            console.log('Unable to load reviews: ' + xhr.responseText);
        }
    });
}

function postRatingAndReview(id, review) {
    $.ajax({
        url: '/web_api/v1/space/' + id + '/reviews',
        dataType: 'json',
        contentType: "application/json",
        data: JSON.stringify(review),
        type: "POST",
        success: function (data) {
            tidyUpRatesComposer();
            $('.space-review-compose').hide(400);
            $('.space-review-submitted').show(400);
        },
        error: function (xhr, textStatus, errorThrown) {
            console.log('Unable to post reviews: ' + xhr.responseText);
        }
    });
}

function showRatingEditor () {
    disableSubmitButton();
    hideRatingEditorButton();
    $('#space-review-remaining').html(window.spacescout_reviews.review_char_limit);
    $('.space-reviews-none').hide(400);
    $('.space-review-compose').show(400, function(){
        var w_node = $('.space-detail-body'),
            w_top = w_node.scrollTop(),
            w_height = w_node.height(),
            t_node = $(this),
            t_top = t_node.offset().top - w_node.offset().top,
            t_height = t_node.height(),
            diff = Math.ceil((t_top + t_height) - (w_top + w_height)),
            padding = 20;

        if (diff > 0) {
            w_node.animate( {scrollTop: (diff + w_top + padding)}, '500');
        }
    });
}

function tidyUpRatesComposer () {
    var node = $('.space-review-compose');

    disableSubmitButton();
    hideReviewGuidelines();
    $('textarea', node).val('');
    $('.space-review-stars span + span', node).html('');
    $('#space-review-remaining').html(window.spacescout_reviews.review_char_limit);
    $('.space-review-rating div + div span', node).removeClass('required');
    $('.space-review-rating i', node).switchClass('fa-star', 'fa-star-o');
}

function showRatingEditorButton () {
    $('.space-ratings-and-reviews h4 button.write-a-review').show();
}

function hideRatingEditorButton () {
    $('.space-ratings-and-reviews h4 button.write-a-review').hide();
}

function enableSubmitButton () {
    $('button#space-review-submit').removeAttr('disabled');
}

function disableSubmitButton () {
    $('button#space-review-submit').attr('disabled', 'disabled');
}

function showReviewGuidelines () {
    var ul = $('.space-review-compose ul');

    ul.show();
    ul.prev().find('i').switchClass('fs-angle-double-down', 'fs-angle-double-up');
}

function hideReviewGuidelines () {
    var ul = $('.space-review-compose ul');

    ul.hide();
    ul.prev().find('i').switchClass('fs-angle-double-up', 'fs-angle-double-down');
};
<commit_msg>more tidying of review composer on cancel<commit_after>/*
    Copyright 2014 UW Information Technology, University of Washington

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

    Changes:

    =================================================================

*/

window.spacescout_reviews = {
    review_char_limit: 300,
    pagination: 5
};

function setupRatingsAndReviews(data) {

    $('.space-ratings-and-reviews').html(Handlebars.compile($('#space_reviews').html())());

    if (data && data.length) {
        showRatingEditorButton();
    }

    $('.space-ratings-and-reviews h4 .write-a-review').on('click', function (e) {
        showRatingEditor();
    });

    $('.space-review-compose textarea').keyup(function (e) {
        var l = $(this).val().length,
            remaining = window.spacescout_reviews.review_char_limit - l,
            span = $('#space-review-remaining');

        if (remaining > 0) {
            span.html(remaining);
            span.removeClass('required');
        } else {
            span.html(0);
            span.addClass('required');
        }

        if (l > 0 && $('.space-review-compose .space-review-stars i.fa-star').length) {
            enableSubmitButton();
        } else {
            disableSubmitButton();
        }
    });

    $('button#space-review-cancel').click(function (e) {
        $('.space-review-compose').hide(400);
        if ($('.space-reviews-none').length) {
            $('.space-reviews-none').show();
        } else {
            showRatingEditorButton();
        }

        tidyUpRatesComposer();
    });

    $('button#space-review-submit').click(function (e) {
        var id = $(e.target).closest('div[id^=detail_container_]').attr('id').match(/_(\d+)$/)[1],
            node = $('.space-review-compose'),
            review = {
                rating: $('.space-review-rating i.fa-star', node).length,
                review: $('textarea', node).val().trim()
            };

        // validate
        if (review.rating < 1 || review.rating > 5 || review.review.length <= 0) {
            return;
        }

        // defer until authenticated
        if (window.spacescout_authenticated_user.length == 0) {
            $.cookie('space_review', JSON.stringify({ id: id, review: review }));
            window.location.href = '/login?next=' + window.location.pathname;
        }

        postRatingAndReview(id, review);
    });

    $('.space-review-submitted a').click(function (e) {
        $('.space-review-submitted').hide(400);

        if ($('.space-reviews-none').length) {
            $('.space-reviews-none').show();
        } else {
            showRatingEditorButton();
        }
    });

    $(document).on('click', '.space-review-stars i', function (e) {
        var target = $(e.target),
            stars = target.prevAll('i'),
            rating = stars.length + 1,
            ratings = ['', gettext('terrible'), gettext('poor'),
                       gettext('average'), gettext('good'), gettext('excellent')];

        stars.switchClass('fa-star-o', 'fa-star');
        target.switchClass('fa-star-o', 'fa-star');
        target.nextAll('i').switchClass('fa-star', 'fa-star-o');
        $('.space-review-stars span:last-child').html(ratings[rating]);
        if ($('.space-review-compose textarea').val().length) {
            enableSubmitButton();
        }
    });

    $('.space-review-compose a').on('click', function () {
        if ($(this).next().is(':visible')) {
            hideReviewGuidelines();
        } else {
            showReviewGuidelines();
        }
    });

    $(document).on('loadSpaceReviews', function (e, id) {
        var review = $.cookie('space_review');

        if (review && window.spacescout_authenticated_user.length > 0) {
            var json_review = review ? JSON.parse(review) : null;

            if (json_review) {
                postRatingAndReview(json_review.id, json_review.review);
                $('.space-reviews-none').hide();
            }

            $.removeCookie('space_review');
        }

        $('#show_reviews').css('cursor', 'pointer');
        $('#show_reviews').click(function () {
            $('.space-detail-body').animate({ scrollTop: $('.space-ratings-and-reviews').offset().top
                                              + $('.space-detail-body').scrollTop()
                                              - $('.space-detail-body').offset().top }, '500');
        });
    });
}

function loadRatingsAndReviews(id, review_container, rating_container) {
    $.ajax({
        url: '/web_api/v1/space/' + id + '/reviews',
        success: function (data) {
            var template = Handlebars.compile($('#space_reviews_review').html()),
                rating_sum = 0,
                total_reviews = (data && data.length) ? data.length : 0,
                html_count = Handlebars.compile($('#space_reviews_count').html())({ count : total_reviews }),
                node;

            review_container.html('');

            $('span#review_count', rating_container).html(html_count);

            if (total_reviews > 0) {

                data.sort(function (a, b) {
                    return new Date(b.date_submitted) - new Date(a.date_submitted);
                });

                $.each(data, function(i) {
                    var review = this,
                        rating = this.rating,
                        date = new Date(this.date_submitted);

                    node = $(template({
                        reviewer: this.reviewer,
                        review: (this.review && this.review.length) ? this.review : 'No review provided',
                        date: date ? monthname_from_month(date.getMonth()) + ' ' + date.getDate() + ', ' + date.getFullYear() : ''
                    }));

                    rating_sum += parseInt(this.rating);

                    $('.space-review-header i', node).each(function(i) {
                        if (i < rating) {
                            $(this).switchClass('fa-star-o', 'fa-star');
                        }
                    });

                    if (i >= window.spacescout_reviews.pagination) {
                        node.hide();
                    }

                    review_container.append(node);
                });

                if (data.length > window.spacescout_reviews.pagination) {
                    node = Handlebars.compile($('#more_space_reviews').html())();
                    review_container.append(node);

                    $('.more-space-reviews a').on('click', function (e) {
                        $('.space-reviews-review:hidden').each(function (i) {
                            if (i < window.spacescout_reviews.pagination) {
                                $(this).slideDown(400);
                            }
                        });

                        if ($('.space-reviews-review:hidden').length <= 0) {
                            $(this).parent().hide();
                        }
                    });
                }

                if (rating_sum) {
                    var avg = Math.ceil((rating_sum) / data.length);

                    $('i', rating_container).each(function(i) {
                        if (i < avg) {
                            $(this).switchClass('fa-star-o', 'fa-star');
                        }
                    });
                }

                showRatingEditorButton();
            } else if ($('.space-review-compose').length) {
                hideRatingEditorButton();
                review_container.html(Handlebars.compile($('#no_space_reviews').html())());
                $('.write-a-review', review_container).on('click', function (e) {
                    showRatingEditor();
                });
            } else {
                review_container.remove();
            }

            $.event.trigger('loadSpaceReviews', [ id ]);
        },
        error: function (xhr, textStatus, errorThrown) {
            console.log('Unable to load reviews: ' + xhr.responseText);
        }
    });
}

function postRatingAndReview(id, review) {
    $.ajax({
        url: '/web_api/v1/space/' + id + '/reviews',
        dataType: 'json',
        contentType: "application/json",
        data: JSON.stringify(review),
        type: "POST",
        success: function (data) {
            tidyUpRatesComposer();
            $('.space-review-compose').hide(400);
            $('.space-review-submitted').show(400);
        },
        error: function (xhr, textStatus, errorThrown) {
            console.log('Unable to post reviews: ' + xhr.responseText);
        }
    });
}

function showRatingEditor () {
    disableSubmitButton();
    hideRatingEditorButton();
    $('#space-review-remaining').html(window.spacescout_reviews.review_char_limit);
    $('.space-reviews-none').hide(400);
    $('.space-review-compose').show(400, function(){
        var w_node = $('.space-detail-body'),
            w_top = w_node.scrollTop(),
            w_height = w_node.height(),
            t_node = $(this),
            t_top = t_node.offset().top - w_node.offset().top,
            t_height = t_node.height(),
            diff = Math.ceil((t_top + t_height) - (w_top + w_height)),
            padding = 20;

        if (diff > 0) {
            w_node.animate( {scrollTop: (diff + w_top + padding)}, '500');
        }
    });
}

function tidyUpRatesComposer () {
    var node = $('.space-review-compose');

    disableSubmitButton();
    hideReviewGuidelines();
    $('textarea', node).val('');
    $('.space-review-stars span + span', node).html('');
    $('#space-review-remaining').html(window.spacescout_reviews.review_char_limit);
    $('#space-review-remaining').removeClass('required');
    $('.space-review-rating div + div span', node).removeClass('required');
    $('.space-review-rating i', node).switchClass('fa-star', 'fa-star-o');
}

function showRatingEditorButton () {
    $('.space-ratings-and-reviews h4 button.write-a-review').show();
}

function hideRatingEditorButton () {
    $('.space-ratings-and-reviews h4 button.write-a-review').hide();
}

function enableSubmitButton () {
    $('button#space-review-submit').removeAttr('disabled');
}

function disableSubmitButton () {
    $('button#space-review-submit').attr('disabled', 'disabled');
}

function showReviewGuidelines () {
    var ul = $('.space-review-compose ul');

    ul.show();
    ul.prev().find('i').switchClass('fs-angle-double-down', 'fs-angle-double-up');
}

function hideReviewGuidelines () {
    var ul = $('.space-review-compose ul');

    ul.hide();
    ul.prev().find('i').switchClass('fs-angle-double-up', 'fs-angle-double-down');
};
<|endoftext|>


<commit_before>import $ from "jquery";
import tippy, {delegate} from "tippy.js";

import render_left_sidebar_stream_setting_popover from "../templates/left_sidebar_stream_setting_popover.hbs";

import * as popovers from "./popovers";
import * as reactions from "./reactions";
import * as rows from "./rows";
import * as settings_data from "./settings_data";

// We override the defaults set by tippy library here,
// so make sure to check this too after checking tippyjs
// documentation for default properties.
tippy.setDefaultProps({
    // We don't want tooltips
    // to take more space than
    // mobile widths ever.
    maxWidth: 300,

    // Some delay to showing / hiding the tooltip makes
    // it look less forced and more natural.
    delay: [100, 20],
    placement: "auto",

    // disable animations to make the
    // tooltips feel snappy
    animation: false,

    // Show tooltips on long press on touch based
    // devices.
    touch: ["hold", 750],

    // This has the side effect of some properties of parent applying to
    // tooltips.
    appendTo: "parent",

    // html content is not supported by default
    // enable it by passing data-tippy-allowHtml="true"
    // in the tag or a parameter.
});

export function initialize() {
    delegate("body", {
        // Add elements here which are not displayed on
        // initial load but are displayed later through
        // some means.
        //
        // Make all html elements having this class
        // show tippy styled tooltip on hover.
        target: ".tippy-zulip-tooltip",
    });

    // message reaction tooltip showing who reacted.
    let observer;
    delegate("body", {
        target: ".message_reaction, .reaction_button",
        placement: "bottom",
        onShow(instance) {
            const elem = $(instance.reference);
            if (!instance.reference.classList.contains("reaction_button")) {
                const local_id = elem.attr("data-reaction-id");
                const message_id = rows.get_message_id(instance.reference);
                const title = reactions.get_reaction_title_data(message_id, local_id);
                instance.setContent(title);
            }

            // Use MutationObserver to check for removal of nodes on which tooltips
            // are still active.
            // We target the message table and check for removal of it, it's children
            // and the reactions individually down in the subtree.
            const target_node = elem.parents(".message_table.focused_table").get(0);
            const nodes_to_check_for_removal = [
                elem.parents(".recipient_row").get(0),
                elem.parents(".message_reactions").get(0),
                elem.get(0),
            ];
            const config = {attributes: false, childList: true, subtree: true};

            const callback = function (mutationsList) {
                for (const mutation of mutationsList) {
                    for (const node of nodes_to_check_for_removal) {
                        // Hide instance if reference is in the removed node list.
                        if (Array.prototype.includes.call(mutation.removedNodes, node)) {
                            instance.hide();
                        }
                    }
                }
            };
            observer = new MutationObserver(callback);
            observer.observe(target_node, config);
        },
        onHidden(instance) {
            instance.destroy();
            observer.disconnect();
        },
        appendTo: () => document.body,
    });

    delegate("body", {
        target: ".compose_control_button",
        placement: "top",
        // Add some additional delay when they open
        // so that regular users don't have to see
        // them unless they want to.
        delay: [300, 20],
    });

    delegate("body", {
        target: ".message_control_button",
        placement: "top",
        // Add some additional delay when they open
        // so that regular users don't have to see
        // them unless they want to.
        delay: [300, 20],
        onShow(instance) {
            // Handle dynamic "starred messages" and "edit" widgets.
            const elem = $(instance.reference);
            let content = elem.attr("data-tippy-content");
            if (content === undefined) {
                // Tippy cannot get the content for message edit button
                // as it is dynamically inserted based on editability.
                // So, we have to manually get the i element to get the
                // content from it.
                //
                // TODO: Change the template structure so logic is unnecessary.
                const edit_button = elem.find("i.edit_content_button");
                content = edit_button.attr("data-tippy-content");
            }
            if (content === undefined) {
                // If content is still undefined it is because content
                // is specified on inner i tags and is handled by our
                // general tippy-zulip-tooltip class. So we return
                // false here to avoid showing an extra empty tooltip
                // for such cases.
                return false;
            }
            instance.setContent(content);
            return true;
        },
    });

    delegate("body", {
        delay: 0,
        target: "#streams_inline_cog",
        onShow(instance) {
            popovers.hide_all_except_sidebars();
            instance.setContent(
                render_left_sidebar_stream_setting_popover({
                    can_create_streams: settings_data.user_can_create_streams(),
                }),
            );
            $(instance.popper).on("click", instance.hide);
        },
        appendTo: () => document.body,
        trigger: "click",
        allowHTML: true,
        interactive: true,
        hideOnClick: true,
        theme: "light-border",
    });
}
<commit_msg>tippyjs: Fix blueslip error on popover add emoji reaction hover.<commit_after>import $ from "jquery";
import tippy, {delegate} from "tippy.js";

import render_left_sidebar_stream_setting_popover from "../templates/left_sidebar_stream_setting_popover.hbs";

import * as popovers from "./popovers";
import * as reactions from "./reactions";
import * as rows from "./rows";
import * as settings_data from "./settings_data";

// We override the defaults set by tippy library here,
// so make sure to check this too after checking tippyjs
// documentation for default properties.
tippy.setDefaultProps({
    // We don't want tooltips
    // to take more space than
    // mobile widths ever.
    maxWidth: 300,

    // Some delay to showing / hiding the tooltip makes
    // it look less forced and more natural.
    delay: [100, 20],
    placement: "auto",

    // disable animations to make the
    // tooltips feel snappy
    animation: false,

    // Show tooltips on long press on touch based
    // devices.
    touch: ["hold", 750],

    // This has the side effect of some properties of parent applying to
    // tooltips.
    appendTo: "parent",

    // html content is not supported by default
    // enable it by passing data-tippy-allowHtml="true"
    // in the tag or a parameter.
});

export function initialize() {
    delegate("body", {
        // Add elements here which are not displayed on
        // initial load but are displayed later through
        // some means.
        //
        // Make all html elements having this class
        // show tippy styled tooltip on hover.
        target: ".tippy-zulip-tooltip",
    });

    // message reaction tooltip showing who reacted.
    let observer;
    delegate("body", {
        target: ".message_reaction, .message_reactions .reaction_button",
        placement: "bottom",
        onShow(instance) {
            const elem = $(instance.reference);
            if (!instance.reference.classList.contains("reaction_button")) {
                const local_id = elem.attr("data-reaction-id");
                const message_id = rows.get_message_id(instance.reference);
                const title = reactions.get_reaction_title_data(message_id, local_id);
                instance.setContent(title);
            }

            // Use MutationObserver to check for removal of nodes on which tooltips
            // are still active.
            // We target the message table and check for removal of it, it's children
            // and the reactions individually down in the subtree.
            const target_node = elem.parents(".message_table.focused_table").get(0);
            const nodes_to_check_for_removal = [
                elem.parents(".recipient_row").get(0),
                elem.parents(".message_reactions").get(0),
                elem.get(0),
            ];
            const config = {attributes: false, childList: true, subtree: true};

            const callback = function (mutationsList) {
                for (const mutation of mutationsList) {
                    for (const node of nodes_to_check_for_removal) {
                        // Hide instance if reference is in the removed node list.
                        if (Array.prototype.includes.call(mutation.removedNodes, node)) {
                            instance.hide();
                        }
                    }
                }
            };
            observer = new MutationObserver(callback);
            observer.observe(target_node, config);
        },
        onHidden(instance) {
            instance.destroy();
            observer.disconnect();
        },
        appendTo: () => document.body,
    });

    delegate("body", {
        target: ".compose_control_button",
        placement: "top",
        // Add some additional delay when they open
        // so that regular users don't have to see
        // them unless they want to.
        delay: [300, 20],
    });

    delegate("body", {
        target: ".message_control_button",
        placement: "top",
        // Add some additional delay when they open
        // so that regular users don't have to see
        // them unless they want to.
        delay: [300, 20],
        onShow(instance) {
            // Handle dynamic "starred messages" and "edit" widgets.
            const elem = $(instance.reference);
            let content = elem.attr("data-tippy-content");
            if (content === undefined) {
                // Tippy cannot get the content for message edit button
                // as it is dynamically inserted based on editability.
                // So, we have to manually get the i element to get the
                // content from it.
                //
                // TODO: Change the template structure so logic is unnecessary.
                const edit_button = elem.find("i.edit_content_button");
                content = edit_button.attr("data-tippy-content");
            }
            if (content === undefined) {
                // If content is still undefined it is because content
                // is specified on inner i tags and is handled by our
                // general tippy-zulip-tooltip class. So we return
                // false here to avoid showing an extra empty tooltip
                // for such cases.
                return false;
            }
            instance.setContent(content);
            return true;
        },
    });

    delegate("body", {
        delay: 0,
        target: "#streams_inline_cog",
        onShow(instance) {
            popovers.hide_all_except_sidebars();
            instance.setContent(
                render_left_sidebar_stream_setting_popover({
                    can_create_streams: settings_data.user_can_create_streams(),
                }),
            );
            $(instance.popper).on("click", instance.hide);
        },
        appendTo: () => document.body,
        trigger: "click",
        allowHTML: true,
        interactive: true,
        hideOnClick: true,
        theme: "light-border",
    });
}
<|endoftext|>