Completed
Push — master ( 84f701...51cc74 )
by Michael
12s queued 10s
created

src/Experiment.jsx   A

Complexity

Total Complexity 20
Complexity/F 2.22

Size

Lines of Code 135
Function Count 9

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 106
dl 0
loc 135
rs 10
c 0
b 0
f 0
wmc 20
mnd 11
bc 11
fnc 9
bpm 1.2222
cpm 2.2222
noi 0

9 Functions

Rating   Name   Duplication   Size   Complexity  
B Experiment._chooseVariant 0 42 7
A Experiment._isVariant 0 11 3
A Experiment.getName 0 3 1
A Experiment.getActiveVariant 0 3 1
A Experiment.getActiveVariantName 0 3 1
A Experiment.getVariant 0 6 1
A Experiment._onChoice 0 10 3
A Experiment.componentDidUpdate 0 15 2
A Experiment.render 0 3 1
1
import React, { Component } from 'react'
2
import PropTypes from 'prop-types'
3
import weightedRandom from './utils/weightedRandom'
4
import getUserIdentifier from './utils/getUserIdentifier'
5
import getWeight from './utils/getWeight'
6
7
8
export default class Experiment extends Component {
9
  static propTypes = {
10
    name: PropTypes.string.isRequired,
11
    onChoice: PropTypes.func,
12
    onRawChoice: PropTypes.func,
13
    userIdentifier: PropTypes.oneOfType([
14
      PropTypes.string,
15
      PropTypes.number,
16
    ]),
17
    variantName: PropTypes.string,
18
  }
19
20
  constructor(props) {
21
    super(props)
22
23
    this.state = {}
24
    this.state.activeVariant = this._chooseVariant(true)
25
  }
26
27
  componentDidUpdate(prevProps) {
28
    const { name, children, userIdentifier, variantName } = this.props
29
    const isNewExperiment = (
30
      prevProps.name !== name
31
      || prevProps.userIdentifier !== userIdentifier
32
      || prevProps.variantName !== variantName
33
    )
34
35
    if (!isNewExperiment && prevProps.children === children) {
36
      return
37
    }
38
39
    this.setState({
40
      activeVariant: this._chooseVariant(prevProps, isNewExperiment),
41
    })
42
  }
43
44
  getActiveVariant() {
45
    return this.state.activeVariant
46
  }
47
48
  getActiveVariantName() {
49
    return this.state.activeVariant && this.state.activeVariant.props.name
50
  }
51
52
  getName() {
53
    return this.props.name
54
  }
55
56
  getVariant(variantName) {
57
    const children = React.Children.toArray(this.props.children)
58
59
    return children.find(element => (
60
      this._isVariant(element) && element.props.name === variantName
61
    ))
62
  }
63
64
  _chooseVariant(isNewExperiment) {
65
    const { variantName, userIdentifier } = this.props
66
67
    if (variantName) {
68
      const variant = this.getVariant(variantName)
69
70
      if (isNewExperiment && variant) {
71
        this._onChoice(variant)
72
      }
73
74
      return variant
75
    }
76
77
    const children = React.Children.toArray(this.props.children)
78
    const activeVariantName = this.state.activeVariant && this.state.activeVariant.props.name
79
    const variants = [], weights = []
80
81
    for (const element of children) {
82
      if (!this._isVariant(element)) {
83
        continue
84
      }
85
86
      if (!isNewExperiment && activeVariantName === element.props.name) {
87
        return element
88
      }
89
90
      variants.push(element)
91
      weights.push(getWeight(element.props.weight))
92
    }
93
94
    const randomSeed = getUserIdentifier(userIdentifier)
95
    const index = weightedRandom(weights, randomSeed)
96
97
    if (index === -1) {
98
      return null
99
    }
100
101
    const variant = variants[index]
102
    this._onChoice(variant)
103
104
    return variant
105
  }
106
107
  _isVariant(element) {
108
    if (!React.isValidElement(element)) {
109
      return false
110
    }
111
112
    if (!element.type.isVariant) {
113
      throw new Error('Experiment children must be Variant components.')
114
    }
115
116
    return true
117
  }
118
119
  _onChoice(variant) {
120
    const { onChoice, onRawChoice, name } = this.props
121
122
    if (onChoice instanceof Function) {
123
      onChoice(name, variant.props.name)
124
    }
125
126
    if (onRawChoice instanceof Function) {
127
      onRawChoice(this, variant)
128
    }
129
  }
130
131
  render() {
132
    return this.state.activeVariant
133
  }
134
}
135