<commit_before>import Route from 'consul-ui/routing/route';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { runInDebug } from '@ember/debug';

import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';

export default class ApplicationRoute extends Route.extend(WithBlockingActions) {
  @service('client/http') client;
  @service('env') env;
  @service('repository/token') tokenRepo;
  @service('settings') settings;

  data;

  async model() {
    if (this.env.var('CONSUL_ACLS_ENABLED')) {
      const secret = this.env.var('CONSUL_HTTP_TOKEN');
      if (secret) {
        const existing = await this.settings.findBySlug('token');

        if (secret && secret !== existing.SecretID) {
          try {
            const token = await this.tokenRepo.self({
              secret: secret,
              dc: this.env.var('CONSUL_DATACENTER_LOCAL'),
            });
            await this.settings.persist({
              token: {
                AccessorID: token.AccessorID,
                SecretID: token.SecretID,
                Namespace: token.Namespace,
                Partition: token.Partition,
              },
            });
          } catch (e) {
            runInDebug((_) => console.error(e));
          }
        }
      }
    }
    return {};
  }

  @action
  onClientChanged(e) {
    let data = e.data;
    if (data === '') {
      data = { blocking: true };
    }
    // this.data is always undefined first time round and its the 'first read'
    // of the value so we don't need to abort anything
    if (typeof this.data === 'undefined') {
      this.data = Object.assign({}, data);
      return;
    }
    if (this.data.blocking === true && data.blocking === false) {
      this.client.abort();
    }
    this.data = Object.assign({}, data);
  }

  @action
  error(e, transition) {
    // TODO: Normalize all this better
    let error = {
      status: e.code || e.statusCode || '',
      message: e.message || e.detail || 'Error',
    };
    if (e.errors && e.errors[0]) {
      error = e.errors[0];
      error.message = error.message || error.title || error.detail || 'Error';
    }
    if (error.status === '') {
      error.message = 'Error';
    }
    this.controllerFor('application').setProperties({ error: error });
    return true;
  }
}
<commit_msg>Cleanup app boot by using hcp service<commit_after>import Route from 'consul-ui/routing/route';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';

import WithBlockingActions from 'consul-ui/mixins/with-blocking-actions';

export default class ApplicationRoute extends Route.extend(WithBlockingActions) {
  @service('client/http') client;
  @service('env') env;
  @service() hcp;

  data;

  async model() {
    if (this.env.var('CONSUL_ACLS_ENABLED')) {
      await this.hcp.updateTokenIfNecessary(this.env.var('CONSUL_HTTP_TOKEN'));
    }
    return {};
  }

  @action
  onClientChanged(e) {
    let data = e.data;
    if (data === '') {
      data = { blocking: true };
    }
    // this.data is always undefined first time round and its the 'first read'
    // of the value so we don't need to abort anything
    if (typeof this.data === 'undefined') {
      this.data = Object.assign({}, data);
      return;
    }
    if (this.data.blocking === true && data.blocking === false) {
      this.client.abort();
    }
    this.data = Object.assign({}, data);
  }

  @action
  error(e, transition) {
    // TODO: Normalize all this better
    let error = {
      status: e.code || e.statusCode || '',
      message: e.message || e.detail || 'Error',
    };
    if (e.errors && e.errors[0]) {
      error = e.errors[0];
      error.message = error.message || error.title || error.detail || 'Error';
    }
    if (error.status === '') {
      error.message = 'Error';
    }
    this.controllerFor('application').setProperties({ error: error });
    return true;
  }
}
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'

import _ from 'lodash'

import QueryMaker from '../components/QueryMaker'
import Visualization from '../components/Visualization'
import WriteDataForm from '../components/WriteDataForm'
import Header from '../containers/Header'
import ResizeContainer from 'shared/components/ResizeContainer'
import OverlayTechnologies from 'shared/components/OverlayTechnologies'

import {VIS_VIEWS} from 'shared/constants'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from '../constants'
import {setAutoRefresh} from 'shared/actions/app'
import * as viewActions from 'src/data_explorer/actions/view'

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

const DataExplorer = React.createClass({
  propTypes: {
    source: shape({
      links: shape({
        proxy: string.isRequired,
        self: string.isRequired,
        queries: string.isRequired,
      }).isRequired,
    }).isRequired,
    queryConfigs: arrayOf(shape({})).isRequired,
    queryConfigActions: shape({
      editQueryStatus: func.isRequired,
    }).isRequired,
    autoRefresh: number.isRequired,
    handleChooseAutoRefresh: func.isRequired,
    timeRange: shape({
      upper: string,
      lower: string,
    }).isRequired,
    setTimeRange: func.isRequired,
    hideWriteFormAction: func.isRequired,
    showWriteFormAction: func.isRequired,
    dataExplorer: shape({
      showWriteForm: bool.isRequired,
      queryIDs: arrayOf(string).isRequired,
    }).isRequired,
  },

  childContextTypes: {
    source: shape({
      links: shape({
        proxy: string.isRequired,
        self: string.isRequired,
      }).isRequired,
    }).isRequired,
  },

  getChildContext() {
    return {source: this.props.source}
  },

  getInitialState() {
    return {
      activeQueryIndex: 0,
    }
  },

  handleSetActiveQueryIndex(index) {
    this.setState({activeQueryIndex: index})
  },

  handleDeleteQuery(index) {
    const {queryConfigs} = this.props
    const query = queryConfigs[index]
    this.props.queryConfigActions.deleteQuery(query.id)
  },

  render() {
    const {
      autoRefresh,
      handleChooseAutoRefresh,
      timeRange,
      setTimeRange,
      queryConfigs,
      queryConfigActions,
      source,
      hideWriteFormAction,
      showWriteFormAction,
      dataExplorer: {showWriteForm},
    } = this.props
    const {activeQueryIndex} = this.state

    return (

        {showWriteForm
          ? <OverlayTechnologies>
              <WriteDataForm onClose={hideWriteFormAction} source={source} />
            </OverlayTechnologies>
          : null}

        <ResizeContainer
          containerClass="page-contents"
          minTopHeight={MINIMUM_HEIGHTS.queryMaker}
          minBottomHeight={MINIMUM_HEIGHTS.visualization}
          initialTopHeight={INITIAL_HEIGHTS.queryMaker}
          initialBottomHeight={INITIAL_HEIGHTS.visualization}
        >
          <QueryMaker
            source={source}
            queries={queryConfigs}
            actions={queryConfigActions}
            autoRefresh={autoRefresh}
            timeRange={timeRange}
            isInDataExplorer={true}
            setActiveQueryIndex={this.handleSetActiveQueryIndex}
            onDeleteQuery={this.handleDeleteQuery}
            activeQueryIndex={activeQueryIndex}
          />
          <Visualization
            isInDataExplorer={true}
            autoRefresh={autoRefresh}
            timeRange={timeRange}
            queryConfigs={queryConfigs}
            activeQueryIndex={activeQueryIndex}
            editQueryStatus={queryConfigActions.editQueryStatus}
            views={VIS_VIEWS}
          />
        </ResizeContainer>

    )
  },
})

function mapStateToProps(state) {
  const {
    app: {persisted: {autoRefresh}},
    dataExplorer,
    queryConfigs,
    timeRange,
  } = state
  const queryConfigValues = _.values(queryConfigs)

  return {
    autoRefresh,
    dataExplorer,
    queryConfigs: queryConfigValues,
    timeRange,
  }
}

function mapDispatchToProps(dispatch) {
  return {
    handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
    setTimeRange: bindActionCreators(viewActions.setTimeRange, dispatch),
    showWriteFormAction: bindActionCreators(
      viewActions.showWriteForm,
      dispatch
    ),
    hideWriteFormAction: bindActionCreators(
      viewActions.hideWriteForm,
      dispatch
    ),
    queryConfigActions: bindActionCreators(viewActions, dispatch),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(DataExplorer)
<commit_msg>use absolute path for imports<commit_after>import React, {PropTypes} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'

import _ from 'lodash'

import QueryMaker from '../components/QueryMaker'
import Visualization from '../components/Visualization'
import WriteDataForm from 'src/data_explorer/components/WriteDataForm'
import Header from '../containers/Header'
import ResizeContainer from 'shared/components/ResizeContainer'
import OverlayTechnologies from 'shared/components/OverlayTechnologies'

import {VIS_VIEWS} from 'shared/constants'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from '../constants'
import {setAutoRefresh} from 'shared/actions/app'
import * as viewActions from 'src/data_explorer/actions/view'

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

const DataExplorer = React.createClass({
  propTypes: {
    source: shape({
      links: shape({
        proxy: string.isRequired,
        self: string.isRequired,
        queries: string.isRequired,
      }).isRequired,
    }).isRequired,
    queryConfigs: arrayOf(shape({})).isRequired,
    queryConfigActions: shape({
      editQueryStatus: func.isRequired,
    }).isRequired,
    autoRefresh: number.isRequired,
    handleChooseAutoRefresh: func.isRequired,
    timeRange: shape({
      upper: string,
      lower: string,
    }).isRequired,
    setTimeRange: func.isRequired,
    hideWriteFormAction: func.isRequired,
    showWriteFormAction: func.isRequired,
    dataExplorer: shape({
      showWriteForm: bool.isRequired,
      queryIDs: arrayOf(string).isRequired,
    }).isRequired,
  },

  childContextTypes: {
    source: shape({
      links: shape({
        proxy: string.isRequired,
        self: string.isRequired,
      }).isRequired,
    }).isRequired,
  },

  getChildContext() {
    return {source: this.props.source}
  },

  getInitialState() {
    return {
      activeQueryIndex: 0,
    }
  },

  handleSetActiveQueryIndex(index) {
    this.setState({activeQueryIndex: index})
  },

  handleDeleteQuery(index) {
    const {queryConfigs} = this.props
    const query = queryConfigs[index]
    this.props.queryConfigActions.deleteQuery(query.id)
  },

  render() {
    const {
      autoRefresh,
      handleChooseAutoRefresh,
      timeRange,
      setTimeRange,
      queryConfigs,
      queryConfigActions,
      source,
      hideWriteFormAction,
      showWriteFormAction,
      dataExplorer: {showWriteForm},
    } = this.props
    const {activeQueryIndex} = this.state

    return (

        {showWriteForm
          ? <OverlayTechnologies>
              <WriteDataForm onClose={hideWriteFormAction} source={source} />
            </OverlayTechnologies>
          : null}

        <ResizeContainer
          containerClass="page-contents"
          minTopHeight={MINIMUM_HEIGHTS.queryMaker}
          minBottomHeight={MINIMUM_HEIGHTS.visualization}
          initialTopHeight={INITIAL_HEIGHTS.queryMaker}
          initialBottomHeight={INITIAL_HEIGHTS.visualization}
        >
          <QueryMaker
            source={source}
            queries={queryConfigs}
            actions={queryConfigActions}
            autoRefresh={autoRefresh}
            timeRange={timeRange}
            isInDataExplorer={true}
            setActiveQueryIndex={this.handleSetActiveQueryIndex}
            onDeleteQuery={this.handleDeleteQuery}
            activeQueryIndex={activeQueryIndex}
          />
          <Visualization
            isInDataExplorer={true}
            autoRefresh={autoRefresh}
            timeRange={timeRange}
            queryConfigs={queryConfigs}
            activeQueryIndex={activeQueryIndex}
            editQueryStatus={queryConfigActions.editQueryStatus}
            views={VIS_VIEWS}
          />
        </ResizeContainer>

    )
  },
})

function mapStateToProps(state) {
  const {
    app: {persisted: {autoRefresh}},
    dataExplorer,
    queryConfigs,
    timeRange,
  } = state
  const queryConfigValues = _.values(queryConfigs)

  return {
    autoRefresh,
    dataExplorer,
    queryConfigs: queryConfigValues,
    timeRange,
  }
}

function mapDispatchToProps(dispatch) {
  return {
    handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
    setTimeRange: bindActionCreators(viewActions.setTimeRange, dispatch),
    showWriteFormAction: bindActionCreators(
      viewActions.showWriteForm,
      dispatch
    ),
    hideWriteFormAction: bindActionCreators(
      viewActions.hideWriteForm,
      dispatch
    ),
    queryConfigActions: bindActionCreators(viewActions, dispatch),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(DataExplorer)
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react'
import OnClickOutside from 'shared/components/OnClickOutside'
import classNames from 'classnames'
import _ from 'lodash'

const labelText = ({localSelectedItems, isOpen, label}) => {
  if (label) {
    return label
  }
  else if (localSelectedItems.length) {
    return localSelectedItems.map((s) => s).join(', ')
  }
  else {
    if (isOpen) {
      return '0 Selected'
    }
    else {
      return 'Apply Function'
    }
  }
}

class MultiSelectDropdown extends Component {
  constructor(props) {
    super(props)

    this.state = {
      isOpen: false,
      localSelectedItems: this.props.selectedItems,
    }

    this.onSelect = ::this.onSelect
    this.onApplyFunctions = ::this.onApplyFunctions
  }

  componentWillReceiveProps(nextProps) {
    if (!_.isEqual(this.state.localSelectedItems, nextProps.selectedItems)) {
      this.setState({
        localSelectedItems: nextProps.selectedItems,
      })
    }
  }

  handleClickOutside() {
    this.setState({isOpen: false})
  }

  toggleMenu(e) {
    e.stopPropagation()
    this.setState({isOpen: !this.state.isOpen})
  }

  onSelect(item, e) {
    e.stopPropagation()

    const {localSelectedItems} = this.state

    let nextItems
    if (this.isSelected(item)) {
      nextItems = localSelectedItems.filter((i) => i !== item)
    } else {
      nextItems = localSelectedItems.concat(item)
    }

    this.setState({localSelectedItems: nextItems})
  }

  isSelected(item) {
    return this.state.localSelectedItems.indexOf(item) > -1
  }

  onApplyFunctions(e) {
    e.stopPropagation()

    this.setState({isOpen: false})
    this.props.onApply(this.state.localSelectedItems)
  }

  render() {
    const {localSelectedItems, isOpen} = this.state
    const {label} = this.props

    return (

            {
              labelText({localSelectedItems, isOpen, label})
            }

        {this.renderMenu()}

    )
  }

  renderMenu() {
    const {items} = this.props

    return (

Apply

          {items.map((listItem, i) => {
            return (

                {listItem}

            )
          })}

    )
  }
}

const {
  arrayOf,
  func,
  string,
} = PropTypes

MultiSelectDropdown.propTypes = {
  onApply: func.isRequired,
  items: arrayOf(string.isRequired).isRequired,
  selectedItems: arrayOf(string.isRequired).isRequired,
  label: string,
}

export default OnClickOutside(MultiSelectDropdown)
<commit_msg>Forgot to save one of my files...<commit_after>import React, {Component, PropTypes} from 'react'
import OnClickOutside from 'shared/components/OnClickOutside'
import classNames from 'classnames'
import _ from 'lodash'

const labelText = ({localSelectedItems, isOpen, label}) => {
  if (label) {
    return label
  }
  else if (localSelectedItems.length) {
    return localSelectedItems.map((s) => s).join(', ')
  }
  else {
    if (isOpen) {
      return '0 Selected'
    }
    else {
      return 'Apply Function'
    }
  }
}

class MultiSelectDropdown extends Component {
  constructor(props) {
    super(props)

    this.state = {
      isOpen: false,
      localSelectedItems: this.props.selectedItems,
    }

    this.onSelect = ::this.onSelect
    this.onApplyFunctions = ::this.onApplyFunctions
  }

  componentWillReceiveProps(nextProps) {
    if (!_.isEqual(this.state.localSelectedItems, nextProps.selectedItems)) {
      this.setState({
        localSelectedItems: nextProps.selectedItems,
      })
    }
  }

  handleClickOutside() {
    this.setState({isOpen: false})
  }

  toggleMenu(e) {
    e.stopPropagation()
    this.setState({isOpen: !this.state.isOpen})
  }

  onSelect(item, e) {
    e.stopPropagation()

    const {localSelectedItems} = this.state

    let nextItems
    if (this.isSelected(item)) {
      nextItems = localSelectedItems.filter((i) => i !== item)
    } else {
      nextItems = localSelectedItems.concat(item)
    }

    this.setState({localSelectedItems: nextItems})
  }

  isSelected(item) {
    return this.state.localSelectedItems.indexOf(item) > -1
  }

  onApplyFunctions(e) {
    e.stopPropagation()

    this.setState({isOpen: false})
    this.props.onApply(this.state.localSelectedItems)
  }

  render() {
    const {localSelectedItems, isOpen} = this.state
    const {label} = this.props

    return (

            {
              labelText({localSelectedItems, isOpen, label})
            }

        {this.renderMenu()}

    )
  }

  renderMenu() {
    const {items} = this.props

    return (

Apply

          {items.map((listItem, i) => {
            return (

                {listItem}

            )
          })}

    )
  }
}

const {
  arrayOf,
  func,
  string,
} = PropTypes

MultiSelectDropdown.propTypes = {
  onApply: func.isRequired,
  items: arrayOf(string.isRequired).isRequired,
  selectedItems: arrayOf(string.isRequired).isRequired,
  label: string,
}

export default OnClickOutside(MultiSelectDropdown)
<|endoftext|>


<commit_before>define([
  'angular',
  'lodash',
  'app/core/utils/datemath',
  './series',
  './query_builder',
  './directives',
  './query_ctrl',
  './aggr_add',
  './aggr_editor',
  './mget_editor',
],
function (angular, _, dateMath, DalmatinerSeries, DalmatinerQueryBuilder) {
  'use strict';

  var module = angular.module('grafana.services');

  module.factory('DataloopDatasource', function($q, backendSrv, templateSrv) {

    function DataloopDatasource(datasource) {
      this.urls = _.map(datasource.url.split(','), function(url) {
        return url.trim();
      });

      this.authKey = datasource.jsonData.authKey;
    }

    DataloopDatasource.prototype.query = function(options) {

      var queries = options.targets
            .filter(function hiddens(target) {return !target.hide;})
            .map(_.partial(buildQuery, options))
            .join(', ');

      //No query => return no data (inside a promise)
      if (!queries) {
        return $q(function(resolve) {resolve([]);});
      }

      var mainQuery = 'SELECT ' + templateSrv.replace(queries, options.scopedVars) + ' ' + getTimeFilter(options);

      return this.runQuery(mainQuery).then(function(res) {
        return {data: new DalmatinerSeries(res.s, res.d).getTimeSeries()};
      });
    };

    DataloopDatasource.prototype.runQuery = function(query) {
      return this._request('GET', '/metrics/series', {query: query});
    };

    DataloopDatasource.prototype.listAgents = function() {
      return this._request('GET', '/agents');
    };

    DataloopDatasource.prototype.listMetrics = function(agent) {
      return this._request('GET', '/metrics', {source: agent});
    };

    DataloopDatasource.prototype.testDatasource = function() {
      return this.listAgents().then(function () {
        return { status: "success", message: "Data source is working", title: "Success" };
      });
    };

    DataloopDatasource.prototype._request = function(method, url, params) {

      var currentUrl = this.urls.shift();
      this.urls.push(currentUrl);

      var options = {
        method: method,
        url:    currentUrl + url,
        params: params,
        headers: {Authorization: 'Bearer ' + this.authKey}
      };

      return backendSrv.datasourceRequest(options).then(function ok(result) {
        return result.data;

      }, function error(err) {
        throw {message: 'Error: ' + (err.message || err.data || err.statusText), data: err.data, config: err.config };
      });
    };

    function isNumber(x) {
      return !isNaN(x);
    }

    function getTimeFilter(options) {
      var from = getDalmatinerTime(options.rangeRaw.from, false);
      var until = getDalmatinerTime(options.rangeRaw.to, true);
      var fromIsAbsolute = isNumber(from);

      if (until === 'now' && !fromIsAbsolute) {
        return 'LAST ' + from;
      }

      return 'BETWEEN ' + from + ' AND ' + until;
    }

    function getDalmatinerTime(date, roundUp) {
      if (_.isString(date)) {
        if (date === 'now') {
          return date;
        }

        var parts = /^now-(\d+)([d|h|m|s])$/.exec(date);
        if (parts) {
          var amount = parseInt(parts[1]);
          var unit = parts[2];
          return amount + unit;
        }
        date = dateMath.parse(date, roundUp);
      }
      return (date.valueOf() / 1000).toFixed(0);
    }

    function buildQuery(options, target) {
      var queryBuilder = new DalmatinerQueryBuilder(_.extend({
        bucket: target.agent,
        metric: target.metric.split('.')
            .map(encodeMetricPart)
            .join('.')
      }, target));
      return queryBuilder.build().replace(/\$interval/g, (target.interval || options.interval));
    }

    function encodeMetricPart(part) {
      return part === '*' ? part : "'" + part + "'";
    }

    return DataloopDatasource;

  });

});
<commit_msg>Fix query with more comples poramaters<commit_after>define([
  'angular',
  'lodash',
  'app/core/utils/datemath',
  './series',
  './query_builder',
  './directives',
  './query_ctrl',
  './aggr_add',
  './aggr_editor',
  './mget_editor',
],
function (angular, _, dateMath, DalmatinerSeries, DalmatinerQueryBuilder) {
  'use strict';

  var module = angular.module('grafana.services');

  module.factory('DataloopDatasource', function($q, backendSrv, templateSrv) {

    function DataloopDatasource(datasource) {
      this.urls = _.map(datasource.url.split(','), function(url) {
        return url.trim();
      });

      this.authKey = datasource.jsonData.authKey;
    }

    DataloopDatasource.prototype.query = function(options) {

      var queries = options.targets
            .filter(function hiddens(target) {return !target.hide;})
            .map(_.partial(buildQuery, options))
            .join(', ');

      //No query => return no data (inside a promise)
      if (!queries) {
        return $q(function(resolve) {resolve([]);});
      }

      var mainQuery = 'SELECT ' + templateSrv.replace(queries, options.scopedVars) + ' ' + getTimeFilter(options);

      return this.runQuery(mainQuery).then(function(res) {
        return {data: new DalmatinerSeries(res.s, res.d).getTimeSeries()};
      });
    };

    DataloopDatasource.prototype.runQuery = function(query) {
      return this._request('GET', '/metrics/series', {query: query});
    };

    DataloopDatasource.prototype.listAgents = function() {
      return this._request('GET', '/agents');
    };

    DataloopDatasource.prototype.listMetrics = function(agent) {
      return this._request('GET', '/metrics', {source: agent});
    };

    DataloopDatasource.prototype.testDatasource = function() {
      return this.listAgents().then(function () {
        return { status: "success", message: "Data source is working", title: "Success" };
      });
    };

    DataloopDatasource.prototype._request = function(method, url, params) {

      var currentUrl = this.urls.shift();
      this.urls.push(currentUrl);

      var options = {
        method: method,
        url:    currentUrl + url,
        params: params,
        headers: {Authorization: 'Bearer ' + this.authKey}
      };

      return backendSrv.datasourceRequest(options).then(function ok(result) {
        return result.data;

      }, function error(err) {
        throw {message: 'Error: ' + (err.message || err.data || err.statusText), data: err.data, config: err.config };
      });
    };

    function isNumber(x) {
      return !isNaN(x);
    }

    function getTimeFilter(options) {
      var from = getDalmatinerTime(options.rangeRaw.from, false);
      var until = getDalmatinerTime(options.rangeRaw.to, true);
      var fromIsAbsolute = isNumber(from);

      if (until === 'now' && !fromIsAbsolute) {
        return 'LAST ' + from;
      }

      return 'BETWEEN ' + from + ' AND ' + until;
    }

    function getDalmatinerTime(date, roundUp) {
      if (_.isString(date)) {
        if (date === 'now') {
          return date;
        }

        var parts = /^now-(\d+)([d|h|m|s])$/.exec(date);
        if (parts) {
          var amount = parseInt(parts[1]);
          var unit = parts[2];
          return amount + unit;
        }
        date = dateMath.parse(date, roundUp);
      }
      return (date.valueOf() / 1000).toFixed(0);
    }

    function buildQuery(options, target) {
      var queryBuilder = new DalmatinerQueryBuilder(_.defaults({
        bucket: target.agent,
        metric: target.metric.split('.')
            .map(encodeMetricPart)
            .join('.')
      }, target));
      return queryBuilder.build().replace(/\$interval/g, (target.interval || options.interval));
    }

    function encodeMetricPart(part) {
      return part === '*' ? part : "'" + part + "'";
    }

    return DataloopDatasource;

  });

});
<|endoftext|>


<commit_before>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community 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://opensource.org/licenses/ECL-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.
 */

var _ = require('underscore');
var AuthzAPI = require('oae-authz');
var OaeUtil = require('oae-util/lib/util');
var TenantsUtil = require('oae-tenants/lib/util');

var SearchConstants = require('oae-search/lib/constants').SearchConstants;
var SearchUtil = require('oae-search/lib/util');

/**
 * A search that searches for users and groups that the current user can interact with
 *
 * In addition to the specific `opts` parameters documented here, there are more generic options available that impact all
 * searches. @see SearchAPI#search for more information.
 *
 * @param  {Context}       ctx                     The context of the current request
 * @param  {Object}        [opts]                  General search options
 * @param  {String[]}      [opts.resourceTypes]    An array of resource types to search (e.g., content, user). If not specified, then the search will not filter on resource type at all. Possible resource types are those that have registered producers in SearchAPI#registerSearchDocumentProducer.
 * @param  {Function}      callback                Invoked when the process completes
 * @param  {Object}        callback.err            An error that occurred, if any
 * @param  {SearchResult}  callback.results        An object that represents the results of the query
 */
module.exports = function(ctx, opts, callback) {
    // Sanitize custom search options
    opts = opts || {};
    opts.resourceTypes = opts.resourceTypes || [];
    opts.limit = OaeUtil.getNumberParam(opts.limit, 10, 1, 25);

    if (!ctx.user()) {
        return callback({'code': 401, 'msg': 'Anonymous users can\'t interact'});
    }

    // Sanitize the resourceTypes array
    if (!_.isArray(opts.resourceTypes)) {
        // Convert to an array if it's a single value
        opts.resourceTypes = [opts.resourceTypes];
    }

    // Remove any falsy values from the array
    opts.resourceTypes = _.compact(opts.resourceTypes);

    // If there were no valid values for resource type, we search all resource types
    opts.searchAll = _.isEmpty(opts.resourceTypes);

    AuthzAPI.getPrincipalMemberships(ctx.user().id, null, 10000, function(err, groups) {
        if (err) {
            return callback(err);
        }

        // A user can always interact with the groups they are a member of
        opts.access = groups || [];

        // A user can always interact with themself
        opts.access.push(ctx.user().id);

        // Create the search object
        _search(ctx, opts, callback);
    });
};

/**
 * Perform the search that searches a 'q' analyzed field on documents, scoping it by user access. This is delegated from the
 * `module.exports` function for convenience, as it will access the members array only if necessary.
 *
 * @param  {Context}       ctx                 The context of the current request
 * @param  {Object}        opts                General search options
 * @param  {Function}      callback            Invoked when the process completes
 * @param  {Object}        callback.err        An error that occurred, if any
 * @param  {SearchResult}  callback.results    An object that represents the results of the query
 */
var _search = function(ctx, opts, callback) {
    var data = {};

    // The query object for the Query DSL
    var query = SearchUtil.createQueryStringQuery(opts.q);

    // Only search for top level resources
    var typeFilter = SearchUtil.filterTerm('_type', SearchConstants.resourceMappingName);

    // The resource type filter will filter by a particular document type (e.g., content, user, group)
    var resourceTypeFilter = (!opts.searchAll) ? SearchUtil.filterTerms('resourceType', opts.resourceTypes) : null;

    // This base filter gets applied to the query unconditionally.
    var baseFilter = SearchUtil.filterAnd(typeFilter, resourceTypeFilter);

    // The access filter will filter by the user's group membership, or the whole tenant if it is tenant admin
    var accessFilter = (ctx.user() && ctx.user().isTenantAdmin(ctx.tenant().alias)) ?
            SearchUtil.filterTerm('tenantAlias', ctx.tenant().alias) : SearchUtil.filterMembers(opts.access);

    // We need to know the tenants with which the current tenant can interact,
    // so we can properly filter out resources from non-permeable tenants
    // and private and loggedin resources from permeable tenants
    var interactingTenantAliases = TenantsUtil.getAllTenantsForInteraction(ctx.tenant().alias);

    // The filter object for the Query DSL
    var filter = SearchUtil.filterAnd(
        baseFilter,
        SearchUtil.filterOr(

            // I can always see principals that I have explicit access to
            accessFilter,

            // This or statement applies visibility restrictions
            SearchUtil.filterOr(

                // Public resources from the current tenant and from tenants with which we can interact should always be included
                SearchUtil.filterAnd(
                    SearchUtil.filterTerm('visibility', 'public'),
                    SearchUtil.filterOr(
                        SearchUtil.filterTerm('tenantAlias', ctx.tenant().alias),
                        (interactingTenantAliases.length > 0) ? SearchUtil.filterTerms('tenantAlias', interactingTenantAliases) : null
                    )
                ),

                // Logged in resources from the current tenant should also be included
                SearchUtil.filterAnd(
                    SearchUtil.filterTerm('tenantAlias', ctx.tenant().alias),
                    SearchUtil.filterTerm('visibility', 'loggedin')
                )
            )
        )
    );

    // wrap the query and filter into the top-level Query DSL "query" object
    return callback(null, SearchUtil.createQuery(query, filter, opts));
};
<commit_msg>(Issue-579) Fix rebase issue<commit_after>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community 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://opensource.org/licenses/ECL-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.
 */

var _ = require('underscore');
var AuthzAPI = require('oae-authz');
var AuthzConstants = require('oae-authz/lib/constants').AuthzConstants;
var OaeUtil = require('oae-util/lib/util');
var TenantsUtil = require('oae-tenants/lib/util');

var SearchConstants = require('oae-search/lib/constants').SearchConstants;
var SearchUtil = require('oae-search/lib/util');

/**
 * A search that searches for users and groups that the current user can interact with
 *
 * In addition to the specific `opts` parameters documented here, there are more generic options available that impact all
 * searches. @see SearchAPI#search for more information.
 *
 * @param  {Context}       ctx                     The context of the current request
 * @param  {Object}        [opts]                  General search options
 * @param  {String[]}      [opts.resourceTypes]    An array of resource types to search (e.g., content, user). If not specified, then the search will not filter on resource type at all. Possible resource types are those that have registered producers in SearchAPI#registerSearchDocumentProducer.
 * @param  {Function}      callback                Invoked when the process completes
 * @param  {Object}        callback.err            An error that occurred, if any
 * @param  {SearchResult}  callback.results        An object that represents the results of the query
 */
module.exports = function(ctx, opts, callback) {
    // Sanitize custom search options
    opts = opts || {};
    opts.resourceTypes = opts.resourceTypes || [];
    opts.limit = OaeUtil.getNumberParam(opts.limit, 10, 1, 25);

    if (!ctx.user()) {
        return callback({'code': 401, 'msg': 'Anonymous users can\'t interact'});
    }

    // Sanitize the resourceTypes array
    if (!_.isArray(opts.resourceTypes)) {
        // Convert to an array if it's a single value
        opts.resourceTypes = [opts.resourceTypes];
    }

    // Remove any falsy values from the array
    opts.resourceTypes = _.compact(opts.resourceTypes);

    // If there were no valid values for resource type, we search all resource types
    opts.searchAll = _.isEmpty(opts.resourceTypes);

    AuthzAPI.getPrincipalMemberships(ctx.user().id, null, 10000, function(err, groups) {
        if (err) {
            return callback(err);
        }

        // A user can always interact with the groups they are a member of
        opts.access = groups || [];

        // A user can always interact with themself
        opts.access.push(ctx.user().id);

        // Create the search object
        _search(ctx, opts, callback);
    });
};

/**
 * Perform the search that searches a 'q' analyzed field on documents, scoping it by user access. This is delegated from the
 * `module.exports` function for convenience, as it will access the members array only if necessary.
 *
 * @param  {Context}       ctx                 The context of the current request
 * @param  {Object}        opts                General search options
 * @param  {Function}      callback            Invoked when the process completes
 * @param  {Object}        callback.err        An error that occurred, if any
 * @param  {SearchResult}  callback.results    An object that represents the results of the query
 */
var _search = function(ctx, opts, callback) {
    var data = {};

    // The query object for the Query DSL
    var query = SearchUtil.createQueryStringQuery(opts.q);

    // Only search for top level resources
    var typeFilter = SearchUtil.filterTerm('_type', SearchConstants.search.MAPPING_RESOURCE);

    // The resource type filter will filter by a particular document type (e.g., content, user, group)
    var resourceTypeFilter = (!opts.searchAll) ? SearchUtil.filterTerms('resourceType', opts.resourceTypes) : null;

    // This base filter gets applied to the query unconditionally.
    var baseFilter = SearchUtil.filterAnd(typeFilter, resourceTypeFilter);

    // The access filter will filter by the user's group membership, or the whole tenant if it is tenant admin
    var accessFilter = (ctx.user() && ctx.user().isTenantAdmin(ctx.tenant().alias)) ?
            SearchUtil.filterTerm('tenantAlias', ctx.tenant().alias) : SearchUtil.createHasChildQuery(AuthzConstants.search.MAPPING_RESOURCE_MEMBERS, SearchUtil.filterTerms('direct_members', opts.access));

    // We need to know the tenants with which the current tenant can interact,
    // so we can properly filter out resources from non-permeable tenants
    // and private and loggedin resources from permeable tenants
    var interactingTenantAliases = TenantsUtil.getAllTenantsForInteraction(ctx.tenant().alias);

    // The filter object for the Query DSL
    var filter = SearchUtil.filterAnd(
        baseFilter,
        SearchUtil.filterOr(

            // I can always see principals that I have explicit access to
            accessFilter,

            // This or statement applies visibility restrictions
            SearchUtil.filterOr(

                // Public resources from the current tenant and from tenants with which we can interact should always be included
                SearchUtil.filterAnd(
                    SearchUtil.filterTerm('visibility', AuthzConstants.visibility.PUBLIC),
                    SearchUtil.filterOr(
                        SearchUtil.filterTerm('tenantAlias', ctx.tenant().alias),
                        (interactingTenantAliases.length > 0) ? SearchUtil.filterTerms('tenantAlias', interactingTenantAliases) : null
                    )
                ),

                // Logged in resources from the current tenant should also be included
                SearchUtil.filterAnd(
                    SearchUtil.filterTerm('tenantAlias', ctx.tenant().alias),
                    SearchUtil.filterTerm('visibility', AuthzConstants.visibility.LOGGEDIN)
                )
            )
        )
    );

    // wrap the query and filter into the top-level Query DSL "query" object
    return callback(null, SearchUtil.createQuery(query, filter, opts));
};
<|endoftext|>


<commit_before>import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { fixtureRecipes, fixtureRevisions, initialState } from '../fixtures/fixtures';
import * as actionTypes from '../../static/control/js/actions/ControlActions'

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore({ controlApp: initialState });

const successPromise = (responseData) => {
  return Promise.resolve(new Response(JSON.stringify(responseData), {
    Headers: 'Content-Type: application/json',
    status: 200
  }));
}

const failurePromise = () => {
  return Promise.resolve(new Response('{}', {
    Headers: 'Content-Type: application/json',
    status: 403
  }));
}

describe('controlApp Actions', () => {

  beforeEach(() => {
    store.clearActions();
  });

  it('creates REQUEST_IN_PROGRESS when initiating an api call', () => {
    const expectedAction = { type: actionTypes.REQUEST_IN_PROGRESS };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes));

    return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes'))
    .then(() => {
      expect(store.getActions()).toContain(expectedAction);
    });
  })

  describe('creates REQUEST_COMPLETE when an api response is returned', () => {

    it('returns with `status: success` if the request succeeded', () => {
      const expectedAction = { type: actionTypes.REQUEST_COMPLETE, status: 'success' };
      spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes));

      return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes'))
      .then(() => {
        expect(store.getActions()).toContain(expectedAction);
      });
    })

    it('returns with `status: error` if the request failed', () => {
      const expectedAction = { type: actionTypes.REQUEST_COMPLETE, status: 'error' };
      spyOn(window, 'fetch').and.returnValue(failurePromise());

      return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes'))
      .catch(() => {
        expect(store.getActions()).toContain(expectedAction);
      });
    });

    it('creates a SET_NOTIFICATION action if provided', () => {
      const expectedAction = { type: actionTypes.SET_NOTIFICATION, notification: { messageType: 'error', 'message': 'Error fetching recipes.'} };
      spyOn(window, 'fetch').and.returnValue(failurePromise());

      return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes')).catch(() => {
        expect(store.getActions()).toContain(expectedAction);
      });
    });
  })

  it('creates RECIPES_RECEIVED when fetching recipes is successful', () => {
    const expectedAction = { type: actionTypes.RECIPES_RECEIVED, recipes: fixtureRecipes }
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes));

    return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes'))
    .then((response) => {
      store.dispatch(actionTypes.recipesReceived(response));

      expect(window.fetch).toHaveBeenCalled();
      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/', jasmine.objectContaining({ method: 'GET' }));
      expect(store.getActions()).toContain(expectedAction);
    });
  });

  it('creates SINGLE_RECIPE_RECEIVED when fetching by RECIPE id is successful', () => {
    const expectedAction = { type: actionTypes.SINGLE_RECIPE_RECEIVED, recipe: fixtureRecipes[0] };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes[0]));

    return store.dispatch(actionTypes.makeApiRequest('fetchSingleRecipe', { recipeId: 1 }))
    .then((response) => {
      store.dispatch(actionTypes.singleRecipeReceived(response));

      expect(window.fetch).toHaveBeenCalled();
      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/1/', jasmine.objectContaining({ method: 'GET' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

  it('creates SINGLE_RECIPE_RECEIVED when fetching by REVISION id is successful', () => {
    const expectedAction = { type: actionTypes.SINGLE_RECIPE_RECEIVED, recipe: fixtureRevisions[0].recipe };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRevisions[0]));

    return store.dispatch(actionTypes.makeApiRequest('fetchSingleRecipe', { revisionId: 169 }))
    .then((response) => {
      store.dispatch(actionTypes.singleRecipeReceived(response.recipe));

      expect(window.fetch).toHaveBeenCalled();
      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe_version/169/', jasmine.objectContaining({ method: 'GET' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

  it('creates RECIPE_ADDED when adding a recipe is successful', () => {
    const expectedAction = { type: actionTypes.RECIPE_ADDED, recipe: fixtureRecipes[0] }
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes[0]));

    return store.dispatch(actionTypes.makeApiRequest('addRecipe', fixtureRecipes[0]))
    .then((response) => {
      store.dispatch(actionTypes.recipeAdded(response));

      expect(window.fetch).toHaveBeenCalled();
      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/', jasmine.objectContaining({ method: 'POST' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

  it('creates RECIPE_UPDATED when updating a recipe is successful', () => {
    const expectedAction = { type: actionTypes.RECIPE_UPDATED, recipe: fixtureRecipes[0] };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes[0]));

    return store.dispatch(actionTypes.makeApiRequest('updateRecipe', { recipe: fixtureRecipes[0], recipeId: 1 }))
    .then((response) => {
      store.dispatch(actionTypes.recipeUpdated(response));

      expect(window.fetch).toHaveBeenCalled();
      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/1/', jasmine.objectContaining({ method: 'PATCH' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

  it('creates RECIPE_DELETED when deleting a recipe is successful', () => {
    const expectedAction = { type: actionTypes.RECIPE_DELETED, recipeId: 1 };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes.filter(recipe => recipe.id !== 1)));

    return store.dispatch(actionTypes.makeApiRequest('deleteRecipe', { recipeId: 1 }))
    .then((response) => {
      store.dispatch(actionTypes.recipeDeleted(1));

      expect(window.fetch).toHaveBeenCalled();
      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/1/', jasmine.objectContaining({ method: 'DELETE' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

})
<commit_msg>Remove redundant .toHaveBeenCalled() assertions<commit_after>import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { fixtureRecipes, fixtureRevisions, initialState } from '../fixtures/fixtures';
import * as actionTypes from '../../static/control/js/actions/ControlActions'

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore({ controlApp: initialState });

const successPromise = (responseData) => {
  return Promise.resolve(new Response(JSON.stringify(responseData), {
    Headers: 'Content-Type: application/json',
    status: 200
  }));
}

const failurePromise = () => {
  return Promise.resolve(new Response('{}', {
    Headers: 'Content-Type: application/json',
    status: 403
  }));
}

describe('controlApp Actions', () => {

  beforeEach(() => {
    store.clearActions();
  });

  it('creates REQUEST_IN_PROGRESS when initiating an api call', () => {
    const expectedAction = { type: actionTypes.REQUEST_IN_PROGRESS };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes));

    return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes'))
    .then(() => {
      expect(store.getActions()).toContain(expectedAction);
    });
  })

  describe('creates REQUEST_COMPLETE when an api response is returned', () => {

    it('returns with `status: success` if the request succeeded', () => {
      const expectedAction = { type: actionTypes.REQUEST_COMPLETE, status: 'success' };
      spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes));

      return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes'))
      .then(() => {
        expect(store.getActions()).toContain(expectedAction);
      });
    })

    it('returns with `status: error` if the request failed', () => {
      const expectedAction = { type: actionTypes.REQUEST_COMPLETE, status: 'error' };
      spyOn(window, 'fetch').and.returnValue(failurePromise());

      return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes'))
      .catch(() => {
        expect(store.getActions()).toContain(expectedAction);
      });
    });

    it('creates a SET_NOTIFICATION action if provided', () => {
      const expectedAction = { type: actionTypes.SET_NOTIFICATION, notification: { messageType: 'error', 'message': 'Error fetching recipes.'} };
      spyOn(window, 'fetch').and.returnValue(failurePromise());

      return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes')).catch(() => {
        expect(store.getActions()).toContain(expectedAction);
      });
    });
  })

  it('creates RECIPES_RECEIVED when fetching recipes is successful', () => {
    const expectedAction = { type: actionTypes.RECIPES_RECEIVED, recipes: fixtureRecipes }
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes));

    return store.dispatch(actionTypes.makeApiRequest('fetchAllRecipes'))
    .then((response) => {
      store.dispatch(actionTypes.recipesReceived(response));

      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/', jasmine.objectContaining({ method: 'GET' }));
      expect(store.getActions()).toContain(expectedAction);
    });
  });

  it('creates SINGLE_RECIPE_RECEIVED when fetching by RECIPE id is successful', () => {
    const expectedAction = { type: actionTypes.SINGLE_RECIPE_RECEIVED, recipe: fixtureRecipes[0] };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes[0]));

    return store.dispatch(actionTypes.makeApiRequest('fetchSingleRecipe', { recipeId: 1 }))
    .then((response) => {
      store.dispatch(actionTypes.singleRecipeReceived(response));

      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/1/', jasmine.objectContaining({ method: 'GET' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

  it('creates SINGLE_RECIPE_RECEIVED when fetching by REVISION id is successful', () => {
    const expectedAction = { type: actionTypes.SINGLE_RECIPE_RECEIVED, recipe: fixtureRevisions[0].recipe };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRevisions[0]));

    return store.dispatch(actionTypes.makeApiRequest('fetchSingleRecipe', { revisionId: 169 }))
    .then((response) => {
      store.dispatch(actionTypes.singleRecipeReceived(response.recipe));

      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe_version/169/', jasmine.objectContaining({ method: 'GET' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

  it('creates RECIPE_ADDED when adding a recipe is successful', () => {
    const expectedAction = { type: actionTypes.RECIPE_ADDED, recipe: fixtureRecipes[0] }
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes[0]));

    return store.dispatch(actionTypes.makeApiRequest('addRecipe', fixtureRecipes[0]))
    .then((response) => {
      store.dispatch(actionTypes.recipeAdded(response));

      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/', jasmine.objectContaining({ method: 'POST' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

  it('creates RECIPE_UPDATED when updating a recipe is successful', () => {
    const expectedAction = { type: actionTypes.RECIPE_UPDATED, recipe: fixtureRecipes[0] };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes[0]));

    return store.dispatch(actionTypes.makeApiRequest('updateRecipe', { recipe: fixtureRecipes[0], recipeId: 1 }))
    .then((response) => {
      store.dispatch(actionTypes.recipeUpdated(response));

      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/1/', jasmine.objectContaining({ method: 'PATCH' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

  it('creates RECIPE_DELETED when deleting a recipe is successful', () => {
    const expectedAction = { type: actionTypes.RECIPE_DELETED, recipeId: 1 };
    spyOn(window, 'fetch').and.returnValue(successPromise(fixtureRecipes.filter(recipe => recipe.id !== 1)));

    return store.dispatch(actionTypes.makeApiRequest('deleteRecipe', { recipeId: 1 }))
    .then((response) => {
      store.dispatch(actionTypes.recipeDeleted(1));

      expect(window.fetch).toHaveBeenCalledWith('/api/v1/recipe/1/', jasmine.objectContaining({ method: 'DELETE' }));
      expect(store.getActions()).toContain(expectedAction);
    })
  });

})
<|endoftext|>


<commit_before>$(function () {
    $(".city-autocomplete").on("autocompleteselect", function (event, ui) {
        show_place_on_map(event.target.id, ui.item.id);
    });
})

// Datepicker init
$(function () {
    $('#datetimepicker').datetimepicker({
        format: 'DD/MM/YYYY HH:mm',
        inline: true,
        sideBySide: true,
        stepping: 10,
        minDate: moment().add(1, 'days').valueOf(),
        useCurrent: true,
    }).on("changeDate", function (event) {
        document.getElementById("searchtrip_datetime").value = $('#datetimepicker').data('DateTimePicker').date().unix();
    }).trigger("changeDate").show();
});

var marker_state = 0;
var markers = new Array();
var router = L.Routing.mapbox('pk.eyJ1IjoiYm9uaXMiLCJhIjoiY2o5NWNmNzFsMWZ5ZDMzbXc4cHU2YndrcSJ9.QZy2V8z1VZQyOqwGfab4Rw');

var greenIcon = new L.Icon({
    iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
    shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
    iconSize: [25, 41],
    iconAnchor: [12, 41],
    popupAnchor: [1, -34],
    shadowSize: [41, 41]
});

function render_marker(index, marker) {
    if (markers[index] == null) {
        marker_state++;
    } else {
        map.removeLayer(markers[index]);
    }
    markers[index] = marker;
    markers[index].addTo(map);
}

function show_route() {
    waypoints = [];
    waypoints.push({latLng: markers[0].getLatLng()});
    waypoints.push({latLng: markers[1].getLatLng()});
    router.route(waypoints, function (err, routes) {
        if (!err) {
            markers.push(L.Routing.line(routes[0], options = {
                styles: [
                    {color: 'black', opacity: 0.15, weight: 9},
                    {color: 'white', opacity: 0.8, weight: 6},
                    {color: 'chartreuse', opacity: 1, weight: 2}
                    ]
            }));
            markers[2].addTo(map);
        }
    });
}

function show_place_on_map(input_id, city_id) {
    $.ajax({
        url: coordinates_url,
        data: {'city_id': city_id},
        dataType: 'json',
        success: function (coords) {
            ref_id = "searchtrip_origin_auto";
            if (!$.isEmptyObject(coords)) {
                var marker = L.marker([coords.lat, coords.lon], {title: coords.name});
                if (marker_state >= 2) {
                    if (input_id === ref_id) {
                        markers.forEach(function (obj) {
                            map.removeLayer(obj);
                        });
                        marker_state = 0;
                        markers.length = 0;
                    } else {
                        map.removeLayer(markers.pop());
                    }
                }
                if (input_id === ref_id) {
                    marker.setIcon(greenIcon);
                    render_marker(0, marker);
                }
                else {
                    render_marker(1, marker);
                    if (marker_state >= 2) {
                        show_route();
                    }
                }
            }
        }
    });
}<commit_msg>Change js datetimepicker event name to properly intercept it<commit_after>$(function () {
    $(".city-autocomplete").on("autocompleteselect", function (event, ui) {
        show_place_on_map(event.target.id, ui.item.id);
    });
})

// Datepicker init
$(function () {
    $('#datetimepicker').datetimepicker({
        format: 'DD/MM/YYYY HH:mm',
        inline: true,
        sideBySide: true,
        stepping: 10,
        minDate: moment().add(1, 'days').valueOf(),
        useCurrent: true,
    }).on("dp.change", function (event) {
        document.getElementById("searchtrip_datetime").value = $('#datetimepicker').data('DateTimePicker').date().unix();
    }).trigger("changeDate").show();
});

var marker_state = 0;
var markers = new Array();
var router = L.Routing.mapbox('pk.eyJ1IjoiYm9uaXMiLCJhIjoiY2o5NWNmNzFsMWZ5ZDMzbXc4cHU2YndrcSJ9.QZy2V8z1VZQyOqwGfab4Rw');

var greenIcon = new L.Icon({
    iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
    shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
    iconSize: [25, 41],
    iconAnchor: [12, 41],
    popupAnchor: [1, -34],
    shadowSize: [41, 41]
});

function render_marker(index, marker) {
    if (markers[index] == null) {
        marker_state++;
    } else {
        map.removeLayer(markers[index]);
    }
    markers[index] = marker;
    markers[index].addTo(map);
}

function show_route() {
    waypoints = [];
    waypoints.push({latLng: markers[0].getLatLng()});
    waypoints.push({latLng: markers[1].getLatLng()});
    router.route(waypoints, function (err, routes) {
        if (!err) {
            markers.push(L.Routing.line(routes[0], options = {
                styles: [
                    {color: 'black', opacity: 0.15, weight: 9},
                    {color: 'white', opacity: 0.8, weight: 6},
                    {color: 'chartreuse', opacity: 1, weight: 2}
                    ]
            }));
            markers[2].addTo(map);
        }
    });
}

function show_place_on_map(input_id, city_id) {
    $.ajax({
        url: coordinates_url,
        data: {'city_id': city_id},
        dataType: 'json',
        success: function (coords) {
            ref_id = "searchtrip_origin_auto";
            if (!$.isEmptyObject(coords)) {
                var marker = L.marker([coords.lat, coords.lon], {title: coords.name});
                if (marker_state >= 2) {
                    if (input_id === ref_id) {
                        markers.forEach(function (obj) {
                            map.removeLayer(obj);
                        });
                        marker_state = 0;
                        markers.length = 0;
                    } else {
                        map.removeLayer(markers.pop());
                    }
                }
                if (input_id === ref_id) {
                    marker.setIcon(greenIcon);
                    render_marker(0, marker);
                }
                else {
                    render_marker(1, marker);
                    if (marker_state >= 2) {
                        show_route();
                    }
                }
            }
        }
    });
}<|endoftext|>


<commit_before>'use strict';

var AdmZip = require('adm-zip');
var fs = require('fs');
var mkdir = require('mkdirp');
var mout = require('mout');
var path = require('path');
var pipeline = require('stream-combiner');
var rm = require('rimraf');
var tar = require('tar');
var temp = require('temp');
var zlib = require('zlib');

/**
 * Initialize Decompress with options
 *
 * Options:
 *
 *   - `ext` String with file name, MIME type, etc
 *   - `path` Path to extract to
 *
 * @param {Object} opts
 * @api private
 */

function Decompress(opts) {
    opts = opts || {};
    opts.strip = opts.strip || 0;
    this.opts = opts;
    this.path = opts.path || process.cwd();
    this.ext = opts.ext || '';
    this.strip = opts.strip >= 0 ? opts.strip : 0;
    this.extractors = {
        '.zip': this._extractZip,
        '.tar': this._extractTar,
        '.tar.gz': this._extractTarGz,
        '.tgz': this._extractTarGz,
        'application/zip': this._extractZip,
        'application/x-tar': this._extractTar,
        'application/x-tgz': this._extractTarGz
    };
    this.extractorTypes = Object.keys(this.extractors);
    this.extractor = this._getExtractor(this.ext);
}

/**
 * Extract an archive
 *
 * @api public
 */

Decompress.prototype.extract = function () {
    var self = this;
    var stream = this.extractor();

    if (!fs.existsSync(this.path)) {
        mkdir.sync(self.path);
    }

    return stream;
};

/**
 * Check if a file can be extracted
 *
 * @param {String} src
 * @param {String} mime
 * @api public
 */

Decompress.prototype.canExtract = function (src, mime) {
    if (this._getExtractor(src)) {
        return true;
    }

    if (mime && this._getExtractor(mime)) {
        return true;
    }

    return false;
};

/**
 * Get the extractor for a desired file
 *
 * @param {String} src
 * @api private
 */

Decompress.prototype._getExtractor = function (src) {
    src = src.toLowerCase();

    var ext = mout.array.find(this.extractorTypes, function (ext) {
        return mout.string.endsWith(src, ext);
    });

    return ext ? this.extractors[ext] : null;
};

/**
 * Extract a zip file
 *
 * @api private
 */

Decompress.prototype._extractZip = function () {
    var self = this;
    var tmp = temp.path({ suffix: '.zip' });

    var stream = fs.createWriteStream(tmp).on('close', function () {
        var zip = new AdmZip(tmp);
        var zipEntries = zip.getEntries();

        zipEntries.forEach(function (entry) {
            var ef = path.basename(entry.entryName.toString());
            var ed = path.dirname(entry.entryName.toString().split('/').slice(self.strip).join('/'));
            var e = path.join(ed, ef);
            var d = path.join(self.path, e);
            var c = entry.getData().toString();

            if (!entry.isDirectory) {
                mkdir.sync(path.dirname(d));
                fs.writeFileSync(d, c);
            }
        });
    });

    return stream.on('close', function () {
        rm.sync(tmp);
    });
};

/**
 * Extract a tar file
 *
 * @api private
 */

Decompress.prototype._extractTar = function () {
    var stream = tar.Extract(this.opts);
    return stream;
};

/**
 * Extract a tar.gz file
 *
 * @api private
 */

Decompress.prototype._extractTarGz = function () {
    var stream = zlib.Unzip();
    var dest = tar.Extract(this.opts);

    return pipeline(stream, dest);
};

/**
 * Module exports
 */

module.exports.extract = function (opts) {
    var decompress = new Decompress(opts);
    return decompress.extract();
};

module.exports.canExtract = function (src, mime) {
    var decompress = new Decompress();
    return decompress.canExtract(src, mime);
};
<commit_msg>Partial require of mout library<commit_after>'use strict';

var AdmZip = require('adm-zip');
var fs = require('fs');
var mkdir = require('mkdirp');
var find = require('mout/array/find');
var endsWith = require('mout/string/endsWith');
var path = require('path');
var pipeline = require('stream-combiner');
var rm = require('rimraf');
var tar = require('tar');
var temp = require('temp');
var zlib = require('zlib');

/**
 * Initialize Decompress with options
 *
 * Options:
 *
 *   - `ext` String with file name, MIME type, etc
 *   - `path` Path to extract to
 *
 * @param {Object} opts
 * @api private
 */

function Decompress(opts) {
    opts = opts || {};
    opts.strip = opts.strip || 0;
    this.opts = opts;
    this.path = opts.path || process.cwd();
    this.ext = opts.ext || '';
    this.strip = opts.strip >= 0 ? opts.strip : 0;
    this.extractors = {
        '.zip': this._extractZip,
        '.tar': this._extractTar,
        '.tar.gz': this._extractTarGz,
        '.tgz': this._extractTarGz,
        'application/zip': this._extractZip,
        'application/x-tar': this._extractTar,
        'application/x-tgz': this._extractTarGz
    };
    this.extractorTypes = Object.keys(this.extractors);
    this.extractor = this._getExtractor(this.ext);
}

/**
 * Extract an archive
 *
 * @api public
 */

Decompress.prototype.extract = function () {
    var self = this;
    var stream = this.extractor();

    if (!fs.existsSync(this.path)) {
        mkdir.sync(self.path);
    }

    return stream;
};

/**
 * Check if a file can be extracted
 *
 * @param {String} src
 * @param {String} mime
 * @api public
 */

Decompress.prototype.canExtract = function (src, mime) {
    if (this._getExtractor(src)) {
        return true;
    }

    if (mime && this._getExtractor(mime)) {
        return true;
    }

    return false;
};

/**
 * Get the extractor for a desired file
 *
 * @param {String} src
 * @api private
 */

Decompress.prototype._getExtractor = function (src) {
    src = src.toLowerCase();

    var ext = find(this.extractorTypes, function (ext) {
        return endsWith(src, ext);
    });

    return ext ? this.extractors[ext] : null;
};

/**
 * Extract a zip file
 *
 * @api private
 */

Decompress.prototype._extractZip = function () {
    var self = this;
    var tmp = temp.path({ suffix: '.zip' });

    var stream = fs.createWriteStream(tmp).on('close', function () {
        var zip = new AdmZip(tmp);
        var zipEntries = zip.getEntries();

        zipEntries.forEach(function (entry) {
            var ef = path.basename(entry.entryName.toString());
            var ed = path.dirname(entry.entryName.toString().split('/').slice(self.strip).join('/'));
            var e = path.join(ed, ef);
            var d = path.join(self.path, e);
            var c = entry.getData().toString();

            if (!entry.isDirectory) {
                mkdir.sync(path.dirname(d));
                fs.writeFileSync(d, c);
            }
        });
    });

    return stream.on('close', function () {
        rm.sync(tmp);
    });
};

/**
 * Extract a tar file
 *
 * @api private
 */

Decompress.prototype._extractTar = function () {
    var stream = tar.Extract(this.opts);
    return stream;
};

/**
 * Extract a tar.gz file
 *
 * @api private
 */

Decompress.prototype._extractTarGz = function () {
    var stream = zlib.Unzip();
    var dest = tar.Extract(this.opts);

    return pipeline(stream, dest);
};

/**
 * Module exports
 */

module.exports.extract = function (opts) {
    var decompress = new Decompress(opts);
    return decompress.extract();
};

module.exports.canExtract = function (src, mime) {
    var decompress = new Decompress();
    return decompress.canExtract(src, mime);
};
<|endoftext|>


<commit_before>'use strict';

const express = require('express');
const http = require('http');
const swig = require('swig');
const Wechat = require('../lib');
const path = require('path');
const fs = require('fs');
const debug = require('debug')('wechat');
const bodyParser = require('body-parser');
const isEmpty = require('lodash.isempty');

const cookieParser = require('cookie-parser');
const session = require('express-session');

const wechatConfig = require('./wechat-config');
const Order = require('./Order');

const Card = Wechat.Card;
const Payment = Wechat.Payment;

const DOMAIN = 'http://beautytest.yjyyun.com';

const wx = new Wechat(wechatConfig);

const order = new Order({ payment: wx.payment });

const app = express();
swig.setDefaults({
  cache: false,
});

app.engine('html', swig.renderFile);
app.set('view engine', 'html');
app.enable('trust proxy');
app.set('views', path.join(__dirname));

app.use(cookieParser());
app.use(
  session({
    name: 'sid',
    secret: 'wechat-app',
    saveUninitialized: true,
    resave: true,
  })
);

app.use(function(req, res, next) {
  res.locals.appId = wechatConfig.appId;
  res.locals.domain = wechatConfig.domain;
  next();
});

app.get('/', function(req, res) {
  //also you can generate one at runtime:
  const implicitOAuthUrl = wx.oauth.generateOAuthUrl(
    DOMAIN + '/implicit-oauth',
    'snsapi_base'
  );
  res.render('index', {
    oauthUrl: wx.oauth.snsUserInfoUrl,
    implicitOAuth: implicitOAuthUrl,
  });
});

app.get('/api/wechat', function(req, res) {
  if (wx.jssdk.verifySignature(req.query)) {
    res.send(req.query.echostr);
    return;
  }
  res.send('error');
});

app.get('/get-signature', function(req, res) {
  console.log(req.query);
  wx.jssdk.getSignature(req.query.url).then(
    data => {
      console.log('OK', data);
      res.json(data);
    },
    reason => {
      console.error(reason);
      res.json(reason);
    }
  );
});

/**
 * @see wechatRedirectUrl in Wechat config
 */
app.get('/oauth', function(req, res) {
  //use default openid as the key
  const key = req.session.openid;

  //use custom key for oauth token store
  // const key = req.sessionID;
  // console.log('oauth sessionID: %s', key);
  wx.oauth.getUserInfo(req.query.code, key).then(function(userProfile) {
    console.log('userProfile:', userProfile);
    //set openid to session to use in following request
    req.session.openid = userProfile.openid;
    console.log(req.session.openid);
    res.render('oauth', {
      wechatInfo: JSON.stringify(userProfile),
    });
  });
});

app.get('/implicit-oauth', function(req, res) {
  const redirect = req.query.from;
  wx.oauth.getUserBaseInfo(req.query.code).then(function(tokenInfo) {
    console.log('implicit oauth: ', tokenInfo);
    // console.log('implicit oauth: ', JSON.stringify(tokenInfo));
    req.session.openid = tokenInfo.openid;
    if (redirect) {
      res.redirect(redirect);
      return;
    }
    res.render('oauth', {
      wechatInfo: JSON.stringify(tokenInfo, null, 2),
    });
  });
});

app.get('/oauth-cache', function(req, res) {
  const key = req.session.openid;
  console.log('openid: ', key);

  // const sid = req.sessionID;
  // console.log('sessionID: %s', sid);

  //get user info without code, but with cached access token,
  //if cached token is expired, or cannot refresh the token,
  //it will redirect to the "/oauth" router above in catch handler to get new code
  wx.oauth
    .getUserInfo(null, key)
    .then(function(userProfile) {
      console.log(userProfile);
      res.render('oauth', {
        wechatInfo: JSON.stringify(userProfile),
      });
    })
    .catch(() => {
      //need to get new code
      res.redirect(wx.oauth.snsUserInfoUrl);
    });
});

app.get('/choose-card', function(req, res) {
  const qs = req.query;
  wx.card
    .getCardSignature(qs.shopId, qs.cardType, qs.cardId)
    .then(sigInfo => {
      res.json(sigInfo);
    })
    .catch(reason => {
      res.json(reason);
    });
});

app.get('/get-card-ext', function(req, res) {
  const qs = req.query;
  wx.card
    .getCardExt(qs.cardId, '', '', '', 'wechat-jssdk')
    .then(sigInfo => {
      res.json({ data: sigInfo });
    })
    .catch(reason => {
      res.json(reason);
    });
});

app.get('/decode-card-code', function(req, res) {
  wx.card
    .decodeCardCode(req.query.encryptCode)
    .then(data => {
      res.json(data);
    })
    .catch(data => {
      res.json(data);
    });
});

app.get('/client.js', function(req, res) {
  res.sendFile(path.join(__dirname, '../dist/client.js'));
});

app.get('/create-order', function(req, res) {
  const openid = req.session.openid;
  console.log('req.session.openid:', openid);
  const orderCase = req.query.case;
  const orderInfo = {
    openid: req.session.openid || 'oy5F1wQTfhx4-V3L5TcUn5V9v2Lo',
    spbill_create_ip: '104.247.128.2', //req.ip,
  };
  let p = undefined;
  switch (orderCase) {
    case '1':
      p = order.createOrderCase1(orderInfo);
      break;
    case '2':
      p = order.createOrderCase2(orderInfo);
      break;
    case '3':
      p = order.createOrderCase3(orderInfo);
      break;
    case '4':
      p = order.createOrderCase4(orderInfo);
      break;
    case '5':
      p = order.createOrderCase5(orderInfo);
      break;
    case '6':
      p = order.createOrderCase6(orderInfo);
      break;
    case '7':
      p = order.createOrderCase7(orderInfo);
      break;
    case '8':
      p = order.createOrderCase8(orderInfo);
      break;
    default:
      p = order.createOrderCase1(orderInfo);
      break;
  }

  p
    .then(data => {
      console.log(data.orderId);
      req.session.orderId = data.orderId;
      res.json(data.chooseWXPay);
    })
    .catch(err => {
      res.json(err);
    });
});

app.get('/query-order', function(req, res) {
  const orderId = req.query.tradeNo || req.session.orderId;
  if(!orderId) {
    res.json({
      msg: 'no available out_trade_no!'
    });
    return;
  }

  order
    .queryOrder(orderId)
    .then(data => {
      res.json(data);
    })
    .catch(err => {
      res.json(err);
    });
});

app.get('/download-bill', function (req, res) {
  const query = req.query;
  wx.payment.downloadBill(query.billDate, Payment.DOWNLOAD_BILL_TYPE.SUCCESS)
    .then(result => {
      console.log('digest: ', result.digest);
      if(result.data) {
        result.data.pipe(res);
      }
    })
    .catch(err => {
      console.error(err);
      res.json(err);
    })

});

//demo: unified order pay result notify_url goes here
app.post('/pay-result-notify', bodyParser.text(), function(req, res) {
  wx.payment
    .parseNotifyData(req.body)
    .then(data => {
      const sign = data.sign;
      data.sign = undefined;
      const genSignData = wx.payment.generateSignature(data, data.sign_type);
      //case test, only case 6 will return sign
      if (!sign ||  (sign && sign === genSignData.sign)) {
        const tradeNo = data.out_trade_no;
        if(tradeNo) {
          const order = order.getOrderFromDB(tradeNo);
          //order info inconsistent
          if(isEmpty(order) || order.total_fee != data.total_fee) {
            return Promise.reject(new Error('notify data not consistent!'));
          }
          //already processed
          if(order && order.processed) {
            return;
          }
        }

        order.updateNotifyResult(data);
        //sign check and send back
        wx.payment.replyData(true).then(ret => {
          res.send(ret);
        });
        return;
      }
      return Promise.reject(new Error('notify sign not matched!'));
    })
    .catch(err => {
      console.error(err);
      // wx.payment.replyData(false).then(ret => {
      //   res.send(ret);
      // });
    });
});
//process refund notify result
app.post('/refund-result-notify', bodyParser.text(), function(req, res) {
  wx.payment
    .decryptRefundNotifyResult(req.body)
    .then(result => {
      const parsedXMLData = result.parsedXMLData;
      const decryptedReqInfoData = result.decryptedData;
      order.updateNotifyRefundResult(
        Object.assign(parsedXMLData, decryptedReqInfoData)
      );

      wx.payment.replyData(true).then(ret => {
        res.send(ret);
      });
    })
    .catch(err => {
      console.error(err);
      wx.payment.replyData(false).then(ret => {
        res.send(ret);
      });
    });
});

const server = http.createServer(app);
const port = process.env.PORT || 3000;
//should use like nginx to proxy the request to 3000, the signature domain must be on PORT 80.
server.listen(port);
server.on('listening', function() {
  debug('Express listening on port %d', port);
});

process.on('exit', function() {
  wx.store.flush();
});
<commit_msg>:bug:minor<commit_after>'use strict';

const express = require('express');
const http = require('http');
const swig = require('swig');
const Wechat = require('../lib');
const path = require('path');
const fs = require('fs');
const debug = require('debug')('wechat');
const bodyParser = require('body-parser');
const isEmpty = require('lodash.isempty');

const cookieParser = require('cookie-parser');
const session = require('express-session');

const wechatConfig = require('./wechat-config');
const Order = require('./Order');

const Card = Wechat.Card;
const Payment = Wechat.Payment;

const DOMAIN = wechatConfig.domain;

const wx = new Wechat(wechatConfig);

const order = new Order({ payment: wx.payment });

const app = express();
swig.setDefaults({
  cache: false,
});

app.engine('html', swig.renderFile);
app.set('view engine', 'html');
app.enable('trust proxy');
app.set('views', path.join(__dirname));

app.use(cookieParser());
app.use(
  session({
    name: 'sid',
    secret: 'wechat-app',
    saveUninitialized: true,
    resave: true,
  })
);

app.use(function(req, res, next) {
  res.locals.appId = wechatConfig.appId;
  res.locals.domain = DOMAIN;
  next();
});

app.get('/', function(req, res) {
  //also you can generate one at runtime:
  const implicitOAuthUrl = wx.oauth.generateOAuthUrl(
    DOMAIN + '/implicit-oauth',
    'snsapi_base'
  );
  res.render('index', {
    oauthUrl: wx.oauth.snsUserInfoUrl,
    implicitOAuth: implicitOAuthUrl,
  });
});

app.get('/api/wechat', function(req, res) {
  if (wx.jssdk.verifySignature(req.query)) {
    res.send(req.query.echostr);
    return;
  }
  res.send('error');
});

app.get('/get-signature', function(req, res) {
  console.log(req.query);
  wx.jssdk.getSignature(req.query.url).then(
    data => {
      console.log('OK', data);
      res.json(data);
    },
    reason => {
      console.error(reason);
      res.json(reason);
    }
  );
});

/**
 * @see wechatRedirectUrl in Wechat config
 */
app.get('/oauth', function(req, res) {
  //use default openid as the key
  const key = req.session.openid;

  //use custom key for oauth token store
  // const key = req.sessionID;
  // console.log('oauth sessionID: %s', key);
  wx.oauth.getUserInfo(req.query.code, key).then(function(userProfile) {
    console.log('userProfile:', userProfile);
    //set openid to session to use in following request
    req.session.openid = userProfile.openid;
    console.log(req.session.openid);
    res.render('oauth', {
      wechatInfo: JSON.stringify(userProfile),
    });
  });
});

app.get('/implicit-oauth', function(req, res) {
  const redirect = req.query.from;
  wx.oauth.getUserBaseInfo(req.query.code).then(function(tokenInfo) {
    console.log('implicit oauth: ', tokenInfo);
    // console.log('implicit oauth: ', JSON.stringify(tokenInfo));
    req.session.openid = tokenInfo.openid;
    if (redirect) {
      res.redirect(redirect);
      return;
    }
    res.render('oauth', {
      wechatInfo: JSON.stringify(tokenInfo, null, 2),
    });
  });
});

app.get('/oauth-cache', function(req, res) {
  const key = req.session.openid;
  console.log('openid: ', key);

  // const sid = req.sessionID;
  // console.log('sessionID: %s', sid);

  //get user info without code, but with cached access token,
  //if cached token is expired, or cannot refresh the token,
  //it will redirect to the "/oauth" router above in catch handler to get new code
  wx.oauth
    .getUserInfo(null, key)
    .then(function(userProfile) {
      console.log(userProfile);
      res.render('oauth', {
        wechatInfo: JSON.stringify(userProfile),
      });
    })
    .catch(() => {
      //need to get new code
      res.redirect(wx.oauth.snsUserInfoUrl);
    });
});

app.get('/choose-card', function(req, res) {
  const qs = req.query;
  wx.card
    .getCardSignature(qs.shopId, qs.cardType, qs.cardId)
    .then(sigInfo => {
      res.json(sigInfo);
    })
    .catch(reason => {
      res.json(reason);
    });
});

app.get('/get-card-ext', function(req, res) {
  const qs = req.query;
  wx.card
    .getCardExt(qs.cardId, '', '', '', 'wechat-jssdk')
    .then(sigInfo => {
      res.json({ data: sigInfo });
    })
    .catch(reason => {
      res.json(reason);
    });
});

app.get('/decode-card-code', function(req, res) {
  wx.card
    .decodeCardCode(req.query.encryptCode)
    .then(data => {
      res.json(data);
    })
    .catch(data => {
      res.json(data);
    });
});

app.get('/client.js', function(req, res) {
  res.sendFile(path.join(__dirname, '../dist/client.js'));
});

app.get('/create-order', function(req, res) {
  const openid = req.session.openid;
  console.log('req.session.openid:', openid);
  const orderCase = req.query.case;
  const orderInfo = {
    openid: req.session.openid || 'oy5F1wQTfhx4-V3L5TcUn5V9v2Lo',
    spbill_create_ip: '104.247.128.2', //req.ip,
  };
  let p = undefined;
  switch (orderCase) {
    case '1':
      p = order.createOrderCase1(orderInfo);
      break;
    case '2':
      p = order.createOrderCase2(orderInfo);
      break;
    case '3':
      p = order.createOrderCase3(orderInfo);
      break;
    case '4':
      p = order.createOrderCase4(orderInfo);
      break;
    case '5':
      p = order.createOrderCase5(orderInfo);
      break;
    case '6':
      p = order.createOrderCase6(orderInfo);
      break;
    case '7':
      p = order.createOrderCase7(orderInfo);
      break;
    case '8':
      p = order.createOrderCase8(orderInfo);
      break;
    default:
      p = order.createOrderCase1(orderInfo);
      break;
  }

  p
    .then(data => {
      console.log(data.orderId);
      req.session.orderId = data.orderId;
      res.json(data.chooseWXPay);
    })
    .catch(err => {
      res.json(err);
    });
});

app.get('/query-order', function(req, res) {
  const orderId = req.query.tradeNo || req.session.orderId;
  if(!orderId) {
    res.json({
      msg: 'no available out_trade_no!'
    });
    return;
  }

  order
    .queryOrder(orderId)
    .then(data => {
      res.json(data);
    })
    .catch(err => {
      res.json(err);
    });
});

app.get('/download-bill', function (req, res) {
  const query = req.query;
  wx.payment.downloadBill(query.billDate, Payment.DOWNLOAD_BILL_TYPE.SUCCESS)
    .then(result => {
      console.log('digest: ', result.digest);
      if(result.data) {
        result.data.pipe(res);
      }
    })
    .catch(err => {
      console.error(err);
      res.json(err);
    })

});

//demo: unified order pay result notify_url goes here
app.post('/pay-result-notify', bodyParser.text(), function(req, res) {
  wx.payment
    .parseNotifyData(req.body)
    .then(data => {
      const sign = data.sign;
      data.sign = undefined;
      const genSignData = wx.payment.generateSignature(data, data.sign_type);
      //case test, only case 6 will return sign
      if (!sign ||  (sign && sign === genSignData.sign)) {
        const tradeNo = data.out_trade_no;
        if(tradeNo) {
          const order = order.getOrderFromDB(tradeNo);
          //order info inconsistent
          if(isEmpty(order) || order.total_fee != data.total_fee) {
            return Promise.reject(new Error('notify data not consistent!'));
          }
          //already processed
          if(order && order.processed) {
            return;
          }
        }

        order.updateNotifyResult(data);
        //sign check and send back
        wx.payment.replyData(true).then(ret => {
          res.send(ret);
        });
        return;
      }
      return Promise.reject(new Error('notify sign not matched!'));
    })
    .catch(err => {
      console.error(err);
      // wx.payment.replyData(false).then(ret => {
      //   res.send(ret);
      // });
    });
});
//process refund notify result
app.post('/refund-result-notify', bodyParser.text(), function(req, res) {
  wx.payment
    .decryptRefundNotifyResult(req.body)
    .then(result => {
      const parsedXMLData = result.parsedXMLData;
      const decryptedReqInfoData = result.decryptedData;
      order.updateNotifyRefundResult(
        Object.assign(parsedXMLData, decryptedReqInfoData)
      );

      wx.payment.replyData(true).then(ret => {
        res.send(ret);
      });
    })
    .catch(err => {
      console.error(err);
      wx.payment.replyData(false).then(ret => {
        res.send(ret);
      });
    });
});

const server = http.createServer(app);
const port = process.env.PORT || 3000;
//should use like nginx to proxy the request to 3000, the signature domain must be on PORT 80.
server.listen(port);
server.on('listening', function() {
  debug('Express listening on port %d', port);
});

process.on('exit', function() {
  wx.store.flush();
});
<|endoftext|>


<commit_before>FOAModel({
  name: 'Todo',
  properties: [
    { name: 'id' },
    { name: 'completed', model_: 'BooleanProperty' },
    { name: 'text' }
  ]
});

FOAModel({
  name: 'Controller',
  properties: [
    {
      name: 'input',
      postSet: function(_, text) {
        if ( ! text ) return;
        this.todoDAO.put(Todo.create({text: text}));
        this.input = '';
      },
      view: { model_: 'TextFieldView', placeholder: 'What needs to be done?' }
    },
    {
      name: 'todoDAO',
      valueFactory: function() {
        return SeqNoDAO.create({
          property: Todo.ID,
          delegate: CachingDAO.create(MDAO.create({model: Todo})/*.addIndex(Todo.COMPLETED)*/, IDBDAO.create({model: Todo}))
        });
      }
    },
    { name: 'filteredDAO', view: { model_: 'DAOListController', rowView: 'TodoView' } },
    { name: 'completedCount', model_: 'IntegerProperty' },
    { name: 'activeCount',    model_: 'IntegerProperty' },
    {
      name: 'query',
      postSet: function(_, q) { this.filteredDAO = this.todoDAO.where(q); },
      defaultValue: TRUE,
      view: ChoiceListView.create({
        choices: [
          [ TRUE,                      'All' ],
          [ EQ(Todo.COMPLETED, FALSE), 'Active' ],
          [ EQ(Todo.COMPLETED, TRUE),  'Completed' ]
        ]
      })
    }
  ],
  methods: {
    init: function() {
      this.SUPER();
      this.query = this.query;  // causes filteredDAO to be set
      this.todoDAO.listen(this.onDAOUpdate);
      this.onDAOUpdate();
    }
  },
  actions: [
    {
      name: 'toggle',
      action: function() {
        this.todoDAO.select(UPDATE(SET(Todo.COMPLETED, this.activeCount), this.todoDAO));
      }
    },
    {
      name: 'clear',
      labelFn: function() { return 'Clear completed (' + this.completedCount + ')'; },
      isEnabled: function() { return this.completedCount > 0; },
      action: function() { this.todoDAO.where(EQ(Todo.COMPLETED, TRUE)).removeAll(); }
    }
  ],
  listeners: [
    {
      name: 'onDAOUpdate',
      isAnimated: true,
      code: function() {
        this.todoDAO.select(GROUP_BY(Todo.COMPLETED, COUNT()))(function (counts) {
          this.completedCount = counts.groups['true'];
          this.activeCount    = counts.groups['false'];
        }.bind(this));
      }
    }
  ]
});

FOAModel({ name: 'TodoView',           extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
FOAModel({ name: 'TodoControllerView', extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
<commit_msg>Todo: Simplified query expressions.<commit_after>FOAModel({
  name: 'Todo',
  properties: [
    { name: 'id' },
    { name: 'completed', model_: 'BooleanProperty' },
    { name: 'text' }
  ]
});

FOAModel({
  name: 'Controller',
  properties: [
    {
      name: 'input',
      postSet: function(_, text) {
        if ( ! text ) return;
        this.todoDAO.put(Todo.create({text: text}));
        this.input = '';
      },
      view: { model_: 'TextFieldView', placeholder: 'What needs to be done?' }
    },
    {
      name: 'todoDAO',
      valueFactory: function() { // return EasyDAO.create({model: Todo, seqNo: true, cache: true, type: StorageDAO})
        return SeqNoDAO.create({
          property: Todo.ID,
          delegate: CachingDAO.create(MDAO.create({model: Todo})/*.addIndex(Todo.COMPLETED)*/, IDBDAO.create({model: Todo}))
        });
      }
    },
    { name: 'filteredDAO',    model_: 'DAOProperty', view: { model_: 'DAOListController', rowView: 'TodoView' } },
    { name: 'completedCount', model_: 'IntegerProperty' },
    { name: 'activeCount',    model_: 'IntegerProperty' },
    {
      name: 'query',
      postSet: function(_, q) { this.filteredDAO = this.todoDAO.where(q); },
      defaultValue: TRUE,
      view: ChoiceListView.create({
        choices: [ [ TRUE, 'All' ], [ Todo.COMPLETED, 'Active' ], [ NOT(Todo.COMPLETED), 'Completed' ] ]
      })
    }
  ],
  methods: {
    init: function() {
      this.SUPER();
      this.query = this.query;  // causes filteredDAO to be set
      this.todoDAO.listen(this.onDAOUpdate);
      this.onDAOUpdate();
    }
  },
  actions: [
    {
      name: 'toggle',
      action: function() { this.todoDAO.select(UPDATE(SET(Todo.COMPLETED, this.activeCount), this.todoDAO)); }
    },
    {
      name: 'clear',
      labelFn:   function() { return 'Clear completed (' + this.completedCount + ')'; },
      isEnabled: function() { return this.completedCount > 0; },
      action:    function() { this.todoDAO.where(Todo.COMPLETED).removeAll(); }
    }
  ],
  listeners: [
    {
      name: 'onDAOUpdate',
      isAnimated: true,
      code: function() {
        this.todoDAO.select(GROUP_BY(Todo.COMPLETED, COUNT()))(function (q) {
          this.completedCount = q.groups['true'];
          this.activeCount    = q.groups['false'];
        }.bind(this));
      }
    }
  ]
});

FOAModel({ name: 'TodoView',           extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
FOAModel({ name: 'TodoControllerView', extendsModel: 'DetailView', templates: [ { name: 'toHTML' } ] });
<|endoftext|>


<commit_before>(function (exports) {
    "use strict";

    /*jshint curly:false */
    /*global d3 */

    exports.dendrogram = function () {

        var dendrogram, width, height, labels, cluster, diagonal;

        width = 300;
        height = 600;

     // The labels of the initial observations.
        labels = [];

     // Create cluster layout.
        cluster = d3.layout.cluster();

        diagonal = d3.svg.diagonal()
                     .projection(function (d) {
                         return [d.y, d.x];
                      });

        dendrogram = function (selection) {

            selection.each(function (data) {

                var svg, group, nodes, link, node;

             // Select existing SVG elements.
                svg = d3.select(this)
                        .selectAll("svg")
                        .data([data]) // Trick to create only one svg element for each data set.

             // Create non-existing SVG elements.
                svg.enter()
                   .append("svg");

             // Update both existing and newly created SVG elements.
                svg.attr("width", width)
                   .attr("height", height);

                group = svg.append("g")
                           .attr("transform", "translate(40, 0)");

                cluster.size([height, width - 160]);

                nodes = cluster.nodes(data[0]);

                link = group.selectAll("path.link")
                            .data(cluster.links(nodes))
                            .enter()
                            .append("path")
                            .attr("class", "link")
                            .attr("d", diagonal);

                node = group.selectAll("g.node")
                            .data(nodes)
                            .enter()
                            .append("g")
                            .attr("class", "node")
                            .attr("transform", function (d) {
                                return "translate(" + d.y + "," + d.x + ")";
                             });

                node.append("circle")
                    .attr("r", 4.5);

                node.append("text")
                    .attr("dx", function (d) {
                        return d.children ? -8 : 8;
                     })
                    .attr("dy", 3)
                    .attr("text-anchor", function (d) {
                        return d.children ? "end" : "start";
                     })
                    .text(function (d) {
                        return labels[d.label];
                     });

            });

        };

        dendrogram.height = function (_) {
            if (!arguments.length) return height;
            height = _;
            return dendrogram;
        };

        dendrogram.width = function (_) {
            if (!arguments.length) return width;
            width = _;
            return dendrogram;
        };

        dendrogram.labels = function (_) {
            if (!arguments.length) return labels;
            labels = _;
            return dendrogram;
        };

        return dendrogram;

    };

}(window));
<commit_msg>Use joins in dendrogram.<commit_after>(function (exports) {
    "use strict";

    /*jshint curly:false */
    /*global d3 */

    exports.dendrogram = function () {

        var dendrogram, width, height, labels, clusterLayout, diagonal;

        width = 300;
        height = 600;

     // The labels of the initial observations.
        labels = [];

     // Create cluster layout.
        clusterLayout = d3.layout.cluster();

        diagonal = d3.svg.diagonal()
                     .projection(function (d) {
                         return [d.y, d.x];
                      });

        dendrogram = function (selection) {

            selection.each(function (data) {

                var nodes, links, svg, dendrogram, edges, vertices, enterVertex;

                clusterLayout.size([height, width - 160]);
                nodes = clusterLayout.nodes(data[0]);
                links = clusterLayout.links(nodes);

             // Generate canvas.
                svg = d3.select(this)
                        .selectAll("svg")
                        .data([data]);

             // Generate chart template.
                svg.enter()
                   .append("svg")
                   .append("g")
                   .attr("id", "dendrogram")
                   .attr("transform", "translate(40, 0)");

             // Update dimensions.
                svg.attr("width", width)
                   .attr("height", height);

                dendrogram = d3.select("g#dendrogram");

             // Generate edges.
                edges = dendrogram.selectAll("path.link")
                                  .data(links);

                edges.enter()
                     .append("path")
                     .attr("class", "link");

                edges.attr("d", diagonal);

                edges.exit()
                     .remove();

             // Until I figure out a way how to join correctly over changing elements,
             // I will just remove what is there first.
                dendrogram.selectAll("g.node")
                          .remove();

             // Generate vertices.
                vertices = dendrogram.selectAll("g.node")
                                     .data(nodes);

                enterVertex = vertices.enter()
                                      .append("g")
                                      .attr("class", "node");
                enterVertex.append("circle")
                           .attr("r", 4.5);
                enterVertex.append("text")
                        .attr("dx", function (d) {
                            return d.children ? -8 : 8;
                         })
                        .attr("dy", 3)
                        .attr("text-anchor", function (d) {
                            return d.children ? "end" : "start";
                         })
                        .text(function (d) {
                            return labels[d.label];
                         });

                vertices.attr("transform", function (d) {
                    return "translate(" + d.y + "," + d.x + ")";
                });

            });

        };

        dendrogram.height = function (_) {
            if (!arguments.length) return height;
            height = _;
            return dendrogram;
        };

        dendrogram.width = function (_) {
            if (!arguments.length) return width;
            width = _;
            return dendrogram;
        };

        dendrogram.labels = function (_) {
            if (!arguments.length) return labels;
            labels = _;
            return dendrogram;
        };

        return dendrogram;

    };

}(window));
<|endoftext|>