Merge pull request #4244 from influxdata/enhancements/web-workers

Initial implementation of background workers
pull/4269/head
Brandon Farmer 2018-08-17 20:32:02 -07:00 committed by GitHub
commit 442888b9cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 855 additions and 206 deletions

View File

@ -10,7 +10,7 @@
}, },
"scripts": { "scripts": {
"start": "node parcel.js", "start": "node parcel.js",
"build": "parcel build -d build --no-source-maps --public-url '' src/index.html", "build": "parcel build -d build --no-source-maps --public-url '' src/index.html src/worker/worker.ts",
"clean": "rm -rf ./build/* && rm -rf ./.cache", "clean": "rm -rf ./build/* && rm -rf ./.cache",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
@ -33,6 +33,7 @@
"@types/dygraphs": "^1.1.6", "@types/dygraphs": "^1.1.6",
"@types/enzyme": "^3.1.9", "@types/enzyme": "^3.1.9",
"@types/jest": "^22.1.4", "@types/jest": "^22.1.4",
"@types/levelup": "^0.0.30",
"@types/lodash": "^4.14.104", "@types/lodash": "^4.14.104",
"@types/node": "^9.4.6", "@types/node": "^9.4.6",
"@types/papaparse": "^4.1.34", "@types/papaparse": "^4.1.34",
@ -71,6 +72,7 @@
"eslint-plugin-react": "6.6.0", "eslint-plugin-react": "6.6.0",
"eslint-watch": "^3.1.2", "eslint-watch": "^3.1.2",
"express": "^4.14.0", "express": "^4.14.0",
"fake-indexeddb": "^2.0.4",
"http-proxy-middleware": "^0.18.0", "http-proxy-middleware": "^0.18.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^23.1.0", "jest": "^23.1.0",
@ -98,11 +100,14 @@
"d3-color": "^1.2.0", "d3-color": "^1.2.0",
"d3-scale": "^2.1.0", "d3-scale": "^2.1.0",
"dygraphs": "2.1.0", "dygraphs": "2.1.0",
"encoding-down": "^5.0.4",
"enzyme-adapter-react-16": "^1.1.1", "enzyme-adapter-react-16": "^1.1.1",
"eslint-plugin-babel": "^4.1.2", "eslint-plugin-babel": "^4.1.2",
"fast.js": "^0.1.1", "fast.js": "^0.1.1",
"fixed-data-table-2": "^0.8.13", "fixed-data-table-2": "^0.8.13",
"he": "^1.1.1", "he": "^1.1.1",
"level-js": "^3.0.0",
"levelup": "^3.1.1",
"lodash": "^4.3.0", "lodash": "^4.3.0",
"moment": "^2.13.0", "moment": "^2.13.0",
"nano-date": "^2.0.1", "nano-date": "^2.0.1",

View File

@ -7,7 +7,7 @@ const port = Number(process.env.PORT || 8080)
console.log(`Serving on http://localhost:${port}`) // eslint-disable-line no-console console.log(`Serving on http://localhost:${port}`) // eslint-disable-line no-console
const app = express() const app = express()
const bundler = new Bundler('src/index.html', { const bundler = new Bundler(['src/index.html', 'src/worker/worker.ts'], {
outDir: './build/', outDir: './build/',
}) })

View File

@ -660,8 +660,7 @@ export const getDashboardWithTemplatesAsync = (
let dashboard: Dashboard let dashboard: Dashboard
try { try {
const resp = await getDashboardAJAX(dashboardId) dashboard = await getDashboardAJAX(dashboardId)
dashboard = resp.data
} catch { } catch {
dispatch(replace(`/sources/${source.id}/dashboards`)) dispatch(replace(`/sources/${source.id}/dashboards`))
dispatch(notify(notifyDashboardNotFound(dashboardId))) dispatch(notify(notifyDashboardNotFound(dashboardId)))

View File

@ -1,5 +1,7 @@
import AJAX from 'src/utils/ajax' import AJAX from 'src/utils/ajax'
import {manager} from 'src/worker/JobManager'
import { import {
linksFromDashboards, linksFromDashboards,
updateDashboardLinks, updateDashboardLinks,
@ -37,10 +39,8 @@ export const loadDashboardLinks = async (
export const getDashboard = async dashboardID => { export const getDashboard = async dashboardID => {
try { try {
return await AJAX({ const url = `/chronograf/v1/dashboards/${dashboardID}`
method: 'GET', return manager.get(url)
url: `/chronograf/v1/dashboards/${dashboardID}`,
})
} catch (error) { } catch (error) {
console.error(error) console.error(error)
throw error throw error

View File

@ -1,4 +1,3 @@
import calculateSize from 'calculate-size'
import _ from 'lodash' import _ from 'lodash'
import {fastMap, fastReduce, fastFilter} from 'src/utils/fast' import {fastMap, fastReduce, fastFilter} from 'src/utils/fast'
@ -16,6 +15,10 @@ import {
} from 'src/types/dashboards' } from 'src/types/dashboards'
import {TimeSeriesValue} from 'src/types/series' import {TimeSeriesValue} from 'src/types/series'
const calculateSize = (message: string): number => {
return message.length * 7
}
interface ColumnWidths { interface ColumnWidths {
totalWidths: number totalWidths: number
widths: {[x: string]: number} widths: {[x: string]: number}
@ -41,11 +44,7 @@ const calculateTimeColumnWidth = (timeFormat: string): number => {
timeFormat = _.replace(timeFormat, 'h', '00') timeFormat = _.replace(timeFormat, 'h', '00')
timeFormat = _.replace(timeFormat, 'X', '1522286058') timeFormat = _.replace(timeFormat, 'X', '1522286058')
const {width} = calculateSize(timeFormat, { const width = calculateSize(timeFormat)
font: '"RobotoMono", monospace',
fontSize: '13px',
fontWeight: 'bold',
})
return width + CELL_HORIZONTAL_PADDING return width + CELL_HORIZONTAL_PADDING
} }
@ -91,11 +90,7 @@ const updateMaxWidths = (
const currentWidth = useTimeWidth const currentWidth = useTimeWidth
? timeFormatWidth ? timeFormatWidth
: calculateSize(colValue.toString().trim(), { : calculateSize(colValue.toString().trim()) + CELL_HORIZONTAL_PADDING
font: isLabel ? '"Roboto"' : '"RobotoMono", monospace',
fontSize: '12px',
fontWeight: '500',
}).width + CELL_HORIZONTAL_PADDING
const {widths: Widths} = maxColumnWidths const {widths: Widths} = maxColumnWidths
const maxWidth = _.get(Widths, `${columnLabel}`, 0) const maxWidth = _.get(Widths, `${columnLabel}`, 0)

View File

@ -47,9 +47,9 @@ export const getDataForCSV = (
query: queryString, query: queryString,
}) })
const {data} = timeSeriesToTableGraph([{response}]) const {data} = await timeSeriesToTableGraph([{response}])
const name = csvName(query.queryConfig) const name = csvName(query.queryConfig)
download(dataToCSV(data), `${name}.csv`, 'text/plain') download(dataToCSV(data as any), `${name}.csv`, 'text/plain')
} catch (error) { } catch (error) {
errorThrown(error, 'Unable to download .csv file') errorThrown(error, 'Unable to download .csv file')
console.error(error) console.error(error)

View File

@ -4,17 +4,12 @@ import {connect} from 'react-redux'
import TimeSeries from 'src/shared/components/time_series/TimeSeries' import TimeSeries from 'src/shared/components/time_series/TimeSeries'
// Components // Components
import Dygraph from 'src/shared/components/Dygraph'
import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown' import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown'
import RuleGraphDygraph from 'src/kapacitor/components/RuleGraphDygraph'
// Utils // Utils
import buildInfluxQLQuery from 'src/utils/influxql' import buildInfluxQLQuery from 'src/utils/influxql'
import buildQueries from 'src/utils/buildQueriesForGraphs' import buildQueries from 'src/utils/buildQueriesForGraphs'
import underlayCallback from 'src/kapacitor/helpers/ruleGraphUnderlay'
import {timeSeriesToDygraph} from 'src/utils/timeSeriesTransformers'
// Constants
import {LINE_COLORS_RULE_GRAPH} from 'src/shared/constants/graphColorPalettes'
// Types // Types
import {Source, AlertRule, QueryConfig, Query, TimeRange} from 'src/types' import {Source, AlertRule, QueryConfig, Query, TimeRange} from 'src/types'
@ -63,25 +58,14 @@ class RuleGraph extends PureComponent<Props> {
queries={this.queries} queries={this.queries}
> >
{data => { {data => {
const {labels, timeSeries, dygraphSeries} = timeSeriesToDygraph(
data.timeSeries,
'rule-builder'
)
return ( return (
<Dygraph <RuleGraphDygraph
labels={labels} loading={data.loading}
staticLegend={false} query={this.props.query}
isGraphFilled={false} rule={rule}
ruleValues={rule.values}
options={this.options}
timeRange={timeRange} timeRange={timeRange}
queries={this.queries} timeSeries={data.timeSeries}
timeSeries={timeSeries} setHoverTime={this.props.setHoverTime}
dygraphSeries={dygraphSeries}
colors={LINE_COLORS_RULE_GRAPH}
containerStyle={this.containerStyle}
underlayCallback={underlayCallback(rule)}
handleSetHoverTime={this.props.setHoverTime}
/> />
) )
}} }}
@ -91,30 +75,6 @@ class RuleGraph extends PureComponent<Props> {
) )
} }
private get options() {
return {
rightGap: 0,
yRangePad: 10,
labelsKMB: true,
fillGraph: true,
axisLabelWidth: 60,
animatedZooms: true,
drawAxesAtZero: true,
axisLineColor: '#383846',
gridLineColor: '#383846',
connectSeparatedPoints: true,
}
}
private get containerStyle(): CSSProperties {
return {
width: 'calc(100% - 32px)',
height: 'calc(100% - 16px)',
position: 'absolute',
top: '8px',
}
}
private get style(): CSSProperties { private get style(): CSSProperties {
return {height: '100%'} return {height: '100%'}
} }

View File

@ -0,0 +1,106 @@
import React, {Component, CSSProperties} from 'react'
import Dygraph from 'src/shared/components/Dygraph'
// Constants
import {LINE_COLORS_RULE_GRAPH} from 'src/shared/constants/graphColorPalettes'
// Utils
import buildQueries from 'src/utils/buildQueriesForGraphs'
import underlayCallback from 'src/kapacitor/helpers/ruleGraphUnderlay'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {setHoverTime as setHoverTimeAction} from 'src/dashboards/actions'
import {
TimeSeriesToDyGraphReturnType,
timeSeriesToDygraph,
} from 'src/utils/timeSeriesTransformers'
// Types
import {
AlertRule,
QueryConfig,
Query,
TimeRange,
RemoteDataState,
} from 'src/types'
import {TimeSeriesServerResponse} from 'src/types/series'
interface Props {
query: QueryConfig
rule: AlertRule
timeRange: TimeRange
setHoverTime: typeof setHoverTimeAction
loading: RemoteDataState
timeSeries: TimeSeriesServerResponse[]
}
interface State {
timeSeriesToDygraphResult?: TimeSeriesToDyGraphReturnType
}
@ErrorHandling
class RuleGraphDygraph extends Component<Props, State> {
public async componentWillReceiveProps(nextProps: Props) {
const {loading, timeSeries} = this.props
if (
loading !== nextProps.loading &&
nextProps.loading === RemoteDataState.Done
) {
const result = await timeSeriesToDygraph(timeSeries, 'rule-builder')
this.setState({timeSeriesToDygraphResult: result})
}
}
public render() {
const {timeRange, rule} = this.props
const {timeSeriesToDygraphResult} = this.state
return (
<Dygraph
labels={timeSeriesToDygraphResult.labels}
staticLegend={false}
isGraphFilled={false}
ruleValues={rule.values}
options={this.options}
timeRange={timeRange}
queries={this.queries}
timeSeries={timeSeriesToDygraphResult.timeSeries}
dygraphSeries={timeSeriesToDygraphResult.dygraphSeries}
colors={LINE_COLORS_RULE_GRAPH}
containerStyle={this.containerStyle}
underlayCallback={underlayCallback(rule)}
handleSetHoverTime={this.props.setHoverTime}
/>
)
}
private get containerStyle(): CSSProperties {
return {
width: 'calc(100% - 32px)',
height: 'calc(100% - 16px)',
position: 'absolute',
top: '8px',
}
}
private get options() {
return {
rightGap: 0,
yRangePad: 10,
labelsKMB: true,
fillGraph: true,
axisLabelWidth: 60,
animatedZooms: true,
drawAxesAtZero: true,
axisLineColor: '#383846',
gridLineColor: '#383846',
connectSeparatedPoints: true,
}
}
private get queries(): Query[] {
const {query, timeRange} = this.props
return buildQueries([query], timeRange)
}
}
export default RuleGraphDygraph

View File

@ -126,13 +126,13 @@ export default class LayoutCell extends Component<Props> {
onSummonOverlayTechnologies(cell) onSummonOverlayTechnologies(cell)
} }
private handleCSVDownload = (): void => { private handleCSVDownload = async (): Promise<void> => {
const {cellData, cell} = this.props const {cellData, cell} = this.props
const joinedName = cell.name.split(' ').join('_') const joinedName = cell.name.split(' ').join('_')
const {data} = timeSeriesToTableGraph(cellData) const {data} = await timeSeriesToTableGraph(cellData)
try { try {
download(dataToCSV(data), `${joinedName}.csv`, 'text/plain') download(dataToCSV(data as any), `${joinedName}.csv`, 'text/plain')
} catch (error) { } catch (error) {
notify(notifyCSVDownloadFailed()) notify(notifyCSVDownloadFailed())
console.error(error) console.error(error)

View File

@ -14,12 +14,13 @@ import {
timeSeriesToDygraph, timeSeriesToDygraph,
TimeSeriesToDyGraphReturnType, TimeSeriesToDyGraphReturnType,
} from 'src/utils/timeSeriesTransformers' } from 'src/utils/timeSeriesTransformers'
import {manager} from 'src/worker/JobManager'
// Types // Types
import {ColorString} from 'src/types/colors' import {ColorString} from 'src/types/colors'
import {DecimalPlaces} from 'src/types/dashboards' import {DecimalPlaces} from 'src/types/dashboards'
import {TimeSeriesServerResponse} from 'src/types/series' import {TimeSeriesServerResponse} from 'src/types/series'
import {DygraphValue} from 'src/types/dygraphs' // import {DygraphValue} from 'src/types/dygraphs'
import {Query, Axes, TimeRange, RemoteDataState, CellType} from 'src/types' import {Query, Axes, TimeRange, RemoteDataState, CellType} from 'src/types'
interface Props { interface Props {
@ -41,34 +42,50 @@ interface Props {
type LineGraphProps = Props & RouteComponentProps<any, any> type LineGraphProps = Props & RouteComponentProps<any, any>
interface State {
timeSeries?: TimeSeriesToDyGraphReturnType
}
@ErrorHandlingWith(InvalidData) @ErrorHandlingWith(InvalidData)
class LineGraph extends PureComponent<LineGraphProps> { class LineGraph extends PureComponent<LineGraphProps, State> {
public static defaultProps: Partial<LineGraphProps> = { public static defaultProps: Partial<LineGraphProps> = {
staticLegend: false, staticLegend: false,
} }
private isComponentMounted: boolean = false
private isValidData: boolean = true private isValidData: boolean = true
private timeSeries: TimeSeriesToDyGraphReturnType
public componentWillMount() { constructor(props: LineGraphProps) {
const {data} = this.props super(props)
this.parseTimeSeries(data)
this.state = {}
} }
public parseTimeSeries(data) { public async componentDidMount() {
this.isComponentMounted = true
const {data} = this.props
await this.parseTimeSeries(data)
}
public componentWillUnmount() {
this.isComponentMounted = false
}
public async parseTimeSeries(data) {
const {location} = this.props const {location} = this.props
this.timeSeries = timeSeriesToDygraph(data, location.pathname) const timeSeries = await timeSeriesToDygraph(data, location.pathname)
const timeSeries = _.get(this.timeSeries, 'timeSeries', []) const innerTimeSeries = _.get(timeSeries, 'timeSeries', [])
this.isValidData = this.validateTimeSeries(timeSeries) this.isValidData = await manager.validateDygraphData(innerTimeSeries)
if (!this.isComponentMounted) {
return
} }
public componentWillUpdate(nextProps) { this.setState({timeSeries})
const {data, activeQueryIndex} = this.props }
if (
data !== nextProps.data || public componentWillReceiveProps(nextProps: LineGraphProps) {
activeQueryIndex !== nextProps.activeQueryIndex if (nextProps.loading === RemoteDataState.Done) {
) {
this.parseTimeSeries(nextProps.data) this.parseTimeSeries(nextProps.data)
} }
} }
@ -94,7 +111,11 @@ class LineGraph extends PureComponent<LineGraphProps> {
handleSetHoverTime, handleSetHoverTime,
} = this.props } = this.props
const {labels, timeSeries, dygraphSeries} = this.timeSeries if (!this.state.timeSeries) {
return <h3 className="graph-spinner" />
}
const {labels, timeSeries, dygraphSeries} = this.state.timeSeries
const options = { const options = {
rightGap: 0, rightGap: 0,
@ -147,16 +168,6 @@ class LineGraph extends PureComponent<LineGraphProps> {
) )
} }
private validateTimeSeries = (ts: DygraphValue[][]) => {
return _.every(ts, r =>
_.every(
r,
(v, i: number) =>
(i === 0 && Date.parse(v as string)) || _.isNumber(v) || _.isNull(v)
)
)
}
private get isGraphFilled(): boolean { private get isGraphFilled(): boolean {
const {type} = this.props const {type} = this.props

View File

@ -85,6 +85,7 @@ class RefreshingGraph extends PureComponent<Props> {
return ( return (
<TimeSeries <TimeSeries
source={source} source={source}
cellType={type}
inView={inView} inView={inView}
queries={this.queries} queries={this.queries}
timeRange={timeRange} timeRange={timeRange}

View File

@ -35,6 +35,8 @@ import {
Sort, Sort,
} from 'src/types/dashboards' } from 'src/types/dashboards'
import {manager} from 'src/worker/JobManager'
const COLUMN_MIN_WIDTH = 100 const COLUMN_MIN_WIDTH = 100
const ROW_HEIGHT = 30 const ROW_HEIGHT = 30
@ -84,6 +86,7 @@ interface State {
class TableGraph extends Component<Props, State> { class TableGraph extends Component<Props, State> {
private gridContainer: HTMLDivElement private gridContainer: HTMLDivElement
private multiGrid?: MultiGrid private multiGrid?: MultiGrid
private isComponentMounted: boolean = false
constructor(props: Props) { constructor(props: Props) {
super(props) super(props)
@ -167,6 +170,7 @@ class TableGraph extends Component<Props, State> {
} }
public componentWillUnmount() { public componentWillUnmount() {
this.isComponentMounted = false
window.removeEventListener('resize', this.handleResize) window.removeEventListener('resize', this.handleResize)
} }
@ -179,7 +183,8 @@ class TableGraph extends Component<Props, State> {
) )
} }
public componentDidMount() { public async componentDidMount() {
this.isComponentMounted = true
window.addEventListener('resize', this.handleResize) window.addEventListener('resize', this.handleResize)
const sortField: string = _.get( const sortField: string = _.get(
@ -196,13 +201,17 @@ class TableGraph extends Component<Props, State> {
fieldOptions, fieldOptions,
decimalPlaces, decimalPlaces,
} = this.props } = this.props
const result = timeSeriesToTableGraph(data) const result = await timeSeriesToTableGraph(data)
const sortedLabels = result.sortedLabels const sortedLabels = result.sortedLabels
const computedFieldOptions = computeFieldOptions(fieldOptions, sortedLabels) const computedFieldOptions = computeFieldOptions(fieldOptions, sortedLabels)
this.handleUpdateFieldOptions(computedFieldOptions) this.handleUpdateFieldOptions(computedFieldOptions)
const {transformedData, sortedTimeVals, columnWidths} = transformTableData( const {
transformedData,
sortedTimeVals,
columnWidths,
} = await manager.tableTransform(
result.data, result.data,
sort, sort,
computedFieldOptions, computedFieldOptions,
@ -234,12 +243,12 @@ class TableGraph extends Component<Props, State> {
) )
} }
public componentWillReceiveProps(nextProps: Props) { public async componentWillReceiveProps(nextProps: Props) {
const {sort} = this.state const {sort} = this.state
let result = {} let result = {}
if (this.hasDataChanged(nextProps.data)) { if (this.hasDataChanged(nextProps.data)) {
result = timeSeriesToTableGraph(nextProps.data) result = await timeSeriesToTableGraph(nextProps.data)
} }
const data = _.get(result, 'data', this.state.data) const data = _.get(result, 'data', this.state.data)
@ -278,7 +287,7 @@ class TableGraph extends Component<Props, State> {
transformedData, transformedData,
sortedTimeVals, sortedTimeVals,
columnWidths, columnWidths,
} = transformTableData( } = await manager.tableTransform(
data, data,
sort, sort,
computedFieldOptions, computedFieldOptions,
@ -296,6 +305,10 @@ class TableGraph extends Component<Props, State> {
isTimeVisible = _.get(timeField, 'visible', this.state.isTimeVisible) isTimeVisible = _.get(timeField, 'visible', this.state.isTimeVisible)
} }
if (!this.isComponentMounted) {
return
}
this.setState({ this.setState({
data, data,
sortedLabels, sortedLabels,

View File

@ -7,7 +7,14 @@ import uuid from 'uuid'
import {fetchTimeSeries} from 'src/shared/apis/query' import {fetchTimeSeries} from 'src/shared/apis/query'
// Types // Types
import {Template, Source, Query, RemoteDataState, TimeRange} from 'src/types' import {
Template,
Source,
Query,
RemoteDataState,
TimeRange,
CellType,
} from 'src/types'
import {TimeSeriesServerResponse, TimeSeriesResponse} from 'src/types/series' import {TimeSeriesServerResponse, TimeSeriesResponse} from 'src/types/series'
import {GrabDataForDownloadHandler} from 'src/types/layout' import {GrabDataForDownloadHandler} from 'src/types/layout'
@ -23,6 +30,7 @@ interface RenderProps {
interface Props { interface Props {
source: Source source: Source
cellType?: CellType
queries: Query[] queries: Query[]
timeRange: TimeRange timeRange: TimeRange
children: (r: RenderProps) => JSX.Element children: (r: RenderProps) => JSX.Element
@ -46,6 +54,7 @@ const GraphLoadingDots = () => (
<div /> <div />
</div> </div>
) )
class TimeSeries extends Component<Props, State> { class TimeSeries extends Component<Props, State> {
public static defaultProps = { public static defaultProps = {
inView: true, inView: true,
@ -53,25 +62,23 @@ class TimeSeries extends Component<Props, State> {
} }
public static getDerivedStateFromProps(props: Props, state: State) { public static getDerivedStateFromProps(props: Props, state: State) {
let {isFirstFetch, timeRange} = state
const oldUpper = _.get(state, 'timeRange.upper', null) const oldUpper = _.get(state, 'timeRange.upper', null)
const oldLower = _.get(state, 'timeRange.lower', null) const oldLower = _.get(state, 'timeRange.lower', null)
const newUpper = _.get(props, 'timeRange.upper', null) const newUpper = _.get(props, 'timeRange.upper', null)
const newLower = _.get(props, 'timeRange.lower', null) const newLower = _.get(props, 'timeRange.lower', null)
if (oldUpper !== newUpper || oldLower !== newLower) { if (oldUpper !== newUpper || oldLower !== newLower) {
isFirstFetch = true return {
timeRange = props.timeRange isFirstFetch: true,
timeRange: props.timeRange,
}
} }
return { return null
isFirstFetch,
timeRange,
}
} }
private latestUUID: string = uuid.v1() private latestUUID: string = uuid.v1()
private isComponentMounted: boolean = false
constructor(props: Props) { constructor(props: Props) {
super(props) super(props)
@ -83,12 +90,32 @@ class TimeSeries extends Component<Props, State> {
} }
} }
public shouldComponentUpdate(prevProps: Props, prevState: State) {
const list = [
'source',
'queries',
'timeRange',
'inView',
'templates',
'cellType',
]
return (
this.state.loading !== prevState.loading ||
_.some(list, key => {
return !_.isEqual(this.props[key], prevProps[key])
})
)
}
public async componentDidMount() { public async componentDidMount() {
this.isComponentMounted = true
this.executeQueries() this.executeQueries()
AutoRefresh.subscribe(this.executeQueries) AutoRefresh.subscribe(this.executeQueries)
} }
public componentWillUnmount() { public componentWillUnmount() {
this.isComponentMounted = false
AutoRefresh.unsubscribe(this.executeQueries) AutoRefresh.unsubscribe(this.executeQueries)
} }
@ -105,7 +132,7 @@ class TimeSeries extends Component<Props, State> {
this.setState({loading: RemoteDataState.Loading}, () => { this.setState({loading: RemoteDataState.Loading}, () => {
window.setTimeout(() => { window.setTimeout(() => {
resolve() resolve()
}, 10) }, 0)
}) })
}) })
} }
@ -153,21 +180,28 @@ class TimeSeries extends Component<Props, State> {
response, response,
})) }))
if (!this.isComponentMounted) {
return
}
this.setState({ this.setState({
timeSeries: newSeries, timeSeries: newSeries,
loading: RemoteDataState.Done, loading: RemoteDataState.Done,
isFirstFetch: false,
}) })
if (grabDataForDownload) { if (grabDataForDownload) {
grabDataForDownload(newSeries) grabDataForDownload(newSeries)
} }
} catch (err) { } catch (err) {
if (!this.isComponentMounted) {
return
}
this.setState({ this.setState({
timeSeries: [], timeSeries: [],
loading: RemoteDataState.Error, loading: RemoteDataState.Error,
}) })
} finally {
this.setState({isFirstFetch: false})
} }
} }

View File

@ -94,15 +94,9 @@ async function AJAX<T = any>(
excludeBasepath = false excludeBasepath = false
): Promise<(T | T & {links: object}) | AxiosResponse<T>> { ): Promise<(T | T & {links: object}) | AxiosResponse<T>> {
try { try {
if (!links) {
console.error(
`AJAX function has no links. Trying to reach url ${url}, resource ${resource}, id ${id}, method ${method}`
)
}
url = addBasepath(url, excludeBasepath) url = addBasepath(url, excludeBasepath)
if (resource) { if (resource && links) {
url = id url = id
? addBasepath(`${links[resource]}/${id}`, excludeBasepath) ? addBasepath(`${links[resource]}/${id}`, excludeBasepath)
: addBasepath(`${links[resource]}`, excludeBasepath) : addBasepath(`${links[resource]}`, excludeBasepath)

View File

@ -2,6 +2,10 @@ import {getRootNode} from 'src/utils/nodes'
export const getBasepath = () => { export const getBasepath = () => {
const rootNode = getRootNode() const rootNode = getRootNode()
if (!rootNode) {
return ''
}
return rootNode.getAttribute('data-basepath') || '' return rootNode.getAttribute('data-basepath') || ''
} }

View File

@ -1,4 +1,4 @@
import AJAX from 'src/utils/ajax' import {manager} from 'src/worker/JobManager'
interface ProxyQuery { interface ProxyQuery {
source: string source: string
@ -16,16 +16,8 @@ export async function proxy<T = any>({
uuid, uuid,
}: ProxyQuery) { }: ProxyQuery) {
try { try {
return await AJAX<T>({ const result = await manager.proxy(source, query, db, rp, uuid)
method: 'POST', return result as T
url: source,
data: {
query,
db,
rp,
uuid,
},
})
} catch (error) { } catch (error) {
console.error(error) console.error(error)
throw error throw error

View File

@ -1,11 +1,7 @@
import {fastMap, fastReduce} from 'src/utils/fast' import {fastMap} from 'src/utils/fast'
import {groupByTimeSeriesTransform} from 'src/utils/groupByTimeSeriesTransform' import {manager} from 'src/worker/JobManager'
import { import {TimeSeriesServerResponse, TimeSeriesValue} from 'src/types/series'
TimeSeriesServerResponse,
TimeSeries,
TimeSeriesValue,
} from 'src/types/series'
import {DygraphSeries, DygraphValue} from 'src/types' import {DygraphSeries, DygraphValue} from 'src/types'
interface Label { interface Label {
@ -25,64 +21,22 @@ interface TimeSeriesToTableGraphReturnType {
sortedLabels: Label[] sortedLabels: Label[]
} }
export const timeSeriesToDygraph = ( export const timeSeriesToDygraph = async (
raw: TimeSeriesServerResponse[], raw: TimeSeriesServerResponse[],
pathname: string = '' pathname: string = ''
): TimeSeriesToDyGraphReturnType => { ): Promise<TimeSeriesToDyGraphReturnType> => {
const isTable = false const result = await manager.timeSeriesToDygraph(raw, pathname)
const isInDataExplorer = pathname.includes('data-explorer') const {timeSeries} = result
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform( const newTimeSeries = fastMap<DygraphValue[], DygraphValue[]>(
raw, timeSeries,
isTable ([time, ...values]) => [new Date(time), ...values]
) )
const labels = [ return {...result, timeSeries: newTimeSeries}
'time',
...fastMap<Label, string>(sortedLabels, ({label}) => label),
]
const timeSeries = fastMap<TimeSeries, DygraphValue[]>(
sortedTimeSeries,
({time, values}) => [new Date(time), ...values]
)
const dygraphSeries = fastReduce<Label, DygraphSeries>(
sortedLabels,
(acc, {label, responseIndex}) => {
if (!isInDataExplorer) {
acc[label] = {
axis: responseIndex === 0 ? 'y' : 'y2',
}
}
return acc
},
{}
)
return {labels, timeSeries, dygraphSeries}
} }
export const timeSeriesToTableGraph = ( export const timeSeriesToTableGraph = async (
raw: TimeSeriesServerResponse[] raw: TimeSeriesServerResponse[]
): TimeSeriesToTableGraphReturnType => { ): Promise<TimeSeriesToTableGraphReturnType> => {
const isTable = true return await manager.timeSeriesToTableGraph(raw)
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(
raw,
isTable
)
const labels = [
'time',
...fastMap<Label, string>(sortedLabels, ({label}) => label),
]
const tableData = fastMap<TimeSeries, TimeSeriesValue[]>(
sortedTimeSeries,
({time, values}) => [time, ...values]
)
const data = tableData.length ? [labels, ...tableData] : [[]]
return {
data,
sortedLabels,
}
} }

View File

@ -0,0 +1,5 @@
import levelup from 'levelup'
import encoding from 'encoding-down'
import level from 'level-js'
export default levelup(encoding(level('worker'), {valueEncoding: 'json'}))

14
ui/src/worker/Deferred.ts Normal file
View File

@ -0,0 +1,14 @@
class Deferred {
public promise: Promise<any>
public resolve: (...rest: any[]) => void
public reject: (error: Error) => void
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
}
}
export default Deferred

154
ui/src/worker/JobManager.ts Normal file
View File

@ -0,0 +1,154 @@
import _ from 'lodash'
import idGenerator from 'uuid'
import Deferred from 'src/worker/Deferred'
import DB from './Database'
import {TimeSeriesServerResponse} from 'src/types/series'
import {DygraphValue} from 'src/types'
import {getBasepath} from 'src/utils/basepath'
import {TimeSeriesToTableGraphReturnType} from 'src/worker/jobs/timeSeriesToTableGraph'
import {TimeSeriesToDyGraphReturnType} from 'src/worker/jobs/timeSeriesToDygraph'
const workerCount = navigator.hardwareConcurrency - 1
// HACK: work around parcel picking up workers and trying to inline them.
// This is need to allow for basepaths
const WorkerClass = Worker
class JobManager {
private currentIndex: number = 0
private workers: Worker[] = []
private jobs: {[key: string]: Deferred} = {}
constructor() {
_.times(workerCount, () => {
const worker = new WorkerClass(
[getBasepath(), 'worker', 'worker.js'].join('/')
)
worker.onmessage = this.handleMessage
worker.onerror = this.handleError
this.workers.push(worker)
})
}
public async tableTransform(
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
): Promise<any> {
const payload = {
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces,
}
return this.publishDBJob('TABLETRANSFORM', payload)
}
public proxy(url, query, db, rp, uuid): Promise<any> {
if (getBasepath() !== '') {
url = `${getBasepath()}${url}`
}
return this.publishJob('PROXY', {url, query, db, rp, uuid})
}
public get(url: string): Promise<any> {
if (getBasepath() !== '') {
url = `${getBasepath()}${url}`
}
return this.publishJob('GET', {url})
}
public timeSeriesToTableGraph = (
raw: TimeSeriesServerResponse[]
): Promise<TimeSeriesToTableGraphReturnType> => {
return this.publishDBJob('TSTOTABLEGRAPH', {raw})
}
public timeSeriesToDygraph = (
raw: TimeSeriesServerResponse[],
pathname: string = ''
): Promise<TimeSeriesToDyGraphReturnType> => {
return this.publishDBJob('TSTODYGRAPH', {raw, pathname})
}
public validateDygraphData = (ts: DygraphValue[][]) => {
return this.publishDBJob('VALIDATEDYGRAPHDATA', ts)
}
private handleMessage = async msg => {
const {data} = msg
const deferred = this.jobs[data.origin]
if (deferred) {
if (data.result === 'success') {
this.fetchPayload(deferred, data.id)
} else {
deferred.reject(data.error)
}
delete this.jobs[data.origin]
}
}
private fetchPayload = async (deferred, id) => {
try {
const payload = await DB.get(id)
await DB.del(id)
deferred.resolve(payload)
} catch (e) {
console.error(e)
deferred.reject(e)
}
}
private handleError = err => {
console.error(err)
}
private get worker(): Worker {
return this.workers[this.currentIndex]
}
private postMessage(msg: any): void {
this.worker.postMessage(msg)
this.incrementWorker()
}
private incrementWorker(): void {
this.currentIndex += 1
this.currentIndex %= workerCount
}
private publishJob = async (type, payload) => {
const id = idGenerator.v1()
const deferred = new Deferred()
this.jobs[id] = deferred
this.postMessage({id, type, payload})
return deferred.promise
}
private publishDBJob = async (type, payload) => {
const id = idGenerator.v1()
const deferred = new Deferred()
this.jobs[id] = deferred
await DB.put(id, payload)
this.postMessage({id, type})
return deferred.promise
}
}
export default JobManager
export const manager = new JobManager()

12
ui/src/worker/jobs/get.ts Normal file
View File

@ -0,0 +1,12 @@
import {Message} from 'src/worker/types'
const get = async (msg: Message) => {
const {
payload: {url},
} = msg
const response = await fetch(url)
return await response.json()
}
export default get

View File

@ -0,0 +1,15 @@
const proxy = async msg => {
const {
payload: {url, query, rp, db, uuid},
} = msg
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify({query, rp, db, uuid}),
})
const data = await response.json()
return {data}
}
export default proxy

View File

@ -0,0 +1,26 @@
import {fetchData} from 'src/worker/utils'
import {transformTableData} from 'src/dashboards/utils/tableGraph'
const tableTransform = async msg => {
const dbResult = await fetchData(msg)
const {
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces,
} = dbResult
return transformTableData(
data,
sort,
fieldOptions,
tableOptions,
timeFormat,
decimalPlaces
)
}
export default tableTransform

View File

@ -0,0 +1,62 @@
import {fastMap, fastReduce} from 'src/utils/fast'
import {groupByTimeSeriesTransform} from 'src/utils/groupByTimeSeriesTransform'
import {TimeSeriesServerResponse, TimeSeries} from 'src/types/series'
import {DygraphSeries, DygraphValue} from 'src/types'
import {fetchData} from 'src/worker/utils'
interface Label {
label: string
seriesIndex: number
responseIndex: number
}
export interface TimeSeriesToDyGraphReturnType {
labels: string[]
timeSeries: DygraphValue[][]
dygraphSeries: DygraphSeries
}
export const timeSeriesToDygraphWork = (
raw: TimeSeriesServerResponse[],
pathname: string = ''
): TimeSeriesToDyGraphReturnType => {
const isTable = false
const isInDataExplorer = pathname.includes('data-explorer')
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(
raw,
isTable
)
const labels = [
'time',
...fastMap<Label, string>(sortedLabels, ({label}) => label),
]
const timeSeries = fastMap<TimeSeries, DygraphValue[]>(
sortedTimeSeries,
({time, values}) => [new Date(time), ...values]
)
const dygraphSeries = fastReduce<Label, DygraphSeries>(
sortedLabels,
(acc, {label, responseIndex}) => {
if (!isInDataExplorer) {
acc[label] = {
axis: responseIndex === 0 ? 'y' : 'y2',
}
}
return acc
},
{}
)
return {labels, timeSeries, dygraphSeries}
}
const timeSeriesToDygraph = async msg => {
const {raw, pathname} = await fetchData(msg)
return timeSeriesToDygraphWork(raw, pathname)
}
export default timeSeriesToDygraph

View File

@ -0,0 +1,51 @@
import {fastMap} from 'src/utils/fast'
import {groupByTimeSeriesTransform} from 'src/utils/groupByTimeSeriesTransform'
import {
TimeSeriesServerResponse,
TimeSeries,
TimeSeriesValue,
} from 'src/types/series'
import {fetchData} from 'src/worker/utils'
interface Label {
label: string
seriesIndex: number
responseIndex: number
}
export interface TimeSeriesToTableGraphReturnType {
data: TimeSeriesValue[][]
sortedLabels: Label[]
}
export const timeSeriesToTableGraphWork = (
raw: TimeSeriesServerResponse[]
): TimeSeriesToTableGraphReturnType => {
const isTable = true
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(
raw,
isTable
)
const labels = [
'time',
...fastMap<Label, string>(sortedLabels, ({label}) => label),
]
const tableData = fastMap<TimeSeries, TimeSeriesValue[]>(
sortedTimeSeries,
({time, values}) => [time, ...values]
)
const data = tableData.length ? [labels, ...tableData] : [[]]
return {
data,
sortedLabels,
}
}
const timeSeriesToTableGraph = async msg => {
const {raw} = await fetchData(msg)
return timeSeriesToTableGraphWork(raw)
}
export default timeSeriesToTableGraph

View File

@ -0,0 +1,19 @@
import _ from 'lodash'
import {DygraphValue} from 'src/types/dygraphs'
import {fetchData} from 'src/worker/utils'
import {Message} from 'src/worker/types'
const validateDygraphData = async (msg: Message): Promise<boolean> => {
const ts: DygraphValue[][] = await fetchData(msg)
return _.every(ts, r =>
_.every(
r,
(v: any, i: number) =>
(i === 0 && Date.parse(v as string)) || _.isNumber(v) || _.isNull(v)
)
)
}
export default validateDygraphData

View File

@ -0,0 +1,5 @@
export interface Message {
id: string
type: string
payload: any
}

View File

@ -0,0 +1,39 @@
import DB from 'src/worker/Database'
import uuid from 'uuid'
import {Message} from 'src/worker/types'
export const removeData = async (msg: Message): Promise<void> => {
await DB.del(msg.id)
}
export const fetchData = async (msg: Message): Promise<any> => {
const result = await DB.get(msg.id)
await removeData(msg)
return result
}
export const error = (msg: Message, err: Error) => {
const id = uuid.v1()
postMessage({
id,
origin: msg.id,
result: 'error',
error: err.toString(),
})
}
export const success = async (msg: Message, payload: any) => {
const id = uuid.v1()
await DB.put(id, payload)
postMessage({
id,
origin: msg.id,
result: 'success',
})
}

42
ui/src/worker/worker.ts Normal file
View File

@ -0,0 +1,42 @@
import _ from 'lodash'
import {error, success} from 'src/worker/utils'
import {Message} from 'src/worker/types'
interface WorkerMessage {
data: Message
}
import timeSeriesToTableGraph from 'src/worker/jobs/timeSeriesToTableGraph'
import timeSeriesToDygraph from 'src/worker/jobs/timeSeriesToDygraph'
import proxy from 'src/worker/jobs/proxy'
import get from 'src/worker/jobs/get'
import tableTransform from 'src/worker/jobs/tableTransform'
import validateDygraphData from 'src/worker/jobs/validateDygraphData'
type Job = (msg: Message) => Promise<any>
const jobMapping: {[key: string]: Job} = {
GET: get,
PROXY: proxy,
TABLETRANSFORM: tableTransform,
TSTOTABLEGRAPH: timeSeriesToTableGraph,
TSTODYGRAPH: timeSeriesToDygraph,
VALIDATEDYGRAPHDATA: validateDygraphData,
}
const errorJob = async (data: Message) => {
error(data, new Error('UNKNOWN JOB TYPE'))
}
onmessage = async (workerMessage: WorkerMessage) => {
const {data} = workerMessage
const job: Job = _.get(jobMapping, data.type, errorJob)
try {
const result = await job(data)
success(data, result)
} catch (e) {
error(data, e)
}
}

View File

@ -3,6 +3,9 @@ import React from 'react'
import Adapter from 'enzyme-adapter-react-16' import Adapter from 'enzyme-adapter-react-16'
window.indexedDB = require('fake-indexeddb')
window.Worker = function() {}
configure({ configure({
adapter: new Adapter(), adapter: new Adapter(),
}) })

View File

@ -1,7 +1,5 @@
import { import {timeSeriesToDygraphWork as timeSeriesToDygraph} from 'src/worker/jobs/timeSeriesToDygraph'
timeSeriesToDygraph, import {timeSeriesToTableGraphWork as timeSeriesToTableGraph} from 'src/worker/jobs/timeSeriesToTableGraph'
timeSeriesToTableGraph,
} from 'src/utils/timeSeriesTransformers'
import { import {
filterTableColumns, filterTableColumns,

View File

@ -15,7 +15,7 @@
"target": "es6", "target": "es6",
"module": "es2015", "module": "es2015",
"moduleResolution": "node", "moduleResolution": "node",
"lib": ["es6", "es2017", "dom"], "lib": ["es6", "es2017", "dom", "webworker"],
"skipLibCheck": true, "skipLibCheck": true,
"isolatedModules": false, "isolatedModules": false,
"jsx": "react", "jsx": "react",

View File

@ -78,6 +78,12 @@
version "22.2.3" version "22.2.3"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d"
"@types/levelup@^0.0.30":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/levelup/-/levelup-0.0.30.tgz#869dd7a82cdbe5983e737006a1ff4e42fac97ca7"
dependencies:
"@types/node" "*"
"@types/lodash@^4.14.104": "@types/lodash@^4.14.104":
version "4.14.111" version "4.14.111"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.111.tgz#d926250baa9526c0ffe85914dd10363068e7893a" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.111.tgz#d926250baa9526c0ffe85914dd10363068e7893a"
@ -159,6 +165,12 @@ abbrev@1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
abstract-leveldown@^5.0.0, abstract-leveldown@~5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz#f7128e1f86ccabf7d2893077ce5d06d798e386c6"
dependencies:
xtend "~4.0.0"
accepts@~1.3.5: accepts@~1.3.5:
version "1.3.5" version "1.3.5"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
@ -1258,6 +1270,10 @@ balanced-match@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
base64-arraybuffer-es6@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.3.1.tgz#fdf0e382f4e2f56caf881f48ee0ce01ae79afe48"
base64-js@^1.0.2: base64-js@^1.0.2:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
@ -1974,7 +1990,7 @@ core-js@^1.0.0:
version "1.2.7" version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
core-js@^2.4.0, core-js@^2.5.0: core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.5.3:
version "2.5.7" version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
@ -2452,6 +2468,13 @@ defaults@^1.0.3:
dependencies: dependencies:
clone "^1.0.2" clone "^1.0.2"
deferred-leveldown@~4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-4.0.2.tgz#0b0570087827bf480a23494b398f04c128c19a20"
dependencies:
abstract-leveldown "~5.0.0"
inherits "^2.0.3"
define-properties@^1.1.2: define-properties@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
@ -2608,7 +2631,7 @@ domelementtype@~1.1.1:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
domexception@^1.0.0: domexception@^1.0.0, domexception@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
dependencies: dependencies:
@ -2702,6 +2725,16 @@ encodeurl@~1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
encoding-down@^5.0.4:
version "5.0.4"
resolved "https://registry.yarnpkg.com/encoding-down/-/encoding-down-5.0.4.tgz#1e477da8e9e9d0f7c8293d320044f8b2cd8e9614"
dependencies:
abstract-leveldown "^5.0.0"
inherits "^2.0.3"
level-codec "^9.0.0"
level-errors "^2.0.0"
xtend "^4.0.1"
encoding@^0.1.11: encoding@^0.1.11:
version "0.1.12" version "0.1.12"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
@ -2758,6 +2791,12 @@ enzyme@^3.3.0:
raf "^3.4.0" raf "^3.4.0"
rst-selector-parser "^2.2.3" rst-selector-parser "^2.2.3"
errno@~0.1.1:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
dependencies:
prr "~1.0.1"
error-ex@^1.2.0, error-ex@^1.3.1: error-ex@^1.2.0, error-ex@^1.3.1:
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@ -3237,6 +3276,14 @@ extsprintf@^1.2.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
fake-indexeddb@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-2.0.4.tgz#401715deb7fc9501866c9f329bde7742599e2de8"
dependencies:
core-js "^2.4.1"
realistic-structured-clone "^2.0.1"
setimmediate "^1.0.5"
falafel@^2.1.0: falafel@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c" resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c"
@ -3954,6 +4001,10 @@ ignore@^3.2.0, ignore@^3.3.3:
version "3.3.10" version "3.3.10"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
immediate@~3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c"
import-local@^1.0.0: import-local@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
@ -4342,7 +4393,7 @@ is-symbol@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
is-typedarray@~1.0.0: is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@ -5106,6 +5157,43 @@ left-pad@^1.2.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
level-codec@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-9.0.0.tgz#2d3a0e835c4aa8339ec63de3f5a37480b74a5f87"
level-errors@^2.0.0, level-errors@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-2.0.0.tgz#2de5b566b62eef92f99e19be74397fbc512563fa"
dependencies:
errno "~0.1.1"
level-iterator-stream@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-3.0.0.tgz#2f780b524b8e7fa479c195e5b1180cd409f85219"
dependencies:
inherits "^2.0.1"
readable-stream "^2.0.5"
xtend "^4.0.0"
level-js@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/level-js/-/level-js-3.0.0.tgz#e6c066fb529b23eec230849c0751e4f6d548c865"
dependencies:
abstract-leveldown "~5.0.0"
immediate "~3.2.3"
inherits "^2.0.3"
ltgt "^2.1.2"
typedarray-to-buffer "~3.1.5"
levelup@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/levelup/-/levelup-3.1.1.tgz#c2c0b3be2b4dc316647c53b42e2f559e232d2189"
dependencies:
deferred-leveldown "~4.0.0"
level-errors "~2.0.0"
level-iterator-stream "~3.0.0"
xtend "~4.0.0"
leven@^2.1.0: leven@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
@ -5230,6 +5318,10 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2" pseudomap "^1.0.2"
yallist "^2.1.2" yallist "^2.1.2"
ltgt@^2.1.2:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5"
magic-string@^0.22.4: magic-string@^0.22.4:
version "0.22.5" version "0.22.5"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e"
@ -6740,6 +6832,10 @@ proxy-addr@~2.0.3:
forwarded "~0.1.2" forwarded "~0.1.2"
ipaddr.js "1.6.0" ipaddr.js "1.6.0"
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
pseudomap@^1.0.1, pseudomap@^1.0.2: pseudomap@^1.0.1, pseudomap@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
@ -7061,7 +7157,7 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2" normalize-package-data "^2.3.2"
path-type "^1.0.0" path-type "^1.0.0"
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.3: readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.3:
version "2.3.6" version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
dependencies: dependencies:
@ -7090,6 +7186,15 @@ readline2@^1.0.1:
is-fullwidth-code-point "^1.0.0" is-fullwidth-code-point "^1.0.0"
mute-stream "0.0.5" mute-stream "0.0.5"
realistic-structured-clone@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/realistic-structured-clone/-/realistic-structured-clone-2.0.2.tgz#2f8ec225b1f9af20efc79ac96a09043704414959"
dependencies:
core-js "^2.5.3"
domexception "^1.0.1"
typeson "^5.8.2"
typeson-registry "^1.0.0-alpha.20"
realpath-native@^1.0.0: realpath-native@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.1.tgz#07f40a0cce8f8261e2e8b7ebebf5c95965d7b633" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.1.tgz#07f40a0cce8f8261e2e8b7ebebf5c95965d7b633"
@ -8182,7 +8287,7 @@ tough-cookie@~2.3.0, tough-cookie@~2.3.3:
dependencies: dependencies:
punycode "^1.4.1" punycode "^1.4.1"
tr46@^1.0.1: tr46@^1.0.0, tr46@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
dependencies: dependencies:
@ -8293,6 +8398,12 @@ type-is@~1.6.15, type-is@~1.6.16:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.18" mime-types "~2.1.18"
typedarray-to-buffer@~3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
dependencies:
is-typedarray "^1.0.0"
typedarray@^0.0.6: typedarray@^0.0.6:
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
@ -8301,6 +8412,19 @@ typescript@^2.7.2:
version "2.9.2" version "2.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
typeson-registry@^1.0.0-alpha.20:
version "1.0.0-alpha.21"
resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.21.tgz#8a4e31abb471d482aa0306ad56d35af7633a166a"
dependencies:
base64-arraybuffer-es6 "0.3.1"
typeson "5.8.2"
uuid "3.2.1"
whatwg-url "6.4.0"
typeson@5.8.2, typeson@^5.8.2:
version "5.8.2"
resolved "https://registry.yarnpkg.com/typeson/-/typeson-5.8.2.tgz#cc26f45b705760a8777fba5d3c910cc3f0e8d7dd"
ua-parser-js@^0.7.18: ua-parser-js@^0.7.18:
version "0.7.18" version "0.7.18"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"
@ -8430,6 +8554,10 @@ utils-merge@1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
uuid@3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1: uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
@ -8506,7 +8634,7 @@ webidl-conversions@^3.0.0:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
webidl-conversions@^4.0.0, webidl-conversions@^4.0.2: webidl-conversions@^4.0.0, webidl-conversions@^4.0.1, webidl-conversions@^4.0.2:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@ -8524,6 +8652,14 @@ whatwg-mimetype@^2.0.0, whatwg-mimetype@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4"
whatwg-url@6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08"
dependencies:
lodash.sortby "^4.7.0"
tr46 "^1.0.0"
webidl-conversions "^4.0.1"
whatwg-url@^4.3.0: whatwg-url@^4.3.0:
version "4.8.0" version "4.8.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0"
@ -8625,7 +8761,7 @@ xml-name-validator@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
xtend@^4.0.0, xtend@~4.0.1: xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"