plugin-user-behavior-collection

1.0.7 • Public • Published

Plugin-User-Behavior-Collection

Background

The more we know about our users, the better equipped we'll be to make smart choices about our application development investments. It is the truth not only for re-search but also for each Carina studio. However, the problem is how to design a common solution which could be applied to every studio without modifying their own business code. The most intuitive way is to use declarative programming, which could decouple the tracking tools from any business logics.

Design ideas

  1. There is one common metric for event tracking, while every studio could have their own standard metrics. (Of course, the standard metric could be flexibly expanded)
// common metric
export default {
    componentDidMount(){
        ...
    },
    componentWillReceiveProps(){
        ...
    },
    getDerivedStateFromProps(){
        ...
    },
    componentDidUpdate(){
        ...
    },
    //args: only one argument, which is the dwell time
    switchToOtherPage(...args: any[]){
        ...
    },
    //args: only one argument, which is the dwell time
    switchPageBack(...args: any[]){
      ...
    },
    //args: only one argument, which is the accumulated dwell time
    urlChanged(...args: any[]){
        ...
    },
    handleButtonWithoutArgClick(ele: any){
        ...
    },
    handleButtonWithArgClick(...args: any[]){
        ...
    },
    handleOtherElementWithoutArgClick(ele: any){
       ...
    },
    handleOtherElementWithArgClick(...args: any[]){
       ...
    }
}
// A config file to determine which method belongs to the method in the common metric
 
export default {
  handleButtonAClick: "handleButtonWithoutArgClick",
  handleButtonBClick: "handleButtonWithArgClick",
  handleLiClick: "handleWordClick",
  componentDidMount: "componentDidMount",
};
// A file to combine the common metric and the defined standard methric
 
import methodName from "./methodName";
import commonBehaviors from "./utils/common-behaviors";
 
const definedWatchList: { [key: string]: any } = Object.keys(methodName).map(
  (name: string) => commonBehaviors[methodName[name]]
);
const result: any = {};
Object.keys(methodName).forEach(
  (key, index) => (result[key] = definedWatchList[index])
);
// You can put your own defined tracking method here
export default result;
  1. Given that each user behavior will trigger any corresponding callback function, we could track these functions.
    Until now, I have two ways. Firstly, for those functions that meet the following requirements: the function must be defined with a function name and must not be called as an arrow function:
// This is a valid callback function
  handleButtonAClick() {
    console.log('Button A is clicked');
  }
 
    render() {
    return (
        <button onClick={this.handleButtonAClick}>buttonA</button>
    )
  }
// The followings are invalid callback functions
  handleButtonAClick = () => {
    console.log('Button A is clicked');
  }
 
    render() {
    return (
        <button onClick={this.handleButtonAClick}>buttonA</button>
    )
  }
 
// Or
 
render() {
    return (
        <button onClick={()=>{ console.log('Button A is clicked') }}>buttonA</button>
    )
  }

User could simply add a decorator at the top of the class (shown below):

@monitor(definedWatchList)
class App extends React.Component {
  ...
}

Secondly, for others, users also need to add decorator at the top of each method, and wrapper the class with my given tracking method (shown below):

@track({})
class App extends React.Component {
  @track((props, state, [event]) => ({
    ...commonBehavior.handleButtonWithoutArgClick(event),
  }))
  handleButtonCClick = () => {
    console.log("button C is clicked");
    this.props.history.push("/test");
  };
}
 
const TrackedApp = track()(App);
  1. Event tracking functions will be called after any callback function, and they could not change any data/return result in business logic code.

  2. Since we need to decouple this event tracking tool from any business logic code, I prefer using HOC (higher-order component) or decorator, so that we only need to package the tracking tool as a plugin/library, and import it in each component file.

  3. User do not need to send the collected data manually.

  4. Support to send several tracking events after one user behavior.
    For 5 and 6, considering sending too many HTTP requests will slow the render speed, it may be better to pile up the data collected from several tracking events, and send them once together. Till now, my design is to send a HTTP request at the end of each session. I will use a sessionStorage to temporarily store the behaviors triggered during this session. e.g. For example02

  5. For each session (user opens a page until closing it), a uid will be automatically generated. All triggered behaviros during this session will share this uid.

  6. Each behavior triggered by user will also trigger other render functions (e.g. componentDidMount). These behaviors could be regarded as one event, and they share one event ID.

  7. I also design a ticker to count the page's dwell time (It is not enough if we only record the trigger time of each behavior because user may temporarily minimize the browser window or change to other browser tabs).

  8. For re-search, we also want to have the search content when collecting the user behavior info of 'clicking any search result' event. The most straightforward is to capture the value of the searchbox element. Therefore, we store the unique id of the searchbox element as a global variable, and fetch the search content using

searchContentSEARCHBOX_ID !== '' ? (<HTMLInputElement>document.getElementById(SEARCHBOX_ID)).value : 'Not applicable',
  1. For re-search, we also want to know the sequence number of the clicked result among all the returned results. It is not good enough if we pass the BE data to the user behavior collection part. So, we still use DOM to do that. A global variable 'SEARCHBOX_LAYER' needs to be set. This variable defines whether the clicked element is at outermost layer. For example:
<div style="margin-bottom: 5px;">
  <div
    id="news-title"
    style="color: rgb(102, 0, 153); font-size: 14px; cursor: pointer;"
  >
    我的附件测试
  </div>
  <div id="news-url" style="color: rgb(0, 102, 33); font-size: 12px;">
    http://itsptest.orientsec.com.cn:8001/upload/IMG_S_20191111173025600.jpeg
  </div>
  <div id="news-description" style="font-size: 12px;">啊啊啊</div>
</div>

when you click the element

<div
  id="news-title"
  style="color: rgb(102, 0, 153); font-size: 14px; cursor: pointer;"
>
  我的附件测试
</div>

It is not at the outermost layer, while its parent node is. Therefore, SEARCHBOX_LAYER is set to be 1. After we get the outermost node of the clicked element, we use the following mehtod to get the sequence number:

positionSEARCHRESULT_LAYER !== ""
  ? Array.prototype.indexOf.call(_.get(node, "parentNode.children", []), node) +
    1
  : "Not applicable";

Install

npm i plugin-user-behavior-collection

Usage

  1. Add user info and ticker
      <UserInfo userName="Serina" />
      <RunTicker />
  1. For methods that could be found using Object.prototype.hasOwnProperty (which is not defined with arrow function):

Add a config file ('methodName.ts') to determine which method belongs to the method in the common metric

export default {
  handleButtonAClick: "handleButtonWithoutArgClick",
  handleButtonBClick: "handleButtonWithArgClick",
  handleLiClick: "handleWordClick",
  componentDidMount: "componentDidMount",
};

Add another file ('defined-behaviors.ts') to combine the common metric and the defined standard methric

import methodName from "./methodName";
import { commonBehaviors } from "plugin-user-behavior-collection";
 
const definedWatchList: { [key: string]: any } = Object.keys(methodName).map(
  (name: string) => commonBehaviors[methodName[name]]
);
const result: any = {};
Object.keys(methodName).forEach(
  (key, index) => (result[key] = definedWatchList[index])
);
// You can put your own defined tracking method here
export default result;

Import these methods to every class

import { monitor } from 'plugin-user-behavior-collection';
import definedBehaviors from './defined-behaviors'
 
@monitor(definedBehaviors)
class App extends React.Component<DefaultProps, IAppState> {
}
  1. For methods that could not be found using Object.prototype.hasOwnProperty (which is defined with arrow function):

Bind the tracking method with the specific method separately.

import { track, subCommonBehavior } from 'plugin-user-behavior-collection'
@track({})
class App extends React.Component<DefaultProps, IAppState> {
  @track((props: any, state: any, event: any) => ({
    ...subCommonBehavior.handleButtonWithArgClick(event, props),
  }))
  handleButtonBClick = (...testArgs: any[]) => {
    console.log('button B is clicked')
  }
 
  const TrackedApp = track()(App)
 
  export default withRouter(TrackedApp)
}

Example

You can see an example under 'example' folder, by using:

cd example
npm install
npm run start

Package Sidebar

Install

npm i plugin-user-behavior-collection

Weekly Downloads

0

Version

1.0.7

License

ISC

Unpacked Size

228 kB

Total Files

5

Last publish

Collaborators

  • jeremy_sehun