1 | 'use strict'
|
2 |
|
3 | const d3 = require('./d3.js')
|
4 | const icons = require('./icons.js')
|
5 | const categories = require('./categories.js')
|
6 | const EventEmitter = require('events')
|
7 |
|
8 | function getTextNodeBoundingRect (textNode) {
|
9 | const range = document.createRange()
|
10 | range.selectNode(textNode)
|
11 | return range.getBoundingClientRect()
|
12 | }
|
13 |
|
14 | class Issue {
|
15 | constructor (id, detected, title) {
|
16 | this.graphId = `graph-${id}`
|
17 | this.detected = detected
|
18 | this.title = title
|
19 | }
|
20 | }
|
21 |
|
22 | class Alert extends EventEmitter {
|
23 | constructor () {
|
24 | super()
|
25 |
|
26 | this.analysis = null
|
27 | this.opened = false
|
28 | this.fullTitleWidth = null
|
29 |
|
30 | this.container = d3.select('#alert')
|
31 |
|
32 | this.summary = this.container.append('div')
|
33 | .classed('summary', true)
|
34 | .on('click', () => {
|
35 | if (this.container.classed('has-issue')) this.emit(this.opened ? 'close' : 'open')
|
36 | })
|
37 |
|
38 | this.alert = this.summary.append('svg')
|
39 | .classed('alert', true)
|
40 | .call(icons.insertIcon('warning'))
|
41 |
|
42 | this.title = this.summary.append('div')
|
43 | .classed('title', true)
|
44 |
|
45 | this.titleTextNode = document.createTextNode('')
|
46 | this.title.node().appendChild(this.titleTextNode)
|
47 |
|
48 | this.toggle = this.summary.append('div')
|
49 | .classed('toggle', true)
|
50 | this.toggle.append('svg')
|
51 | .classed('arrow-down', true)
|
52 | .call(icons.insertIcon('arrow-down'))
|
53 | this.toggle.append('svg')
|
54 | .classed('arrow-up', true)
|
55 | .call(icons.insertIcon('arrow-up'))
|
56 |
|
57 | this.details = this.container.append('ul')
|
58 | .classed('details', true)
|
59 | }
|
60 |
|
61 | _setTitleText (title) {
|
62 | this.titleTextNode.textContent = `Detected ${title}`
|
63 | }
|
64 |
|
65 | setData (data) {
|
66 | this.analysis = data.analysis
|
67 |
|
68 |
|
69 | const content = categories.getContent(this.analysis.issueCategory)
|
70 | this.container.classed('has-issue', content.issue)
|
71 |
|
72 |
|
73 | const issues = this.analysis.issues
|
74 | const memory = issues.memory.external || issues.memory.heapTotal ||
|
75 | issues.memory.heapUsage || issues.memory.rss
|
76 |
|
77 | const issuesAsData = [
|
78 | new Issue('cpu', issues.cpu, 'CPU Usage'),
|
79 | new Issue('memory', memory, 'Memory Usage'),
|
80 | new Issue('delay', issues.delay, 'Event Loop Delay'),
|
81 | new Issue('handles', issues.handles, 'Active Handles')
|
82 | ].filter((issue) => issue.detected)
|
83 |
|
84 | this.details
|
85 | .selectAll('li')
|
86 | .data(issuesAsData)
|
87 | .enter()
|
88 | .append('li')
|
89 | .on('click', (d) => this.emit('click', d.graphId))
|
90 | .on('mouseover', (d) => this.emit('hover-in', d.graphId))
|
91 | .on('mouseout', (d) => this.emit('hover-out', d.graphId))
|
92 | .append('span')
|
93 | .text((d) => d.title)
|
94 |
|
95 |
|
96 | this._setTitleText(content.title)
|
97 | this.fullTitleWidth = getTextNodeBoundingRect(this.titleTextNode).width
|
98 | }
|
99 |
|
100 | draw () {
|
101 | const content = categories.getContent(this.analysis.issueCategory)
|
102 |
|
103 |
|
104 | const titleNode = this.title.node()
|
105 | if (parseInt(window.getComputedStyle(titleNode).width) < this.fullTitleWidth) {
|
106 | this._setTitleText(content.issue ? 'issue' : 'no issue')
|
107 | } else {
|
108 | this._setTitleText(content.title)
|
109 | }
|
110 | }
|
111 |
|
112 | open () {
|
113 | this.opened = true
|
114 | this.container.classed('open', true)
|
115 | }
|
116 |
|
117 | close () {
|
118 | this.opened = false
|
119 | this.container.classed('open', false)
|
120 | }
|
121 | }
|
122 |
|
123 | module.exports = new Alert()
|