<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import React, { PureComponent } from 'react';
import { Form, Input, Select, Button, Card, InputNumber, Row, Col, Pagination, DatePicker } from 'antd';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import { DataSet } from '@antv/data-set';
import moment from 'moment';
import TraceList from '../../components/Trace/TraceList';
import { generateDuration } from '../../utils/time';
import styles from './Trace.less';

const { Option } = Select;
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
const initPaging = {
  pageNum: 1,
  pageSize: 20,
  needTotal: true,
};

@Form.create({
  mapPropsToFields(props) {
    const { variables: { values } } = props.trace;
    const result = {};
    Object.keys(values).filter(_ => _ !== 'range-time-picker').forEach((_) => {
      result[_] = Form.createFormField({
        value: values[_],
      });
    });
    const { duration } = values;
    if (duration) {
      result['range-time-picker'] = Form.createFormField({
        value: [duration.raw.start, duration.raw.end],
      });
    }
    return result;
  },
})
export default class Trace extends PureComponent {
  componentDidMount() {
    const { trace: { variables: { values } } } = this.props;
    const { duration } = values;
    this.props.dispatch({
      type: 'trace/initOptions',
      payload: { variables: { duration: duration.input } },
    });
    const condition = { ...values };
    condition.queryDuration = values.duration.input;
    delete condition.duration;
    this.fetchData(condition, initPaging);
  }
  getDefaultDuration = () => {
    return generateDuration({
      from() {
        return moment().subtract(15, 'minutes');
      },
      to() {
        return moment();
      },
    });
  }
  handleSearch = (e) => {
    if (e) {
      e.preventDefault();
    }
    const { form, dispatch } = this.props;

    form.validateFields((err, fieldsValue) => {
      if (err) return;
      const condition = { ...fieldsValue };
      delete condition['range-time-picker'];
      const rangeTime = fieldsValue['range-time-picker'];
      const duration = generateDuration({ from: () => rangeTime[0], to: () => rangeTime[1] });
      dispatch({
        type: 'trace/saveVariables',
        payload: {
          values: {
            ...condition,
            duration,
            paging: initPaging,
          },
        },
      });
      this.fetchData({ ...condition, queryDuration: duration.input });
    });
  }
  fetchData = (queryCondition, paging = initPaging) => {
    this.props.dispatch({
      type: 'trace/fetchData',
      payload: {
        variables: {
          condition: {
            ...queryCondition,
            paging,
          },
        },
      },
    });
  }
  handleTableChange = (pagination) => {
    const { dispatch, trace: { variables: { values } } } = this.props;
    const condition = {
      ...values,
      paging: {
        pageNum: pagination.current,
        pageSize: pagination.pageSize,
        needTotal: true,
      },
    };
    dispatch({
      type: 'trace/saveVariables',
      payload: {
        values: {
          ...condition,
        },
      },
    });
    delete condition.duration;
    this.fetchData({ ...condition, queryDuration: values.duration.input }, condition.paging);
  }
  handleShowTrace = (traceId) => {
    const { dispatch } = this.props;
    dispatch({
      type: 'trace/fetchSpans',
      payload: { variables: { traceId } },
    });
  }
  renderPointChart = (traces) => {
    if (!traces) {
      return null;
    }
    const ds = new DataSet();
    const dv = ds.createView().source(traces);
    dv.transform({
      type: 'map',
      callback(row) {
        return {
          ...row,
          state: row.isError ? 'error' : 'success',
          startTime: moment(parseInt(row.start, 10)).format('YYYY-MM-DD HH:mm:ss.SSS'),
        };
      },
    });
    return (
      <Chart
        data={dv}
        height={432}
        forceFit
        scale={{
          startTime: {
            tickCount: 4,
          },
        }}
      >
        <Tooltip
          showTitle={false}
          crosshairs={{ type: 'cross' }}
          itemTpl='
{name}
{value}
'
        />
        <Axis name="startTime" />
        <Axis
          name="duration"
          label={{
            formatter: (text) => {
              if (parseInt(text, 10) >= 1000) {
                return `${parseInt(text, 10) / 1000} s`;
              }
              return `${text} ms`;
            },
          }}
        />
        <Legend />
        <Geom
          type="point"
          position="startTime*duration"
          color={['state', state => (state === 'error' ? 'red' : '#1890ff')]}
          opacity={0.65}
          shape="circle"
          size={4}
          tooltip={['operationName*startTime*duration', (operationName, startTime, duration) => {
            return {
              name: operationName,
              value: `
                ${startTime}
                ${duration}ms
              `,
            };
          }]}
        />
      </Chart>);
  }
  renderForm() {
    const { getFieldDecorator } = this.props.form;
    const { trace: { variables: { options } } } = this.props;
    return (
      <Form onSubmit={this.handleSearch} layout="vertical">
        <FormItem label="Time Range">
          {getFieldDecorator('range-time-picker')(
            <RangePicker
              showTime
              disabledDate={current => current && current.valueOf() >= Date.now()}
              format="YYYY-MM-DD HH:mm"
              style={{ width: '100%' }}
            />
          )}
        </FormItem>
        <FormItem label="Application">
          {getFieldDecorator('applicationId')(
            <Select placeholder="All application" style={{ width: '100%' }}>
              {options.applicationId && options.applicationId.map((app) => {
                  return (
                    <Option key={app.key ? app.key : -1} value={app.key}>
                      {app.label}
                    </Option>);
                })}
            </Select>
          )}
        </FormItem>
        <FormItem label="OperationName">
          {getFieldDecorator('operationName')(
            <Input placeholder="eg Kafka/Trace-topic-1/Consumer" />
          )}
        </FormItem>
        <FormItem label="TraceId">
          {getFieldDecorator('traceId')(
            <Input placeholder="eg 3.84.15204769998380001" />
          )}
        </FormItem>
        <Row>
          <Col xs={24} sm={24} md={24} lg={12} xl={12}>
            <FormItem label="Min Duration">
              {getFieldDecorator('minTraceDuration')(
                <InputNumber placeholder="eg 100" />
              )}
            </FormItem>
          </Col>
          <Col xs={24} sm={24} md={24} lg={12} xl={12}>
            <FormItem label="Max Duration">
              {getFieldDecorator('maxTraceDuration')(
                <InputNumber placeholder="eg 5000" />
              )}
            </FormItem>
          </Col>
        </Row>
        <FormItem>
          <Button type="primary" htmlType="submit">Search</Button>
        </FormItem>
      </Form>
    );
  }
  renderPage = (values, total) => {
    if (total < 1) {
      return null;
    }
    let currentPageNum = 1;
    let currentPageSize = 20;
    if (values.paging) {
      const { paging: { pageNum, pageSize } } = values;
      currentPageNum = pageNum;
      currentPageSize = pageSize;
    }
    return (
      <Row type="flex" justify="end">
        <Col>
          <Pagination
            size="small"
            current={currentPageNum}
            pageSize={currentPageSize}
            total={total}
            defaultPageSize={20}
            showSizeChanger
            pageSizeOptions={['20', '50', '100', '200']}
            onChange={(page, pageSize) => {
              this.handleTableChange({ current: page, pageSize });
            }}
            onShowSizeChange={(current, size) => {
              this.handleTableChange({ current: 1, pageSize: size });
            }}
          />
        </Col>
      </Row>);
  }
  render() {
    const { trace: { variables: { values }, data: { queryBasicTraces } }, loading } = this.props;
    return (
      <Card bordered={false}>

          <Row>
            <Col xs={24} sm={24} md={24} lg={8} xl={8}>
              {this.renderForm()}
            </Col>
            <Col xs={24} sm={24} md={24} lg={16} xl={16}>
              {this.renderPointChart(queryBasicTraces.traces)}
            </Col>
          </Row>
          {this.renderPage(values, queryBasicTraces.total)}
          <TraceList
            loading={loading}
            data={queryBasicTraces.traces}
            onClickTraceTag={this.handleShowTrace}
          />
          {this.renderPage(values, queryBasicTraces.total)}

      </Card>
    );
  }
}
<commit_msg>Fix Trace search time is null ,there is an error (#168)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import React, { PureComponent } from 'react';
import { Form, Input, Select, Button, Card, InputNumber, Row, Col, Pagination, DatePicker } from 'antd';
import { Chart, Geom, Axis, Tooltip, Legend } from 'bizcharts';
import { DataSet } from '@antv/data-set';
import moment from 'moment';
import TraceList from '../../components/Trace/TraceList';
import { generateDuration } from '../../utils/time';
import styles from './Trace.less';

const { Option } = Select;
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
const initPaging = {
  pageNum: 1,
  pageSize: 20,
  needTotal: true,
};

@Form.create({
  mapPropsToFields(props) {
    const { variables: { values } } = props.trace;
    const result = {};
    Object.keys(values).filter(_ => _ !== 'range-time-picker').forEach((_) => {
      result[_] = Form.createFormField({
        value: values[_],
      });
    });
    const { duration } = values;
    if (duration) {
      result['range-time-picker'] = Form.createFormField({
        value: [duration.raw.start, duration.raw.end],
      });
    }
    return result;
  },
})
export default class Trace extends PureComponent {
  componentDidMount() {
    const { trace: { variables: { values } } } = this.props;
    const { duration } = values;
    this.props.dispatch({
      type: 'trace/initOptions',
      payload: { variables: { duration: duration.input } },
    });
    const condition = { ...values };
    condition.queryDuration = values.duration.input;
    delete condition.duration;
    this.fetchData(condition, initPaging);
  }
  getDefaultDuration = () => {
    return generateDuration({
      from() {
        return moment().subtract(15, 'minutes');
      },
      to() {
        return moment();
      },
    });
  }
  handleSearch = (e) => {
    if (e) {
      e.preventDefault();
    }
    const { form, dispatch } = this.props;

    form.validateFields((err, fieldsValue) => {
      if (err) return;
      const condition = { ...fieldsValue };
      delete condition['range-time-picker'];
      const rangeTime = fieldsValue['range-time-picker'];
      const duration = generateDuration({ from: () => rangeTime[0], to: () => rangeTime[1] });
      dispatch({
        type: 'trace/saveVariables',
        payload: {
          values: {
            ...condition,
            duration,
            paging: initPaging,
          },
        },
      });
      this.fetchData({ ...condition, queryDuration: duration.input });
    });
  }
  fetchData = (queryCondition, paging = initPaging) => {
    this.props.dispatch({
      type: 'trace/fetchData',
      payload: {
        variables: {
          condition: {
            ...queryCondition,
            paging,
          },
        },
      },
    });
  }
  handleTableChange = (pagination) => {
    const { dispatch, trace: { variables: { values } } } = this.props;
    const condition = {
      ...values,
      paging: {
        pageNum: pagination.current,
        pageSize: pagination.pageSize,
        needTotal: true,
      },
    };
    dispatch({
      type: 'trace/saveVariables',
      payload: {
        values: {
          ...condition,
        },
      },
    });
    delete condition.duration;
    this.fetchData({ ...condition, queryDuration: values.duration.input }, condition.paging);
  }
  handleShowTrace = (traceId) => {
    const { dispatch } = this.props;
    dispatch({
      type: 'trace/fetchSpans',
      payload: { variables: { traceId } },
    });
  }
  renderPointChart = (traces) => {
    if (!traces) {
      return null;
    }
    const ds = new DataSet();
    const dv = ds.createView().source(traces);
    dv.transform({
      type: 'map',
      callback(row) {
        return {
          ...row,
          state: row.isError ? 'error' : 'success',
          startTime: moment(parseInt(row.start, 10)).format('YYYY-MM-DD HH:mm:ss.SSS'),
        };
      },
    });
    return (
      <Chart
        data={dv}
        height={432}
        forceFit
        scale={{
          startTime: {
            tickCount: 4,
          },
        }}
      >
        <Tooltip
          showTitle={false}
          crosshairs={{ type: 'cross' }}
          itemTpl='
{name}
{value}
'
        />
        <Axis name="startTime" />
        <Axis
          name="duration"
          label={{
            formatter: (text) => {
              if (parseInt(text, 10) >= 1000) {
                return `${parseInt(text, 10) / 1000} s`;
              }
              return `${text} ms`;
            },
          }}
        />
        <Legend />
        <Geom
          type="point"
          position="startTime*duration"
          color={['state', state => (state === 'error' ? 'red' : '#1890ff')]}
          opacity={0.65}
          shape="circle"
          size={4}
          tooltip={['operationName*startTime*duration', (operationName, startTime, duration) => {
            return {
              name: operationName,
              value: `
                ${startTime}
                ${duration}ms
              `,
            };
          }]}
        />
      </Chart>);
  }
  renderForm() {
    const { getFieldDecorator } = this.props.form;
    const { trace: { variables: { options } } } = this.props;
    return (
      <Form onSubmit={this.handleSearch} layout="vertical">
        <FormItem label="Time Range">
          {getFieldDecorator('range-time-picker', {
            rules: [{
              required: true,
              message: 'Please select the correct date',
            }],
          })(
            <RangePicker
              showTime
              disabledDate={current => current && current.valueOf() >= Date.now()}
              format="YYYY-MM-DD HH:mm"
              style={{ width: '100%' }}
            />
          )}
        </FormItem>
        <FormItem label="Application">
          {getFieldDecorator('applicationId')(
            <Select placeholder="All application" style={{ width: '100%' }}>
              {options.applicationId && options.applicationId.map((app) => {
                  return (
                    <Option key={app.key ? app.key : -1} value={app.key}>
                      {app.label}
                    </Option>);
                })}
            </Select>
          )}
        </FormItem>
        <FormItem label="OperationName">
          {getFieldDecorator('operationName')(
            <Input placeholder="eg Kafka/Trace-topic-1/Consumer" />
          )}
        </FormItem>
        <FormItem label="TraceId">
          {getFieldDecorator('traceId')(
            <Input placeholder="eg 3.84.15204769998380001" />
          )}
        </FormItem>
        <Row>
          <Col xs={24} sm={24} md={24} lg={12} xl={12}>
            <FormItem label="Min Duration">
              {getFieldDecorator('minTraceDuration')(
                <InputNumber placeholder="eg 100" />
              )}
            </FormItem>
          </Col>
          <Col xs={24} sm={24} md={24} lg={12} xl={12}>
            <FormItem label="Max Duration">
              {getFieldDecorator('maxTraceDuration')(
                <InputNumber placeholder="eg 5000" />
              )}
            </FormItem>
          </Col>
        </Row>
        <FormItem>
          <Button type="primary" htmlType="submit">Search</Button>
        </FormItem>
      </Form>
    );
  }
  renderPage = (values, total) => {
    if (total < 1) {
      return null;
    }
    let currentPageNum = 1;
    let currentPageSize = 20;
    if (values.paging) {
      const { paging: { pageNum, pageSize } } = values;
      currentPageNum = pageNum;
      currentPageSize = pageSize;
    }
    return (
      <Row type="flex" justify="end">
        <Col>
          <Pagination
            size="small"
            current={currentPageNum}
            pageSize={currentPageSize}
            total={total}
            defaultPageSize={20}
            showSizeChanger
            pageSizeOptions={['20', '50', '100', '200']}
            onChange={(page, pageSize) => {
              this.handleTableChange({ current: page, pageSize });
            }}
            onShowSizeChange={(current, size) => {
              this.handleTableChange({ current: 1, pageSize: size });
            }}
          />
        </Col>
      </Row>);
  }
  render() {
    const { trace: { variables: { values }, data: { queryBasicTraces } }, loading } = this.props;
    return (
      <Card bordered={false}>

          <Row>
            <Col xs={24} sm={24} md={24} lg={8} xl={8}>
              {this.renderForm()}
            </Col>
            <Col xs={24} sm={24} md={24} lg={16} xl={16}>
              {this.renderPointChart(queryBasicTraces.traces)}
            </Col>
          </Row>
          {this.renderPage(values, queryBasicTraces.total)}
          <TraceList
            loading={loading}
            data={queryBasicTraces.traces}
            onClickTraceTag={this.handleShowTrace}
          />
          {this.renderPage(values, queryBasicTraces.total)}

      </Card>
    );
  }
}
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;
var pg = require('pg');
var Q = require("q");
var request = require("request");
var newUser = require("./db");
var logConversation = require("./conversation");
var nlp = require("./nlp");
var pushConv = require("./push");
var newBot_msg = require("./newBot");
//var findSession = require("./sessionsearch");

var sched = require("./sched");  //delete later

const scriptRules = require('./script.json');

var msgLog = {
    smoochId: '',
    received: '',
    usermessage: '',
    role: '',
    message_id: '',
    sourcetype: '',
    receivedtime: '',
    responsemessage: '',
    responsetype: '',
    responsetime: ''
  };

function wait(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

module.exports = new Script({
    processing: {
        //prompt: (bot) => bot.say('Beep boop...'),
        receive: () => 'processing'
    },

    start: {
        receive: (bot) => {
            return bot.say('Hello!  I\'m A16, the Agile2016 EventBot Concierge.  To find out what I can help you with type MENU for options or KEY for Keywords.')
                .then(() => 'speak');
        }
    },

//    speakers: {
//      prompt: (bot) => bot.say('Type part of the session name?'),
//      receive: (bot, message) => {
//        const name = message.text;
//        return bot.setProp('name', name)
//          .then(() => bot.say(`I will search for ${name} is that OK? %[Yes](postback:yes) %[No](postback:no)`))
//          .then(() => 'speak');
//      }
//    },

//    chris: {
//      receive: (bot, message) => {
//        return bot.getProp('name')
//          .then((name) => bot.say('That is all!'))
//          .then(() => 'speak');
//      }
//    },

    speak: {
        receive: (bot, message) => {
            console.log("===bot user ");
            let upperText = message.text.trim().toUpperCase();

//            if (upperText == 'SPEAKERS') {
//              return bot.getProp('name')
//              .then(() => 'speakers');
//            }

            msgLog.smoochId = bot.userId;
            msgLog.received = message.received;
            msgLog.usermessage = message.text;
            msgLog.role = message.role;
            msgLog.message_id = message._id;
            msgLog.sourcetype = message.source.type;
            msgLog.receivedtime = new Date();

            //sched.schedSessions('SessionList');

            //This is the control list of smoochId that can send broadcast messages
            var authUsers = ['a30fa820d0a0f0216fa26070'];

            newUser(bot)

            //For ad hoc messages - scheduled messages are done differently in checkItems
            if (authUsers.indexOf(bot.userId) !== -1) {
              if (upperText.substr(0,4) == '/SK ') {
                upperText = upperText.substr(0,3);
                newBot_msg(message.text.substr(4));
                console.log("****after push msg:  ",message.text," authUser:  ",authUsers);
              }
            }

            function updateSilent() {
                switch (upperText) {
                    case "CONNECT ME":
                        return bot.setProp("silent", true);
                    case "/SUPPORT":
                        return bot.setProp("silent", true);
                    case "DISCONNECT":
                        return bot.setProp("silent", false);
                    case "/A16":
                        console.log("*** ",upperText," ***");
                        processMessage(false);
                        return bot.setProp("silent", false);
                    default:
                        return Promise.resolve();
                }
            }

            function getSilent() {
                return bot.getProp("silent");
            }

            function processMessage(isSilent) {
                if (isSilent) {
                    return Promise.resolve("speak");
                }

                var promises = [];
                var source;
                var fulfillmentSpeech;
                var simplified;
                promises.push(nlp(upperText, bot.userId));

                Q.all(promises).then(function(responses) {
                    // response is the JSON from API.ai
                    responses.forEach(function(response) {
                        //console.log("===in Q.all");
                        console.log("===received result from API.ai",response);
                        source = response.result.source;
                        if (source && source !== 'agent')
                        {
                            fulfillmentSpeech = response.result.fulfillment.speech;
                            simplified = response.result.parameters.simplified;
                        }
//                        console.log("source: ", source);
//                        console.log("fulfillmentSpeech: ", fulfillmentSpeech);
//                        console.log("simplified: ", simplified);

                        respondMessage(source, fulfillmentSpeech, simplified);
                    });
                }, function(error) {
                    console.log("===Q all error ", error);
                });
            }

            function respondMessage(source, fulfillmentSpeech, simplified)
            {
//                console.log("source: ", source);
//                console.log("fulfillmentSpeech: ", fulfillmentSpeech);
//                console.log("simplified: ", simplified);
//                console.log("===receive step 3",upperText);

              if (source != 'agent')
                {
//                    console.log("===source is ", source);
                    if (fulfillmentSpeech)
                    {
                      switch (simplified) {
                        case "hello":
//                          console.log("===in hello");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        case "what do you know":
//                          console.log("===in what do you know");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        case "can you talk":
//                          console.log("===can you talk");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        default:
//                          console.log("===in switch default");
                          msgLog.responsemessage = fulfillmentSpeech;
                          msgLog.responsetime = new Date;
                          msgLog.responsetype = 'API.AI';
                          return bot.say(fulfillmentSpeech).then(() => 'speak');
                      }
                    }
                    else if (simplified)
                    {
//                        console.log("simplified is: ", simplified);
                        upperText = simplified.toUpperCase();
                    }
                }
                if (!_.has(scriptRules, upperText)) {
//                    console.log("===no rule", upperText);
                    return bot.say(`I'm sorry that is not something I know.  Type MENU or KEY for a list of things I can help you with.`).then(() => 'speak');
                }

                var response = scriptRules[upperText];
                var lines = response.split('\n');

                var p = Promise.resolve();
                _.each(lines, function(line) {
                    line = line.trim();
                    p = p.then(function() {
                        console.log("=== p line",line);
                        return wait(5).then(function() {
                            msgLog.responsemessage = line;
                            msgLog.responsetime = new Date;
                            msgLog.responsetype = 'JSON';
                            console.log("=== msgLog  obj",msgLog);
                            logConversation(msgLog);
                            return bot.say(line);
                        });
                    });
                });
                return p.then(() => 'speak');
            }

            return updateSilent()
                .then(getSilent)
                .then(processMessage);
        }
    }
});
<commit_msg>bur fix for msgLog if multiple response lines<commit_after>'use strict';

const _ = require('lodash');
const Script = require('smooch-bot').Script;
var pg = require('pg');
var Q = require("q");
var request = require("request");
var newUser = require("./db");
var logConversation = require("./conversation");
var nlp = require("./nlp");
var pushConv = require("./push");
var newBot_msg = require("./newBot");
//var findSession = require("./sessionsearch");

var sched = require("./sched");  //delete later

const scriptRules = require('./script.json');

var msgLog = {
    smoochId: '',
    received: '',
    usermessage: '',
    role: '',
    message_id: '',
    sourcetype: '',
    receivedtime: '',
    responsemessage: '',
    responsetype: '',
    responsetime: ''
  };

function wait(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

module.exports = new Script({
    processing: {
        //prompt: (bot) => bot.say('Beep boop...'),
        receive: () => 'processing'
    },

    start: {
        receive: (bot) => {
            return bot.say('Hello!  I\'m A16, the Agile2016 EventBot Concierge.  To find out what I can help you with type MENU for options or KEY for Keywords.')
                .then(() => 'speak');
        }
    },

//    speakers: {
//      prompt: (bot) => bot.say('Type part of the session name?'),
//      receive: (bot, message) => {
//        const name = message.text;
//        return bot.setProp('name', name)
//          .then(() => bot.say(`I will search for ${name} is that OK? %[Yes](postback:yes) %[No](postback:no)`))
//          .then(() => 'speak');
//      }
//    },

//    chris: {
//      receive: (bot, message) => {
//        return bot.getProp('name')
//          .then((name) => bot.say('That is all!'))
//          .then(() => 'speak');
//      }
//    },

    speak: {
        receive: (bot, message) => {
            console.log("===bot user ");
            let upperText = message.text.trim().toUpperCase();

//            if (upperText == 'SPEAKERS') {
//              return bot.getProp('name')
//              .then(() => 'speakers');
//            }

            msgLog.smoochId = bot.userId;
            msgLog.received = message.received;
            msgLog.usermessage = message.text;
            msgLog.role = message.role;
            msgLog.message_id = message._id;
            msgLog.sourcetype = message.source.type;
            msgLog.receivedtime = new Date();

            //sched.schedSessions('SessionList');

            //This is the control list of smoochId that can send broadcast messages
            var authUsers = ['a30fa820d0a0f0216fa26070'];

            newUser(bot)

            //For ad hoc messages - scheduled messages are done differently in checkItems
            if (authUsers.indexOf(bot.userId) !== -1) {
              if (upperText.substr(0,4) == '/SK ') {
                upperText = upperText.substr(0,3);
                newBot_msg(message.text.substr(4));
                console.log("****after push msg:  ",message.text," authUser:  ",authUsers);
              }
            }

            function updateSilent() {
                switch (upperText) {
                    case "CONNECT ME":
                        return bot.setProp("silent", true);
                    case "/SUPPORT":
                        return bot.setProp("silent", true);
                    case "DISCONNECT":
                        return bot.setProp("silent", false);
                    case "/A16":
                        console.log("*** ",upperText," ***");
                        processMessage(false);
                        return bot.setProp("silent", false);
                    default:
                        return Promise.resolve();
                }
            }

            function getSilent() {
                return bot.getProp("silent");
            }

            function processMessage(isSilent) {
                if (isSilent) {
                    return Promise.resolve("speak");
                }

                var promises = [];
                var source;
                var fulfillmentSpeech;
                var simplified;
                promises.push(nlp(upperText, bot.userId));

                Q.all(promises).then(function(responses) {
                    // response is the JSON from API.ai
                    responses.forEach(function(response) {
                        //console.log("===in Q.all");
                        console.log("===received result from API.ai",response);
                        source = response.result.source;
                        if (source && source !== 'agent')
                        {
                            fulfillmentSpeech = response.result.fulfillment.speech;
                            simplified = response.result.parameters.simplified;
                        }
//                        console.log("source: ", source);
//                        console.log("fulfillmentSpeech: ", fulfillmentSpeech);
//                        console.log("simplified: ", simplified);

                        respondMessage(source, fulfillmentSpeech, simplified);
                    });
                }, function(error) {
                    console.log("===Q all error ", error);
                });
            }

            function respondMessage(source, fulfillmentSpeech, simplified)
            {
//                console.log("source: ", source);
//                console.log("fulfillmentSpeech: ", fulfillmentSpeech);
//                console.log("simplified: ", simplified);
//                console.log("===receive step 3",upperText);

              if (source != 'agent')
                {
//                    console.log("===source is ", source);
                    if (fulfillmentSpeech)
                    {
                      switch (simplified) {
                        case "hello":
//                          console.log("===in hello");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        case "what do you know":
//                          console.log("===in what do you know");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        case "can you talk":
//                          console.log("===can you talk");
                          upperText = simplified.trim().toUpperCase();
                          break;
                        default:
//                          console.log("===in switch default");
                          msgLog.responsemessage = fulfillmentSpeech;
                          msgLog.responsetime = new Date;
                          msgLog.responsetype = 'API.AI';
                          return bot.say(fulfillmentSpeech).then(() => 'speak');
                      }
                    }
                    else if (simplified)
                    {
//                        console.log("simplified is: ", simplified);
                        upperText = simplified.toUpperCase();
                    }
                }
                if (!_.has(scriptRules, upperText)) {
//                    console.log("===no rule", upperText);
                    return bot.say(`I'm sorry that is not something I know.  Type MENU or KEY for a list of things I can help you with.`).then(() => 'speak');
                }

                var response = scriptRules[upperText];
                var lines = response.split('\n');

                var p = Promise.resolve();
                _.each(lines, function(line) {
                    line = line.trim();
                    p = p.then(function() {
                        console.log("=== p line",line);
                        return wait(5).then(function() {
                            msgLog.responsemessage = response;
                            msgLog.responsetime = new Date;
                            msgLog.responsetype = 'JSON';
                            console.log("=== msgLog  obj",msgLog);
                            logConversation(msgLog);
                            return bot.say(line);
                        });
                    });
                });
                return p.then(() => 'speak');
            }

            return updateSilent()
                .then(getSilent)
                .then(processMessage);
        }
    }
});
<|endoftext|>


<commit_before>/**
 * Custom event emitter plugin utilizing some special properties of the
 * callbacks function; specifically that we can bind events after the
 * event has already been triggered.
 */
(function($) {

    $.eventEmitter = function() {
        this._callbacks = {};

        this.on = function(name, callback) {
            if (!this._callbacks[name]) {
                this._callbacks[name] = $.Callbacks('once unique memory');
            }
            this._callbacks[name].add(callback);
        };

        this.off = function(name, callback) {
            if (!this._callbacks[name]) {
                return;
            }
            this._callbacks[name].remove(callback);
        };

        this.trigger = function(name) {
            if (!this._callbacks[name]) {
                this._callbacks[name] = $.Callbacks('once unique memory');
            }
            this._callbacks[name].fire(Array.prototype.slice.call(arguments, 1));
        };
    };

})(jQuery);
<commit_msg>Adding support for multiple events per on/off call.<commit_after>/**
 * Custom event emitter plugin utilizing some special properties of the
 * callbacks function; specifically that we can bind events after the
 * event has already been triggered.
 */
(function($) {

    $.eventEmitter = function() {
        this._callbacks = {};

        this.on = function(names, callback) {
            var names = names.split(' '),
                l = names.length,
                i;

            for (i = 0; i < l; i++) {
                if (!this._callbacks[names[i]]) {
                    this._callbacks[names[i]] = $.Callbacks('once unique memory');
                }
                this._callbacks[names[i]].add(callback);
            }
        };

        this.off = function(names, callback) {
            var names = names.split(' '),
                l = names.length,
                i;

            for (i = 0; i < l; i++) {
                if (this._callbacks[names[i]]) {
                    this._callbacks[names[i]].remove(callback);
                }
            }
        };

        this.trigger = function(name) {
            if (!this._callbacks[name]) {
                this._callbacks[name] = $.Callbacks('once unique memory');
            }
            this._callbacks[name].fire(Array.prototype.slice.call(arguments, 1));
        };
    };

})(jQuery);
<|endoftext|>


<commit_before>// Copyright 2020, University of Colorado Boulder

/**
 * Creates an object that has the same API as an Array, but also supports notifications and PhET-iO
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import arrayRemove from '../../phet-core/js/arrayRemove.js';
import assertMutuallyExclusiveOptions from '../../phet-core/js/assertMutuallyExclusiveOptions.js';
import merge from '../../phet-core/js/merge.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import IOType from '../../tandem/js/types/IOType.js';
import axon from './axon.js';
import Emitter from './Emitter.js';
import NumberProperty from './NumberProperty.js';
import ValidatorDef from './ValidatorDef.js';

/**
 * @param {Object} [options]
 * @returns {[]}
 */
const createArrayProxy = options => {

  assertMutuallyExclusiveOptions( options, [ 'length' ], [ 'elements' ] );

  // If the options supplied the phetioElementType, it is passed through as a phetioType to the Emitter parameter
  // const isPhetioElementTypeProvided = options && options.hasOwnProperty( 'phetioElementType' );

  options = merge( {

    // Also supports phetioType or validator options.  If both are supplied, only the phetioType is respected

    length: 0,
    elements: [],
    tandem: Tandem.OPTIONAL
  }, options );

  let emitterParameterOptions = null;
  if ( options.phetioType ) {

    assert && assert( options.phetioType.typeName.startsWith( 'ArrayProxyIO' ) );
    emitterParameterOptions = { name: 'value', phetioType: options.phetioType.parameterTypes[ 0 ] };
  }
  else if ( ValidatorDef.isValidValidator( options ) ) {
    const validator = _.pick( options, ValidatorDef.VALIDATOR_KEYS );
    emitterParameterOptions = merge( { name: 'value' }, validator );
  }
  else {
    emitterParameterOptions = merge( { name: 'value' }, { isValidValue: _.stubTrue } );
  }

  // notifies when an element has been added
  const elementAddedEmitter = new Emitter( {
    tandem: options.tandem.createTandem( 'elementAddedEmitter' ),
    parameters: [ emitterParameterOptions ]
  } );

  // notifies when an element has been removed
  const elementRemovedEmitter = new Emitter( {
    tandem: options.tandem.createTandem( 'elementRemovedEmitter' ),
    parameters: [ emitterParameterOptions ]
  } );

  // observe this, but don't set it. Updated when Array modifiers are called (except array.length=...)
  const lengthProperty = new NumberProperty( 0, {
    numberType: 'Integer',
    tandem: options.tandem.createTandem( 'lengthProperty' ),
    phetioReadOnly: true
  } );

  //REVIEW https://github.com/phetsims/axon/issues/330 document targetArray
  const targetArray = [];

  //REVIEW https://github.com/phetsims/axon/issues/330 would @param doc make get/set/deleteProperty easier to read? I don't understand it because I'm not familiar with Proxy.
  const arrayProxy = new Proxy( targetArray, {

    get: function( target, key, receiver ) {
      if ( methods.hasOwnProperty( key ) ) {
        return methods[ key ];
      }
      else {
        return Reflect.get( target, key, receiver );
      }
    },

    set: function( array, key, newValue ) {
      const oldValue = array[ key ];

      let removedElements = null;

      // See which items are removed
      if ( key === 'length' ) {
        removedElements = array.slice( newValue );
      }

      const returnValue = Reflect.set( array, key, newValue );

      //REVIEW https://github.com/phetsims/axon/issues/330 I have no idea what's going on here. Why is key an int, and why do we need to parse it?
      const parsed = parseInt( key, 10 );
      if ( !isNaN( parsed ) ) {
        if ( oldValue !== undefined ) {
          elementRemovedEmitter.emit( array[ key ] );
        }
        if ( newValue !== undefined ) {
          elementAddedEmitter.emit( newValue );
        }
      }
      else if ( key === 'length' ) {
        lengthProperty.value = newValue;

        assert && assert( removedElements, 'removedElements should be defined for key===length' );
        removedElements && removedElements.forEach( element => elementRemovedEmitter.emit( element ) );
      }
      return returnValue;
    },

    deleteProperty: function( array, key ) {
      //REVIEW https://github.com/phetsims/axon/issues/330 I have no idea what's going on here. Why is key an int, and why do we need to parse it?
      const parsed = parseInt( key, 10 );

      let removed;
      if ( !isNaN( parsed ) ) {
        removed = array[ key ];
      }
      const returnValue = Reflect.deleteProperty( array, key );
      if ( removed !== undefined ) {
        elementRemovedEmitter.emit( removed );
      }

      return returnValue;
    }
  } );

  // @private - Make it possible to use the targetArray in the overridden methods above.
  arrayProxy.targetArray = targetArray;

  // @public (listen only)
  arrayProxy.elementAddedEmitter = elementAddedEmitter;
  arrayProxy.elementRemovedEmitter = elementRemovedEmitter;
  arrayProxy.lengthProperty = lengthProperty;

  if ( options.length >= 0 ) {
    arrayProxy.length = options.length;
  }
  if ( options.elements.length > 0 ) {
    Array.prototype.push.apply( arrayProxy, options.elements );
  }

  //TODO https://github.com/phetsims/axon/issues/330 Move to "prototype" above or drop support
  //REVIEW https://github.com/phetsims/axon/issues/330 Add reset tests to createArrayProxyTests. Does this behave correctly if I'm listening to lengthProperty?
  arrayProxy.reset = () => {
    arrayProxy.length = 0;
    if ( options.length >= 0 ) {
      arrayProxy.length = options.length;
    }
    if ( options.elements.length > 0 ) {
      Array.prototype.push.apply( arrayProxy, options.elements );
    }
  };

  /******************************************
   * PhET-iO support
   *******************************************/
  if ( options.tandem.supplied ) {
    arrayProxy.phetioElementType = options.phetioType.parameterTypes[ 0 ];

    // @private - for managing state in phet-io
    // Use the same tandem and phetioState options so it can "masquerade" as the real object.  When PhetioObject is a mixin this can be changed.
    arrayProxy.arrayProxyPhetioObject = new ArrayProxyPhetioObject( arrayProxy, options );
  }

  return arrayProxy;
};

/**
 * Manages state save/load.  ArrayProxy uses Proxy and hence cannot be instrumented as a PhetioObject.  This type
 * provides that functionality.
 */
class ArrayProxyPhetioObject extends PhetioObject {

  /**
   * @param {ArrayProxyDef} arrayProxy
   * @param {Object} [options] - same as the options to the parent ArrayProxyDef
   */
  constructor( arrayProxy, options ) {

    options = merge( {
      phetioType: ArrayProxyIO
    }, options );

    super( options );

    // @private
    this.arrayProxy = arrayProxy;
  }
}

// Methods shared by all arrayProxy instances
const methods = {

  /******************************************
   * Overridden Array methods
   *******************************************/

  // @public
  pop() {
    const initialLength = this.targetArray.length;
    const returnValue = Array.prototype.pop.apply( this.targetArray, arguments );
    this.lengthProperty.value = this.length;
    initialLength > 0 && this.elementRemovedEmitter.emit( returnValue );
    return returnValue;
  },

  // @public
  shift() {
    const initialLength = this.targetArray.length;
    const returnValue = Array.prototype.shift.apply( this.targetArray, arguments );
    this.lengthProperty.value = this.length;
    initialLength > 0 && this.elementRemovedEmitter.emit( returnValue );
    return returnValue;
  },

  // @public
  push() {
    const returnValue = Array.prototype.push.apply( this.targetArray, arguments );
    this.lengthProperty.value = this.length;
    for ( let i = 0; i < arguments.length; i++ ) {
      this.elementAddedEmitter.emit( arguments[ i ] );
    }
    return returnValue;
  },

  // @public
  unshift() {
    const returnValue = Array.prototype.unshift.apply( this.targetArray, arguments );
    this.lengthProperty.value = this.length;
    for ( let i = 0; i < arguments.length; i++ ) {
      this.elementAddedEmitter.emit( arguments[ i ] );
    }
    return returnValue;
  },

  // @public
  splice() {
    const returnValue = Array.prototype.splice.apply( this.targetArray, arguments );

    // Set length first so it will be correct in elementListener callbacks
    this.lengthProperty.value = this.length;
    const deletedElements = returnValue;
    for ( let i = 2; i < arguments.length; i++ ) {
      this.elementAddedEmitter.emit( arguments[ i ] );
    }
    deletedElements.forEach( deletedElement => this.elementRemovedEmitter.emit( deletedElement ) );
    return returnValue;
  },

  // @public
  copyWithin() {
    const before = this.targetArray.slice();
    const returnValue = Array.prototype.copyWithin.apply( this.targetArray, arguments );
    reportDifference( before, this );
    return returnValue;
  },

  // @public
  fill() {
    const before = this.targetArray.slice();
    const returnValue = Array.prototype.fill.apply( this.targetArray, arguments );
    reportDifference( before, this );
    return returnValue;
  },

  /******************************************
   * For compatibility with ObservableArray
   * TODO https://github.com/phetsims/axon/issues/330 consider deleting after migration
   *******************************************/

  // @public
  get: function( index ) {return this[ index ];},

  // @public
  addItemAddedListener: function( listener ) { this.elementAddedEmitter.addListener( listener ); },

  // @public
  removeItemAddedListener: function( listener ) { this.elementAddedEmitter.removeListener( listener ); },

  // @public
  addItemRemovedListener: function( listener ) { this.elementRemovedEmitter.addListener( listener ); },

  // @public
  removeItemRemovedListener: function( listener ) { this.elementRemovedEmitter.removeListener( listener ); },

  // @public
  add: function( element ) { this.push( element );},

  // @public
  addAll: function( elements ) { this.push( ...elements );},

  // @public
  remove: function( element ) { this.includes( element ) && arrayRemove( this, element );},

  // @public
  removeAll: function( elements ) {
    elements.forEach( element => this.includes( element ) && arrayRemove( this, element ) );
  },

  // @public
  clear: function() {
    while ( this.length > 0 ) {
      this.pop();
    }
  },

  // @public
  count: function( predicate ) {
    let count = 0;
    for ( let i = 0; i < this.length; i++ ) {
      if ( predicate( this[ i ] ) ) {
        count++;
      }
    }
    return count;
  },

  // @public
  find: function( predicate, fromIndex ) {
    assert && ( fromIndex !== undefined ) && assert( typeof fromIndex === 'number', 'fromIndex must be numeric, if provided' );
    assert && ( typeof fromIndex === 'number' ) && assert( fromIndex >= 0 && fromIndex < this.length,
      `fromIndex out of bounds: ${fromIndex}` );
    return _.find( this, predicate, fromIndex );
  },

  // @public
  shuffle: function( random ) {
    assert && assert( random, 'random must be supplied' );

    // preserve the same _array reference in case any clients got a reference to it with getArray()
    const shuffled = random.shuffle( this );
    this.length = 0;
    Array.prototype.push.apply( this, shuffled );
  },

  // TODO https://github.com/phetsims/axon/issues/330 This seems important to eliminate
  // @public
  getArray: function() { return this; },

  //REVIEW https://github.com/phetsims/axon/issues/330 This also seems important to eliminate
  //REVIEW https://github.com/phetsims/axon/issues/330 do we need methods.slice?
  // @public
  getArrayCopy: function() { return this.slice(); },

  /******************************************
   * PhET-iO
   *******************************************/

  // @public
  toStateObject: function() {
    return { array: this.map( item => this.phetioElementType.toStateObject( item ) ) };
  },

  // @public
  applyState: function( stateObject ) {
    this.length = 0;
    const elements = stateObject.array.map( paramStateObject => this.phetioElementType.fromStateObject( paramStateObject ) );
    this.push( ...elements );
  },

  //REVIEW https://github.com/phetsims/axon/issues/330 dispose isn't specific to PhET-iO, move above the "PhET-iO" section comment?
  // @public
  dispose: function() {
    this.elementAddedEmitter.dispose();
    this.elementRemovedEmitter.dispose();
    this.lengthProperty.dispose();
    this.arrayProxyPhetioObject && this.arrayProxyPhetioObject.dispose();
  }
};

//REVIEW https://github.com/phetsims/axon/issues/330 I've read this comment multiple times and I'm still in the dark.
/**
 * Black box testing is less efficient but more concise and easy to verify correctness.  Used for the rarer methods.
 * @param {Object[]} shallowCopy
 * @param {ArrayProxyDef} arrayProxy
 */
const reportDifference = ( shallowCopy, arrayProxy ) => {

  const before = shallowCopy;
  const after = arrayProxy.targetArray.slice();

  for ( let i = 0; i < before.length; i++ ) {
    const beforeElement = before[ i ];
    const afterIndex = after.indexOf( beforeElement );
    if ( afterIndex >= 0 ) {
      before.splice( i, 1 );
      after.splice( afterIndex, 1 );
      i--;
    }
  }
  before.forEach( element => arrayProxy.elementRemovedEmitter.emit( element ) );
  after.forEach( element => arrayProxy.elementAddedEmitter.emit( element ) );
};

// {Map.<cacheKey:function(new:ArrayProxyIO), function(new:ArrayProxyIO)>} - Cache each parameterized ArrayProxyIO
// based on the parameter type, so that it is only created once.
const cache = new Map();

/**
 * ArrayProxyIO is the IO Type for ArrayProxyDef. It delegates most of its implementation to ArrayProxyDef.
 * Instead of being a parametric type, it leverages the phetioElementType on ArrayProxyDef.
 */
const ArrayProxyIO = parameterType => {
  if ( !cache.has( parameterType ) ) {
    cache.set( parameterType, new IOType( `ArrayProxyIO<${parameterType.typeName}>`, {
      parameterTypes: [ parameterType ],
      valueType: ArrayProxyPhetioObject,
      toStateObject: arrayProxyPhetioObject => arrayProxyPhetioObject.arrayProxy.toStateObject(),
      applyState: ( arrayProxyPhetioObject, state ) => arrayProxyPhetioObject.arrayProxy.applyState( state )
    } ) );
  }
  return cache.get( parameterType );
};

// @public
createArrayProxy.ArrayProxyIO = ArrayProxyIO;

axon.register( 'createArrayProxy', createArrayProxy );
export default createArrayProxy;<commit_msg>added REVIEW comments, https://github.com/phetsims/axon/issues/330<commit_after>// Copyright 2020, University of Colorado Boulder

/**
 * Creates an object that has the same API as an Array, but also supports notifications and PhET-iO
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */

import arrayRemove from '../../phet-core/js/arrayRemove.js';
import assertMutuallyExclusiveOptions from '../../phet-core/js/assertMutuallyExclusiveOptions.js';
import merge from '../../phet-core/js/merge.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import Tandem from '../../tandem/js/Tandem.js';
import IOType from '../../tandem/js/types/IOType.js';
import axon from './axon.js';
import Emitter from './Emitter.js';
import NumberProperty from './NumberProperty.js';
import ValidatorDef from './ValidatorDef.js';

/**
 * @param {Object} [options]
 * @returns {[]}
 */
const createArrayProxy = options => {

  assertMutuallyExclusiveOptions( options, [ 'length' ], [ 'elements' ] );

  //REVIEW https://github.com/phetsims/axon/issues/330 is phetioElementType a Core Type or an IO Type? Where is that validated?
  //REVIEW https://github.com/phetsims/axon/issues/330 is the last line of this comment dead code, or is it supposed to tell me something?
  // If the options supplied the phetioElementType, it is passed through as a phetioType to the Emitter parameter
  // const isPhetioElementTypeProvided = options && options.hasOwnProperty( 'phetioElementType' );

  options = merge( {

    // Also supports phetioType or validator options.  If both are supplied, only the phetioType is respected

    length: 0,
    elements: [],
    tandem: Tandem.OPTIONAL
  }, options );

  let emitterParameterOptions = null;
  if ( options.phetioType ) {

    assert && assert( options.phetioType.typeName.startsWith( 'ArrayProxyIO' ) );
    emitterParameterOptions = { name: 'value', phetioType: options.phetioType.parameterTypes[ 0 ] };
  }
  else if ( ValidatorDef.isValidValidator( options ) ) {
    const validator = _.pick( options, ValidatorDef.VALIDATOR_KEYS );
    emitterParameterOptions = merge( { name: 'value' }, validator );
  }
  else {
    emitterParameterOptions = merge( { name: 'value' }, { isValidValue: _.stubTrue } );
  }

  // notifies when an element has been added
  const elementAddedEmitter = new Emitter( {
    tandem: options.tandem.createTandem( 'elementAddedEmitter' ),
    parameters: [ emitterParameterOptions ]
  } );

  // notifies when an element has been removed
  const elementRemovedEmitter = new Emitter( {
    tandem: options.tandem.createTandem( 'elementRemovedEmitter' ),
    parameters: [ emitterParameterOptions ]
  } );

  // observe this, but don't set it. Updated when Array modifiers are called (except array.length=...)
  const lengthProperty = new NumberProperty( 0, {
    numberType: 'Integer',
    tandem: options.tandem.createTandem( 'lengthProperty' ),
    phetioReadOnly: true
  } );

  //REVIEW https://github.com/phetsims/axon/issues/330 document targetArray
  const targetArray = [];

  //REVIEW https://github.com/phetsims/axon/issues/330 would @param doc make get/set/deleteProperty easier to read? I don't understand it because I'm not familiar with Proxy.
  const arrayProxy = new Proxy( targetArray, {

    get: function( target, key, receiver ) {
      if ( methods.hasOwnProperty( key ) ) {
        return methods[ key ];
      }
      else {
        return Reflect.get( target, key, receiver );
      }
    },

    set: function( array, key, newValue ) {
      const oldValue = array[ key ];

      let removedElements = null;

      // See which items are removed
      if ( key === 'length' ) {
        removedElements = array.slice( newValue );
      }

      const returnValue = Reflect.set( array, key, newValue );

      //REVIEW https://github.com/phetsims/axon/issues/330 I have no idea what's going on here. Why is key an int, and why do we need to parse it?
      const parsed = parseInt( key, 10 );
      if ( !isNaN( parsed ) ) {
        if ( oldValue !== undefined ) {
          elementRemovedEmitter.emit( array[ key ] );
        }
        if ( newValue !== undefined ) {
          elementAddedEmitter.emit( newValue );
        }
      }
      else if ( key === 'length' ) {
        lengthProperty.value = newValue;

        assert && assert( removedElements, 'removedElements should be defined for key===length' );
        removedElements && removedElements.forEach( element => elementRemovedEmitter.emit( element ) );
      }
      return returnValue;
    },

    deleteProperty: function( array, key ) {
      //REVIEW https://github.com/phetsims/axon/issues/330 I have no idea what's going on here. Why is key an int, and why do we need to parse it?
      const parsed = parseInt( key, 10 );

      let removed;
      if ( !isNaN( parsed ) ) {
        removed = array[ key ];
      }
      const returnValue = Reflect.deleteProperty( array, key );
      if ( removed !== undefined ) {
        elementRemovedEmitter.emit( removed );
      }

      return returnValue;
    }
  } );

  // @private - Make it possible to use the targetArray in the overridden methods above.
  arrayProxy.targetArray = targetArray;

  // @public (listen only)
  arrayProxy.elementAddedEmitter = elementAddedEmitter;
  arrayProxy.elementRemovedEmitter = elementRemovedEmitter;
  arrayProxy.lengthProperty = lengthProperty;

  if ( options.length >= 0 ) {
    arrayProxy.length = options.length;
  }
  if ( options.elements.length > 0 ) {
    Array.prototype.push.apply( arrayProxy, options.elements );
  }

  //TODO https://github.com/phetsims/axon/issues/330 Move to "prototype" above or drop support
  //REVIEW https://github.com/phetsims/axon/issues/330 Add reset tests to createArrayProxyTests. Does this behave correctly if I'm listening to lengthProperty?
  arrayProxy.reset = () => {
    arrayProxy.length = 0;
    if ( options.length >= 0 ) {
      arrayProxy.length = options.length;
    }
    if ( options.elements.length > 0 ) {
      Array.prototype.push.apply( arrayProxy, options.elements );
    }
  };

  /******************************************
   * PhET-iO support
   *******************************************/
  if ( options.tandem.supplied ) {
    arrayProxy.phetioElementType = options.phetioType.parameterTypes[ 0 ];

    // @private - for managing state in phet-io
    // Use the same tandem and phetioState options so it can "masquerade" as the real object.  When PhetioObject is a mixin this can be changed.
    arrayProxy.arrayProxyPhetioObject = new ArrayProxyPhetioObject( arrayProxy, options );
  }

  return arrayProxy;
};

/**
 * Manages state save/load.  ArrayProxy uses Proxy and hence cannot be instrumented as a PhetioObject.  This type
 * provides that functionality.
 */
class ArrayProxyPhetioObject extends PhetioObject {

  /**
   * @param {ArrayProxyDef} arrayProxy
   * @param {Object} [options] - same as the options to the parent ArrayProxyDef
   */
  constructor( arrayProxy, options ) {

    options = merge( {
      phetioType: ArrayProxyIO
    }, options );

    super( options );

    // @private
    this.arrayProxy = arrayProxy;
  }
}

// Methods shared by all arrayProxy instances
const methods = {

  /******************************************
   * Overridden Array methods
   *******************************************/

  // @public
  pop() {
    const initialLength = this.targetArray.length;
    const returnValue = Array.prototype.pop.apply( this.targetArray, arguments );
    this.lengthProperty.value = this.length;
    initialLength > 0 && this.elementRemovedEmitter.emit( returnValue );
    return returnValue;
  },

  // @public
  shift() {
    const initialLength = this.targetArray.length;
    const returnValue = Array.prototype.shift.apply( this.targetArray, arguments );
    this.lengthProperty.value = this.length;
    initialLength > 0 && this.elementRemovedEmitter.emit( returnValue );
    return returnValue;
  },

  // @public
  push() {
    const returnValue = Array.prototype.push.apply( this.targetArray, arguments );
    this.lengthProperty.value = this.length;
    for ( let i = 0; i < arguments.length; i++ ) {
      this.elementAddedEmitter.emit( arguments[ i ] );
    }
    return returnValue;
  },

  // @public
  unshift() {
    const returnValue = Array.prototype.unshift.apply( this.targetArray, arguments );
    this.lengthProperty.value = this.length;
    for ( let i = 0; i < arguments.length; i++ ) {
      this.elementAddedEmitter.emit( arguments[ i ] );
    }
    return returnValue;
  },

  // @public
  splice() {
    const returnValue = Array.prototype.splice.apply( this.targetArray, arguments );

    // Set length first so it will be correct in elementListener callbacks
    this.lengthProperty.value = this.length;
    const deletedElements = returnValue;
    for ( let i = 2; i < arguments.length; i++ ) {
      this.elementAddedEmitter.emit( arguments[ i ] );
    }
    deletedElements.forEach( deletedElement => this.elementRemovedEmitter.emit( deletedElement ) );
    return returnValue;
  },

  // @public
  copyWithin() {
    const before = this.targetArray.slice();
    const returnValue = Array.prototype.copyWithin.apply( this.targetArray, arguments );
    reportDifference( before, this );
    return returnValue;
  },

  // @public
  fill() {
    const before = this.targetArray.slice();
    const returnValue = Array.prototype.fill.apply( this.targetArray, arguments );
    reportDifference( before, this );
    return returnValue;
  },

  /******************************************
   * For compatibility with ObservableArray
   * TODO https://github.com/phetsims/axon/issues/330 consider deleting after migration
   *******************************************/

  // @public
  get: function( index ) {return this[ index ];},

  // @public
  addItemAddedListener: function( listener ) { this.elementAddedEmitter.addListener( listener ); },

  // @public
  removeItemAddedListener: function( listener ) { this.elementAddedEmitter.removeListener( listener ); },

  // @public
  addItemRemovedListener: function( listener ) { this.elementRemovedEmitter.addListener( listener ); },

  // @public
  removeItemRemovedListener: function( listener ) { this.elementRemovedEmitter.removeListener( listener ); },

  // @public
  add: function( element ) { this.push( element );},

  // @public
  addAll: function( elements ) { this.push( ...elements );},

  // @public
  remove: function( element ) { this.includes( element ) && arrayRemove( this, element );},

  // @public
  removeAll: function( elements ) {
    elements.forEach( element => this.includes( element ) && arrayRemove( this, element ) );
  },

  // @public
  clear: function() {
    while ( this.length > 0 ) {
      this.pop();
    }
  },

  // @public
  count: function( predicate ) {
    let count = 0;
    for ( let i = 0; i < this.length; i++ ) {
      if ( predicate( this[ i ] ) ) {
        count++;
      }
    }
    return count;
  },

  // @public
  find: function( predicate, fromIndex ) {
    assert && ( fromIndex !== undefined ) && assert( typeof fromIndex === 'number', 'fromIndex must be numeric, if provided' );
    assert && ( typeof fromIndex === 'number' ) && assert( fromIndex >= 0 && fromIndex < this.length,
      `fromIndex out of bounds: ${fromIndex}` );
    return _.find( this, predicate, fromIndex );
  },

  // @public
  shuffle: function( random ) {
    assert && assert( random, 'random must be supplied' );

    // preserve the same _array reference in case any clients got a reference to it with getArray()
    const shuffled = random.shuffle( this );
    this.length = 0;
    Array.prototype.push.apply( this, shuffled );
  },

  // TODO https://github.com/phetsims/axon/issues/330 This seems important to eliminate
  // @public
  getArray: function() { return this; },

  //REVIEW https://github.com/phetsims/axon/issues/330 This also seems important to eliminate
  //REVIEW https://github.com/phetsims/axon/issues/330 do we need methods.slice?
  // @public
  getArrayCopy: function() { return this.slice(); },

  /******************************************
   * PhET-iO
   *******************************************/

  // @public
  toStateObject: function() {
    return { array: this.map( item => this.phetioElementType.toStateObject( item ) ) };
  },

  // @public
  applyState: function( stateObject ) {
    this.length = 0;
    const elements = stateObject.array.map( paramStateObject => this.phetioElementType.fromStateObject( paramStateObject ) );
    this.push( ...elements );
  },

  //REVIEW https://github.com/phetsims/axon/issues/330 dispose isn't specific to PhET-iO, move above the "PhET-iO" section comment?
  // @public
  dispose: function() {
    this.elementAddedEmitter.dispose();
    this.elementRemovedEmitter.dispose();
    this.lengthProperty.dispose();
    this.arrayProxyPhetioObject && this.arrayProxyPhetioObject.dispose();
  }
};

//REVIEW https://github.com/phetsims/axon/issues/330 I've read this comment multiple times and I'm still in the dark.
/**
 * Black box testing is less efficient but more concise and easy to verify correctness.  Used for the rarer methods.
 * @param {Object[]} shallowCopy
 * @param {ArrayProxyDef} arrayProxy
 */
const reportDifference = ( shallowCopy, arrayProxy ) => {

  const before = shallowCopy;
  const after = arrayProxy.targetArray.slice();

  for ( let i = 0; i < before.length; i++ ) {
    const beforeElement = before[ i ];
    const afterIndex = after.indexOf( beforeElement );
    if ( afterIndex >= 0 ) {
      before.splice( i, 1 );
      after.splice( afterIndex, 1 );
      i--;
    }
  }
  before.forEach( element => arrayProxy.elementRemovedEmitter.emit( element ) );
  after.forEach( element => arrayProxy.elementAddedEmitter.emit( element ) );
};

// {Map.<cacheKey:function(new:ArrayProxyIO), function(new:ArrayProxyIO)>} - Cache each parameterized ArrayProxyIO
// based on the parameter type, so that it is only created once.
const cache = new Map();

/**
 * ArrayProxyIO is the IO Type for ArrayProxyDef. It delegates most of its implementation to ArrayProxyDef.
 * Instead of being a parametric type, it leverages the phetioElementType on ArrayProxyDef.
 */
const ArrayProxyIO = parameterType => {
  if ( !cache.has( parameterType ) ) {
    cache.set( parameterType, new IOType( `ArrayProxyIO<${parameterType.typeName}>`, {
      parameterTypes: [ parameterType ],
      valueType: ArrayProxyPhetioObject,
      toStateObject: arrayProxyPhetioObject => arrayProxyPhetioObject.arrayProxy.toStateObject(),
      applyState: ( arrayProxyPhetioObject, state ) => arrayProxyPhetioObject.arrayProxy.applyState( state )
    } ) );
  }
  return cache.get( parameterType );
};

// @public
createArrayProxy.ArrayProxyIO = ArrayProxyIO;

axon.register( 'createArrayProxy', createArrayProxy );
export default createArrayProxy;<|endoftext|>


<commit_before>$(document).ready (function menu_handler_js () {
    "use strict";

    var mode ='L',
        ulHeight = parseInt( $('ul.navigation').css('height') );

    /**
     * performs the menu show-hide action
     */
    function menuToggle() {
        $(".menu_items").toggleClass("hidden")
            .toggleClass("animation");
    }

    /**
     * binds the menu show-hide action
     */
    function menuActionsS () {
        if (mode == 'S') {
            $(".menu_button_img").on("click", menuToggle);
        }
    }

    /**
     * Sets the mode by Viewsize
     * returns string 'L' or 'S'
     * 'S' = mobile view
     * given in var mode
     */
    function setMode () {
        /**
         * unsets the classes for mobile view ("S view")
         * when resizing from small to normal view
         */
        function runL () {
            /** style class changes **/
            $(".js-L-view").removeClass("hidden");
            $(".js-S-view").not("hidden").addClass("hidden");
            $(".logging").removeClass("box")
                .not("log_me_out").addClass("log_me_out");
            $('.navigation .linkPic').removeClass("hidden");
        }

        /**
         * sets the classes for mobile view ("S view")
         */
        function runS () {
            /** style class changes **/
            $(".js-S-view").removeClass("hidden");
            $(".js-L-view").not("hidden").addClass("hidden"); //resets the menu to closed state
            $(".logging").removeClass("log_me_out")
                .not("box").addClass("box");
            $(".navigation .linkPic").not("hidden").addClass("hidden");
        }

        /** removes click event to avoid multiple bindings **/
        $(".menu_button_img").off("click",  menuToggle);

        if(window.matchMedia('(max-width: 700px)').matches) {
            mode = "S";
            runS();
        } else {
            mode ="L";
            runL();
        }
    }

    function countItems() {
        var selector,
            ItemCount = $('.navigation li.level_0').length,
            zIndex = $('ul.navigation ul').css('z-index'),
            bodyWidth = parseInt( $('body').css('width')),
            ulWidth = parseInt( $('ul.navigation').css('width') ),
            liWidth = parseInt( $('ul.navigation li').css('width') ),
            difference = bodyWidth-ulWidth,
            ele = $('.navigation li');
        selector = '(max-width: ' + ( ( ItemCount*liWidth ) + difference ) + 'px)';

        function getProperty() {
            var $ul = $("

").hide().appendTo("body");
            ulHeight = parseInt( $ul.css("height") );
            $ul.remove();
        }
        function up (){
            $('*', this).css('z-index', 12);
        }
        function down(){
            $('*', this).css('z-index', zIndex);
        }
        function remove(){
            $("ul.navigation").removeAttr("style");
            $("ul.navigation li").removeAttr("style");
            $(ele).off("mouseover", up);
            $(ele).off("mouseout", down);
        }

        if (mode == 'S'){
            getProperty();
        }
        if (window.matchMedia('(min-width: 700px)').matches) {
            if (window.matchMedia(selector).matches) {
                $('ul.navigation').css('height', 'calc('+(2*ulHeight)+'px + 0.5vw)')
                    .css('height', 'calc('+(2*ulHeight)+'px + 0.5vw)');
                $(ele).on("mouseover", up);
                $(ele).on("mouseout", down);
            }
            else {
                remove();
            }
        }
        else {
            remove();
        }
        console.log(ulHeight);
    }
/* ------------------ WORKING SCRIPT -------------------- */
    setMode ();
    menuActionsS();
    countItems();

    $(window).resize ( function () {
        setMode ();
        menuActionsS();
        countItems();
    });
})<commit_msg>menu.js<commit_after>$(document).ready (function menu_handler_js () {
    "use strict";

    var mode ='L',
        ulHeight = parseInt( $('ul.navigation').css('height') );

    /**
     * performs the menu show-hide action
     */
    function menuToggle() {
        $(".menu_items").toggleClass("hidden")
            .toggleClass("animation");
    }

    /**
     * Sets the mode by Viewsize
     * returns string 'L' or 'S'
     * 'S' = mobile view
     * given in var mode
     */
    function setMode () {
        /**
         * unsets the classes for mobile view ("S view")
         * when resizing from small to normal view
         */
        function runL () {
            /** style class changes **/
            $(".js-L-view").removeClass("hidden");
            $(".js-S-view").not("hidden").addClass("hidden");
            $(".logging").removeClass("box")
                .not("log_me_out").addClass("log_me_out");
            $('.navigation .linkPic').removeClass("hidden");
        }

        /**
         * sets the classes for mobile view ("S view")
         */
        function runS () {
            /** style class changes **/
            $(".js-S-view").removeClass("hidden");
            $(".js-L-view").not("hidden").addClass("hidden"); //resets the menu to closed state
            $(".logging").removeClass("log_me_out")
                .not("box").addClass("box");
            $(".navigation .linkPic").not("hidden").addClass("hidden");
            $(".menu_items").removeClass("animation");
        }

        /** removes click event to avoid multiple bindings **/
        $(".menu_button_img").off("click",  menuToggle);

        if(window.matchMedia('(max-width: 700px)').matches) {
            mode = "S";
            runS();
        } else {
            mode ="L";
            runL();
        }
    }

    /**
     * binds the menu show-hide action
     */
    function menuActionsS () {
        if (mode == 'S') {
            $(".menu_button_img").on("click", menuToggle);
        }
    }

    function countItems() {
        var selector,
            ItemCount = $('.navigation li.level_0').length,
            zIndex = $('ul.navigation ul').css('z-index'),
            bodyWidth = parseInt( $('body').css('width')),
            ulWidth = parseInt( $('ul.navigation').css('width') ),
            liWidth = parseInt( $('ul.navigation li').css('width') ),
            difference = bodyWidth-ulWidth,
            ele = $('.navigation li');
        selector = '(max-width: ' + ( ( ItemCount*liWidth ) + difference ) + 'px)';

        function getProperty() {
            var $ul = $("

").hide().appendTo("body");
            ulHeight = parseInt( $ul.css("height") );
            $ul.remove();
        }
        function up (){
            $('*', this).css('z-index', 12);
        }
        function down(){
            $('*', this).css('z-index', zIndex);
        }
        function remove(){
            $("ul.navigation").removeAttr("style");
            $("ul.navigation li").removeAttr("style");
            $(ele).off("mouseover", up);
            $(ele).off("mouseout", down);
        }

        if (mode == 'S'){
            getProperty();
        }
        if (window.matchMedia('(min-width: 700px)').matches) {
            if (window.matchMedia(selector).matches) {
                $('ul.navigation').css('height', 'calc('+(2*ulHeight)+'px + 0.5vw)')
                    .css('height', 'calc('+(2*ulHeight)+'px + 0.5vw)');
                $(ele).on("mouseover", up);
                $(ele).on("mouseout", down);
            }
            else {
                remove();
            }
        }
        else {
            remove();
        }
        console.log(ulHeight);
    }
/* ------------------ WORKING SCRIPT -------------------- */
    setMode ();
    menuActionsS();
    countItems();

    $(window).resize ( function () {
        setMode ();
        menuActionsS();
        countItems();
    });
})<|endoftext|>


<commit_before>/**
 * Kaltura style analytics reporting class
 */
( function( mw, $ ) { "use strict";

// Avoid undefined symbol for javascript response "Kaltura" from the api
window['Kaltura'] = true;

// KAnalytics Constructor
mw.KAnalytics = function( embedPlayer ){
	this.init( embedPlayer );
};

// Add analytics to the embed player:
mw.addKAnalytics = function( embedPlayer ) {
	embedPlayer.kAnalytics = new mw.KAnalytics( embedPlayer );
}
mw.KAnalytics.prototype = {

	// Local reference to embedPlayer
	embedPlayer: null,

	// Report Set object
	reportSet : null,

	// Stores the last time we issued a seek event
	// avoids sending lots of seeks while scrubbing
	lastSeekEventTime: 0,

	bindPostFix: '.kAnalytics',

	// Start Time
	startReportTime: 0,

	kEventTypes : {
		'WIDGET_LOADED' : 1,
		'MEDIA_LOADED' : 2,
		'PLAY' : 3,
		'PLAY_REACHED_25' : 4,
		'PLAY_REACHED_50' : 5,
		'PLAY_REACHED_75' : 6,
		'PLAY_REACHED_100' : 7,
		'OPEN_EDIT' : 8,
		'OPEN_VIRAL' : 9,
		'OPEN_DOWNLOAD' : 10,
		'OPEN_REPORT' : 11,
		'BUFFER_START' : 12,
		'BUFFER_END' : 13,
		'OPEN_FULL_SCREEN' : 14,
		'CLOSE_FULL_SCREEN' : 15,
		'REPLAY' : 16,
		'SEEK' : 17,
		'OPEN_UPLOAD' : 18,
		'SAVE_PUBLISH' : 19,
		'CLOSE_EDITOR' : 20,
		'PRE_BUMPER_PLAYED' : 21,
		'POST_BUMPER_PLAYED' : 22,
		'BUMPER_CLICKED' : 23,
		'FUTURE_USE_1' : 24,
		'FUTURE_USE_2' : 25,
		'FUTURE_USE_3' : 26
	},

	/**
	 * Constructor for kAnalytics
	 *
	 * @param {Object}
	 *		  embedPlayer Player to apply Kaltura analytics to.
	 * @parma {Object}
	 * 			kalturaClient Kaltura client object for the api session.
	 */
	init: function( embedPlayer ) {
		// set the version of html5 player
		this.version = mw.getConfig( 'version' );
		// Setup the local reference to the embed player
		this.embedPlayer = embedPlayer;
		if( ! this.kClient ) {
			this.kClient = mw.kApiGetPartnerClient( embedPlayer.kwidgetid );
		}
		// Remove any old bindings:
		$( embedPlayer ).unbind( this.bindPostFix );

		// Setup the initial state of some flags
		this.resetPlayerflags();

		// Add relevant hooks for reporting beacons
		this.bindPlayerEvents();
	},
	resetPlayerflags:function(){
		this._p25Once = false;
		this._p50Once = false;
		this._p75Once = false;
		this._p100Once = false;
		this.hasSeeked = false;
		this.lastSeek = 0;
	},
	/**
	 * Get the current report set
	 *
	 * @param {Number}
	 *			KalturaStatsEventType The eventType number.
	 */
	sendAnalyticsEvent: function( KalturaStatsEventKey ){
		var _this = this;
		mw.log("KAnalytics :: doSendAnalyticsEvent > " + KalturaStatsEventKey );
		// Kalutra analytics does not collect info for ads:
		if( this.embedPlayer.evaluate('{sequenceProxy.isInSequence}') ){
			return ;
		}

		// get the id for the given event:
		var eventKeyId = this.kEventTypes[ KalturaStatsEventKey ];

		// Generate the status event
		var eventSet = {
			'eventType'			: eventKeyId,
			'clientVer'			: this.version,
			'currentPoint'		: parseInt( this.embedPlayer.currentTime * 1000 ),
			'duration'			: this.embedPlayer.getDuration(),
			'eventTimestamp'	: new Date().getTime(),
			'isFirstInSession'	: 'false',
			'objectType'		: 'KalturaStatsEvent',
			'partnerId'			: this.embedPlayer.kpartnerid,
			'sessionId'			: this.embedPlayer.evaluate('{configProxy.sessionId}'),
			'uiconfId'			: 0
		};
		if( isNaN( eventSet.duration )  ){
			eventSet.duration = 0;
		}

		// Set the seek condition:
		eventSet[ 'seek' ] = ( this.hasSeeked ) ? 'true' : 'false';

		// Set the 'event:entryId'
		if( this.embedPlayer.kentryid ){
			eventSet[ 'entryId' ] = this.embedPlayer.kentryid;
		} else {
			// if kentryid is not set, use the selected source url
			eventSet[ 'entryId' ] = this.embedPlayer.getSrc();
		}

		// Set the 'event:uiconfId'
		if( this.embedPlayer.kuiconfid ) {
			eventSet[ 'uiconfId' ] = this.embedPlayer.kuiconfid;
		}
		// Set the 'event:widgetId'
		if( this.embedPlayer.kwidgetid ) {
			eventSet[ 'widgetId' ] = this.embedPlayer.kwidgetid;
		}
		var flashVarEvents = {
				'playbackContext' : 'contextId',
				'originFeature' : 'featureType',
				'applicationName' : 'applicationId',
				'userId' : 'userId'
		}
		// support legacy ( deprecated ) top level config
		for( var fvKey in flashVarEvents){
			if( this.embedPlayer.getKalturaConfig( '', fvKey ) ){
				eventSet[ flashVarEvents[ fvKey ] ] = encodeURIComponent( this.embedPlayer.getKalturaConfig('', fvKey ) );
			}
		}
		// check for the vars in the correct location:
		for( var fvKey in flashVarEvents){
			if( this.embedPlayer.getKalturaConfig( 'statistics', fvKey ) ){
				eventSet[ flashVarEvents[ fvKey ] ] = encodeURIComponent( this.embedPlayer.getKalturaConfig('', fvKey ) );
			}
		}

		// Add referrer parameter
		eventSet[ 'referrer' ] = encodeURIComponent( mw.getConfig('EmbedPlayer.IframeParentUrl') );

		// Add in base service and action calls:
		var eventRequest = {'service' : 'stats', 'action' : 'collect'};
		// Add event parameters
		for( var i in eventSet){
			eventRequest[ 'event:' + i] = eventSet[i];
		}

		// Send events for this player:
		$( this.embedPlayer ).trigger( 'KalturaSendAnalyticEvent', [ KalturaStatsEventKey, eventSet ] );

		// check for defined callback:
		var parentTrackName = this.embedPlayer.getKalturaConfig( 'statistics', 'trackEventMonitor');
		if(  mw.getConfig('EmbedPlayer.IsFriendlyIframe') ){
			try {
				if( window.parent[ parentTrackName ] ){
					 window.parent[ parentTrackName ]( KalturaStatsEventKey, eventSet );
				}
			} catch( e ){
				// error in calling parent page event
			}
		}
		if (this.embedPlayer.getFlashvars('ks')){
			eventRequest['ks'] = this.embedPlayer.getFlashvars('ks');
		}

		// Do the api request:
		this.kClient.doRequest( eventRequest, null, true );
	},

	/**
	 * Binds player events for analytics reporting
	 */
	bindPlayerEvents: function(){

		// Setup local reference to embedPlayer
		var embedPlayer = this.embedPlayer;
		var _this = this;

		// Setup shortcut anonymous function for player bindings
		var b = function( hookName, eventType ){
			$( _this.embedPlayer ).bind( hookName + _this.bindPostFix, function(){
				_this.sendAnalyticsEvent( eventType );
			});
		};

		// When the player is ready
		b( 'widgetLoaded', 'WIDGET_LOADED' );

		// When the poster or video ( when autoplay ) media is loaded
		b( 'KalturaSupport_EntryDataReady', 'MEDIA_LOADED' );

		// When the play button is pressed or called from javascript
		b( 'firstPlay', 'PLAY' );

		// When the show Share menu is displayed
		b( 'showShareEvent', 'OPEN_VIRAL' );

		// When the show download menu is displayed
		b( 'downloadMedia', 'OPEN_DOWNLOAD' );

		// When the clip starts to buffer ( not all player types )
		b( 'bufferStartEvent', 'BUFFER_START' );

		// When the clip is full buffered
		b( 'bufferEndEvent', 'BUFFER_END' );

		// When the fullscreen button is pressed
		// ( presently does not register iPhone / iPad until it has js bindings )
		b( 'onOpenFullScreen', 'OPEN_FULL_SCREEN' );

		// When the close fullscreen button is pressed.
		// ( presently does not register iphone / ipad until it has js bindings )
		b( 'onCloseFullScreen', 'CLOSE_FULL_SCREEN' );

		// When the user plays (after the ondone event was fired )
		b( 'replayEvent', 'REPLAY' );

		// Bind on the seek event
		$( embedPlayer ).bind( 'seeked' + this.bindPostFix, function( seekTarget ) {
			// Don't send a bunch of seeks on scrub:
			if( _this.lastSeekEventTime == 0 ||
				_this.lastSeekEventTime + 2000	< new Date().getTime() )
			{
				_this.sendAnalyticsEvent( 'SEEK' );
			}

			// Update the last seekTime
			_this.lastSeekEventTime =  new Date().getTime();

			// Then set local seek flags
			this.hasSeeked = true;
			this.lastSeek = seekTarget;
		} );

		// Let updateTimeStats handle the currentTime monitor timing

		$( embedPlayer ).bind( 'monitorEvent' + this.bindPostFix, function(){
			_this.updateTimeStats();
		});

		/*
		 * Other kaltura event types that are presently not usable in the
		 * html5 player at this point in time:
		 *
		 * OPEN_EDIT = 8;
		 * OPEN_REPORT = 11;
		 * OPEN_UPLOAD = 18;
		 * SAVE_PUBLISH = 19;
		 * CLOSE_EDITOR = 20;
		 *
		 * PRE_BUMPER_PLAYED = 21;
		 * POST_BUMPER_PLAYED = 22;
		 * BUMPER_CLICKED = 23;
		 *
		 * FUTURE_USE_1 = 24;
		 * FUTURE_USE_2 = 25;
		 * FUTURE_USE_3 = 26;
		 */
	},

	/**
	 * Send updates for time stats
	 */
	updateTimeStats: function() {
		// Setup local references:
		var embedPlayer = this.embedPlayer;
		var _this = this;

		// Set the seek and time percent:
		var percent = embedPlayer.currentTime / embedPlayer.duration;
		var seekPercent = this.lastSeek/ embedPlayer.duration;

		// Send updates based on logic present in StatisticsMediator.as
		if ( !embedPlayer.isLive() ){
			if( !_this._p25Once && percent >= .25  &&  seekPercent <= .25 ) {

				_this._p25Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_25' );
				$( embedPlayer ).trigger( "firstQuartile" );

			} else if ( !_this._p50Once && percent >= .50 && seekPercent < .50 ) {

				_this._p50Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_50' );
				$( embedPlayer ).trigger( "secondQuartile" );

			} else if( !_this._p75Once && percent >= .75 && seekPercent < .75 ) {

				_this._p75Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_75' );
				$( embedPlayer ).trigger( "thirdQuartile" );

			} else if(  !_this._p100Once && percent >= .98 && seekPercent < 1) {

				_this._p100Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_100' );
			}
		}
	}
};

})( window.mw, window.jQuery );<commit_msg>#SUP-4621 #comment fix KAnalytics statistics undefined #time 2h<commit_after>/**
 * Kaltura style analytics reporting class
 */
( function( mw, $ ) { "use strict";

// Avoid undefined symbol for javascript response "Kaltura" from the api
window['Kaltura'] = true;

// KAnalytics Constructor
mw.KAnalytics = function( embedPlayer ){
	this.init( embedPlayer );
};

// Add analytics to the embed player:
mw.addKAnalytics = function( embedPlayer ) {
	embedPlayer.kAnalytics = new mw.KAnalytics( embedPlayer );
}
mw.KAnalytics.prototype = {

	// Local reference to embedPlayer
	embedPlayer: null,

	// Report Set object
	reportSet : null,

	// Stores the last time we issued a seek event
	// avoids sending lots of seeks while scrubbing
	lastSeekEventTime: 0,

	bindPostFix: '.kAnalytics',

	// Start Time
	startReportTime: 0,

	kEventTypes : {
		'WIDGET_LOADED' : 1,
		'MEDIA_LOADED' : 2,
		'PLAY' : 3,
		'PLAY_REACHED_25' : 4,
		'PLAY_REACHED_50' : 5,
		'PLAY_REACHED_75' : 6,
		'PLAY_REACHED_100' : 7,
		'OPEN_EDIT' : 8,
		'OPEN_VIRAL' : 9,
		'OPEN_DOWNLOAD' : 10,
		'OPEN_REPORT' : 11,
		'BUFFER_START' : 12,
		'BUFFER_END' : 13,
		'OPEN_FULL_SCREEN' : 14,
		'CLOSE_FULL_SCREEN' : 15,
		'REPLAY' : 16,
		'SEEK' : 17,
		'OPEN_UPLOAD' : 18,
		'SAVE_PUBLISH' : 19,
		'CLOSE_EDITOR' : 20,
		'PRE_BUMPER_PLAYED' : 21,
		'POST_BUMPER_PLAYED' : 22,
		'BUMPER_CLICKED' : 23,
		'FUTURE_USE_1' : 24,
		'FUTURE_USE_2' : 25,
		'FUTURE_USE_3' : 26
	},

	/**
	 * Constructor for kAnalytics
	 *
	 * @param {Object}
	 *		  embedPlayer Player to apply Kaltura analytics to.
	 * @parma {Object}
	 * 			kalturaClient Kaltura client object for the api session.
	 */
	init: function( embedPlayer ) {
		// set the version of html5 player
		this.version = mw.getConfig( 'version' );
		// Setup the local reference to the embed player
		this.embedPlayer = embedPlayer;
		if( ! this.kClient ) {
			this.kClient = mw.kApiGetPartnerClient( embedPlayer.kwidgetid );
		}
		// Remove any old bindings:
		$( embedPlayer ).unbind( this.bindPostFix );

		// Setup the initial state of some flags
		this.resetPlayerflags();

		// Add relevant hooks for reporting beacons
		this.bindPlayerEvents();
	},
	resetPlayerflags:function(){
		this._p25Once = false;
		this._p50Once = false;
		this._p75Once = false;
		this._p100Once = false;
		this.hasSeeked = false;
		this.lastSeek = 0;
	},
	/**
	 * Get the current report set
	 *
	 * @param {Number}
	 *			KalturaStatsEventType The eventType number.
	 */
	sendAnalyticsEvent: function( KalturaStatsEventKey ){
		var _this = this;
		mw.log("KAnalytics :: doSendAnalyticsEvent > " + KalturaStatsEventKey );
		// Kalutra analytics does not collect info for ads:
		if( this.embedPlayer.evaluate('{sequenceProxy.isInSequence}') ){
			return ;
		}

		// get the id for the given event:
		var eventKeyId = this.kEventTypes[ KalturaStatsEventKey ];

		// Generate the status event
		var eventSet = {
			'eventType'			: eventKeyId,
			'clientVer'			: this.version,
			'currentPoint'		: parseInt( this.embedPlayer.currentTime * 1000 ),
			'duration'			: this.embedPlayer.getDuration(),
			'eventTimestamp'	: new Date().getTime(),
			'isFirstInSession'	: 'false',
			'objectType'		: 'KalturaStatsEvent',
			'partnerId'			: this.embedPlayer.kpartnerid,
			'sessionId'			: this.embedPlayer.evaluate('{configProxy.sessionId}'),
			'uiconfId'			: 0
		};
		if( isNaN( eventSet.duration )  ){
			eventSet.duration = 0;
		}

		// Set the seek condition:
		eventSet[ 'seek' ] = ( this.hasSeeked ) ? 'true' : 'false';

		// Set the 'event:entryId'
		if( this.embedPlayer.kentryid ){
			eventSet[ 'entryId' ] = this.embedPlayer.kentryid;
		} else {
			// if kentryid is not set, use the selected source url
			eventSet[ 'entryId' ] = this.embedPlayer.getSrc();
		}

		// Set the 'event:uiconfId'
		if( this.embedPlayer.kuiconfid ) {
			eventSet[ 'uiconfId' ] = this.embedPlayer.kuiconfid;
		}
		// Set the 'event:widgetId'
		if( this.embedPlayer.kwidgetid ) {
			eventSet[ 'widgetId' ] = this.embedPlayer.kwidgetid;
		}
		var flashVarEvents = {
				'playbackContext' : 'contextId',
				'originFeature' : 'featureType',
				'applicationName' : 'applicationId',
				'userId' : 'userId'
		}
		// support legacy ( deprecated ) top level config
		for( var fvKey in flashVarEvents){
			if( this.embedPlayer.getKalturaConfig( '', fvKey ) ){
				eventSet[ flashVarEvents[ fvKey ] ] = encodeURIComponent( this.embedPlayer.getKalturaConfig('', fvKey ) );
			}
		}
		// check for the vars in the correct location:
		for( var fvKey in flashVarEvents){
			if( this.embedPlayer.getKalturaConfig( 'statistics', fvKey ) ){
				eventSet[ flashVarEvents[ fvKey ] ] = encodeURIComponent( this.embedPlayer.getKalturaConfig('statistics', fvKey ) );
			}
		}

		// Add referrer parameter
		eventSet[ 'referrer' ] = encodeURIComponent( mw.getConfig('EmbedPlayer.IframeParentUrl') );

		// Add in base service and action calls:
		var eventRequest = {'service' : 'stats', 'action' : 'collect'};
		// Add event parameters
		for( var i in eventSet){
			eventRequest[ 'event:' + i] = eventSet[i];
		}

		// Send events for this player:
		$( this.embedPlayer ).trigger( 'KalturaSendAnalyticEvent', [ KalturaStatsEventKey, eventSet ] );

		// check for defined callback:
		var parentTrackName = this.embedPlayer.getKalturaConfig( 'statistics', 'trackEventMonitor');
		if(  mw.getConfig('EmbedPlayer.IsFriendlyIframe') ){
			try {
				if( window.parent[ parentTrackName ] ){
					 window.parent[ parentTrackName ]( KalturaStatsEventKey, eventSet );
				}
			} catch( e ){
				// error in calling parent page event
			}
		}
		if (this.embedPlayer.getFlashvars('ks')){
			eventRequest['ks'] = this.embedPlayer.getFlashvars('ks');
		}

		// Do the api request:
		this.kClient.doRequest( eventRequest, null, true );
	},

	/**
	 * Binds player events for analytics reporting
	 */
	bindPlayerEvents: function(){

		// Setup local reference to embedPlayer
		var embedPlayer = this.embedPlayer;
		var _this = this;

		// Setup shortcut anonymous function for player bindings
		var b = function( hookName, eventType ){
			$( _this.embedPlayer ).bind( hookName + _this.bindPostFix, function(){
				_this.sendAnalyticsEvent( eventType );
			});
		};

		// When the player is ready
		b( 'widgetLoaded', 'WIDGET_LOADED' );

		// When the poster or video ( when autoplay ) media is loaded
		b( 'KalturaSupport_EntryDataReady', 'MEDIA_LOADED' );

		// When the play button is pressed or called from javascript
		b( 'firstPlay', 'PLAY' );

		// When the show Share menu is displayed
		b( 'showShareEvent', 'OPEN_VIRAL' );

		// When the show download menu is displayed
		b( 'downloadMedia', 'OPEN_DOWNLOAD' );

		// When the clip starts to buffer ( not all player types )
		b( 'bufferStartEvent', 'BUFFER_START' );

		// When the clip is full buffered
		b( 'bufferEndEvent', 'BUFFER_END' );

		// When the fullscreen button is pressed
		// ( presently does not register iPhone / iPad until it has js bindings )
		b( 'onOpenFullScreen', 'OPEN_FULL_SCREEN' );

		// When the close fullscreen button is pressed.
		// ( presently does not register iphone / ipad until it has js bindings )
		b( 'onCloseFullScreen', 'CLOSE_FULL_SCREEN' );

		// When the user plays (after the ondone event was fired )
		b( 'replayEvent', 'REPLAY' );

		// Bind on the seek event
		$( embedPlayer ).bind( 'seeked' + this.bindPostFix, function( seekTarget ) {
			// Don't send a bunch of seeks on scrub:
			if( _this.lastSeekEventTime == 0 ||
				_this.lastSeekEventTime + 2000	< new Date().getTime() )
			{
				_this.sendAnalyticsEvent( 'SEEK' );
			}

			// Update the last seekTime
			_this.lastSeekEventTime =  new Date().getTime();

			// Then set local seek flags
			this.hasSeeked = true;
			this.lastSeek = seekTarget;
		} );

		// Let updateTimeStats handle the currentTime monitor timing

		$( embedPlayer ).bind( 'monitorEvent' + this.bindPostFix, function(){
			_this.updateTimeStats();
		});

		/*
		 * Other kaltura event types that are presently not usable in the
		 * html5 player at this point in time:
		 *
		 * OPEN_EDIT = 8;
		 * OPEN_REPORT = 11;
		 * OPEN_UPLOAD = 18;
		 * SAVE_PUBLISH = 19;
		 * CLOSE_EDITOR = 20;
		 *
		 * PRE_BUMPER_PLAYED = 21;
		 * POST_BUMPER_PLAYED = 22;
		 * BUMPER_CLICKED = 23;
		 *
		 * FUTURE_USE_1 = 24;
		 * FUTURE_USE_2 = 25;
		 * FUTURE_USE_3 = 26;
		 */
	},

	/**
	 * Send updates for time stats
	 */
	updateTimeStats: function() {
		// Setup local references:
		var embedPlayer = this.embedPlayer;
		var _this = this;

		// Set the seek and time percent:
		var percent = embedPlayer.currentTime / embedPlayer.duration;
		var seekPercent = this.lastSeek/ embedPlayer.duration;

		// Send updates based on logic present in StatisticsMediator.as
		if ( !embedPlayer.isLive() ){
			if( !_this._p25Once && percent >= .25  &&  seekPercent <= .25 ) {

				_this._p25Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_25' );
				$( embedPlayer ).trigger( "firstQuartile" );

			} else if ( !_this._p50Once && percent >= .50 && seekPercent < .50 ) {

				_this._p50Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_50' );
				$( embedPlayer ).trigger( "secondQuartile" );

			} else if( !_this._p75Once && percent >= .75 && seekPercent < .75 ) {

				_this._p75Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_75' );
				$( embedPlayer ).trigger( "thirdQuartile" );

			} else if(  !_this._p100Once && percent >= .98 && seekPercent < 1) {

				_this._p100Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_100' );
			}
		}
	}
};

})( window.mw, window.jQuery );<|endoftext|>