diff --git a/ui/package.json b/ui/package.json index 4169f3c28..2af9e3868 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,7 +10,7 @@ }, "scripts": { "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", "test": "jest", "test:watch": "jest --watch", @@ -33,6 +33,7 @@ "@types/dygraphs": "^1.1.6", "@types/enzyme": "^3.1.9", "@types/jest": "^22.1.4", + "@types/levelup": "^0.0.30", "@types/lodash": "^4.14.104", "@types/node": "^9.4.6", "@types/papaparse": "^4.1.34", @@ -71,6 +72,7 @@ "eslint-plugin-react": "6.6.0", "eslint-watch": "^3.1.2", "express": "^4.14.0", + "fake-indexeddb": "^2.0.4", "http-proxy-middleware": "^0.18.0", "identity-obj-proxy": "^3.0.0", "jest": "^23.1.0", @@ -98,11 +100,14 @@ "d3-color": "^1.2.0", "d3-scale": "^2.1.0", "dygraphs": "2.1.0", + "encoding-down": "^5.0.4", "enzyme-adapter-react-16": "^1.1.1", "eslint-plugin-babel": "^4.1.2", "fast.js": "^0.1.1", "fixed-data-table-2": "^0.8.13", "he": "^1.1.1", + "level-js": "^3.0.0", + "levelup": "^3.1.1", "lodash": "^4.3.0", "moment": "^2.13.0", "nano-date": "^2.0.1", diff --git a/ui/parcel.js b/ui/parcel.js index b90b79ac1..bd733e31f 100644 --- a/ui/parcel.js +++ b/ui/parcel.js @@ -7,7 +7,7 @@ const port = Number(process.env.PORT || 8080) console.log(`Serving on http://localhost:${port}`) // eslint-disable-line no-console const app = express() -const bundler = new Bundler('src/index.html', { +const bundler = new Bundler(['src/index.html', 'src/worker/worker.ts'], { outDir: './build/', }) diff --git a/ui/src/dashboards/actions/index.ts b/ui/src/dashboards/actions/index.ts index 61d9ea0be..4b7eae2e4 100644 --- a/ui/src/dashboards/actions/index.ts +++ b/ui/src/dashboards/actions/index.ts @@ -660,8 +660,7 @@ export const getDashboardWithTemplatesAsync = ( let dashboard: Dashboard try { - const resp = await getDashboardAJAX(dashboardId) - dashboard = resp.data + dashboard = await getDashboardAJAX(dashboardId) } catch { dispatch(replace(`/sources/${source.id}/dashboards`)) dispatch(notify(notifyDashboardNotFound(dashboardId))) diff --git a/ui/src/dashboards/apis/index.ts b/ui/src/dashboards/apis/index.ts index e566519cc..2aaacf771 100644 --- a/ui/src/dashboards/apis/index.ts +++ b/ui/src/dashboards/apis/index.ts @@ -1,5 +1,7 @@ import AJAX from 'src/utils/ajax' +import {manager} from 'src/worker/JobManager' + import { linksFromDashboards, updateDashboardLinks, @@ -37,10 +39,8 @@ export const loadDashboardLinks = async ( export const getDashboard = async dashboardID => { try { - return await AJAX({ - method: 'GET', - url: `/chronograf/v1/dashboards/${dashboardID}`, - }) + const url = `/chronograf/v1/dashboards/${dashboardID}` + return manager.get(url) } catch (error) { console.error(error) throw error diff --git a/ui/src/dashboards/utils/tableGraph.ts b/ui/src/dashboards/utils/tableGraph.ts index 325f45e89..57c5e5334 100644 --- a/ui/src/dashboards/utils/tableGraph.ts +++ b/ui/src/dashboards/utils/tableGraph.ts @@ -1,4 +1,3 @@ -import calculateSize from 'calculate-size' import _ from 'lodash' import {fastMap, fastReduce, fastFilter} from 'src/utils/fast' @@ -16,6 +15,10 @@ import { } from 'src/types/dashboards' import {TimeSeriesValue} from 'src/types/series' +const calculateSize = (message: string): number => { + return message.length * 7 +} + interface ColumnWidths { totalWidths: number widths: {[x: string]: number} @@ -41,11 +44,7 @@ const calculateTimeColumnWidth = (timeFormat: string): number => { timeFormat = _.replace(timeFormat, 'h', '00') timeFormat = _.replace(timeFormat, 'X', '1522286058') - const {width} = calculateSize(timeFormat, { - font: '"RobotoMono", monospace', - fontSize: '13px', - fontWeight: 'bold', - }) + const width = calculateSize(timeFormat) return width + CELL_HORIZONTAL_PADDING } @@ -91,11 +90,7 @@ const updateMaxWidths = ( const currentWidth = useTimeWidth ? timeFormatWidth - : calculateSize(colValue.toString().trim(), { - font: isLabel ? '"Roboto"' : '"RobotoMono", monospace', - fontSize: '12px', - fontWeight: '500', - }).width + CELL_HORIZONTAL_PADDING + : calculateSize(colValue.toString().trim()) + CELL_HORIZONTAL_PADDING const {widths: Widths} = maxColumnWidths const maxWidth = _.get(Widths, `${columnLabel}`, 0) diff --git a/ui/src/data_explorer/apis/index.ts b/ui/src/data_explorer/apis/index.ts index 4b627549b..2a79b5600 100644 --- a/ui/src/data_explorer/apis/index.ts +++ b/ui/src/data_explorer/apis/index.ts @@ -47,9 +47,9 @@ export const getDataForCSV = ( query: queryString, }) - const {data} = timeSeriesToTableGraph([{response}]) + const {data} = await timeSeriesToTableGraph([{response}]) const name = csvName(query.queryConfig) - download(dataToCSV(data), `${name}.csv`, 'text/plain') + download(dataToCSV(data as any), `${name}.csv`, 'text/plain') } catch (error) { errorThrown(error, 'Unable to download .csv file') console.error(error) diff --git a/ui/src/kapacitor/components/RuleGraph.tsx b/ui/src/kapacitor/components/RuleGraph.tsx index 2153b1696..8d80307d8 100644 --- a/ui/src/kapacitor/components/RuleGraph.tsx +++ b/ui/src/kapacitor/components/RuleGraph.tsx @@ -4,17 +4,12 @@ import {connect} from 'react-redux' import TimeSeries from 'src/shared/components/time_series/TimeSeries' // Components -import Dygraph from 'src/shared/components/Dygraph' import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown' +import RuleGraphDygraph from 'src/kapacitor/components/RuleGraphDygraph' // Utils import buildInfluxQLQuery from 'src/utils/influxql' 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 import {Source, AlertRule, QueryConfig, Query, TimeRange} from 'src/types' @@ -63,25 +58,14 @@ class RuleGraph extends PureComponent { queries={this.queries} > {data => { - const {labels, timeSeries, dygraphSeries} = timeSeriesToDygraph( - data.timeSeries, - 'rule-builder' - ) return ( - ) }} @@ -91,30 +75,6 @@ class RuleGraph extends PureComponent { ) } - 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 { return {height: '100%'} } diff --git a/ui/src/kapacitor/components/RuleGraphDygraph.tsx b/ui/src/kapacitor/components/RuleGraphDygraph.tsx new file mode 100644 index 000000000..cef76c3d5 --- /dev/null +++ b/ui/src/kapacitor/components/RuleGraphDygraph.tsx @@ -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 { + 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 ( + + ) + } + + 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 diff --git a/ui/src/shared/components/LayoutCell.tsx b/ui/src/shared/components/LayoutCell.tsx index ed776662f..95996aa71 100644 --- a/ui/src/shared/components/LayoutCell.tsx +++ b/ui/src/shared/components/LayoutCell.tsx @@ -126,13 +126,13 @@ export default class LayoutCell extends Component { onSummonOverlayTechnologies(cell) } - private handleCSVDownload = (): void => { + private handleCSVDownload = async (): Promise => { const {cellData, cell} = this.props const joinedName = cell.name.split(' ').join('_') - const {data} = timeSeriesToTableGraph(cellData) + const {data} = await timeSeriesToTableGraph(cellData) try { - download(dataToCSV(data), `${joinedName}.csv`, 'text/plain') + download(dataToCSV(data as any), `${joinedName}.csv`, 'text/plain') } catch (error) { notify(notifyCSVDownloadFailed()) console.error(error) diff --git a/ui/src/shared/components/LineGraph.tsx b/ui/src/shared/components/LineGraph.tsx index f2778cddc..f4cfb3451 100644 --- a/ui/src/shared/components/LineGraph.tsx +++ b/ui/src/shared/components/LineGraph.tsx @@ -14,12 +14,13 @@ import { timeSeriesToDygraph, TimeSeriesToDyGraphReturnType, } from 'src/utils/timeSeriesTransformers' +import {manager} from 'src/worker/JobManager' // Types import {ColorString} from 'src/types/colors' import {DecimalPlaces} from 'src/types/dashboards' 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' interface Props { @@ -41,34 +42,50 @@ interface Props { type LineGraphProps = Props & RouteComponentProps +interface State { + timeSeries?: TimeSeriesToDyGraphReturnType +} + @ErrorHandlingWith(InvalidData) -class LineGraph extends PureComponent { +class LineGraph extends PureComponent { public static defaultProps: Partial = { staticLegend: false, } + private isComponentMounted: boolean = false private isValidData: boolean = true - private timeSeries: TimeSeriesToDyGraphReturnType - public componentWillMount() { - const {data} = this.props - this.parseTimeSeries(data) + constructor(props: LineGraphProps) { + super(props) + + 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 - this.timeSeries = timeSeriesToDygraph(data, location.pathname) - const timeSeries = _.get(this.timeSeries, 'timeSeries', []) - this.isValidData = this.validateTimeSeries(timeSeries) + const timeSeries = await timeSeriesToDygraph(data, location.pathname) + const innerTimeSeries = _.get(timeSeries, 'timeSeries', []) + this.isValidData = await manager.validateDygraphData(innerTimeSeries) + if (!this.isComponentMounted) { + return + } + + this.setState({timeSeries}) } - public componentWillUpdate(nextProps) { - const {data, activeQueryIndex} = this.props - if ( - data !== nextProps.data || - activeQueryIndex !== nextProps.activeQueryIndex - ) { + public componentWillReceiveProps(nextProps: LineGraphProps) { + if (nextProps.loading === RemoteDataState.Done) { this.parseTimeSeries(nextProps.data) } } @@ -94,7 +111,11 @@ class LineGraph extends PureComponent { handleSetHoverTime, } = this.props - const {labels, timeSeries, dygraphSeries} = this.timeSeries + if (!this.state.timeSeries) { + return

+ } + + const {labels, timeSeries, dygraphSeries} = this.state.timeSeries const options = { rightGap: 0, @@ -147,16 +168,6 @@ class LineGraph extends PureComponent { ) } - 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 { const {type} = this.props diff --git a/ui/src/shared/components/RefreshingGraph.tsx b/ui/src/shared/components/RefreshingGraph.tsx index 6ba3581ae..1fa12d2a4 100644 --- a/ui/src/shared/components/RefreshingGraph.tsx +++ b/ui/src/shared/components/RefreshingGraph.tsx @@ -85,6 +85,7 @@ class RefreshingGraph extends PureComponent { return ( { private gridContainer: HTMLDivElement private multiGrid?: MultiGrid + private isComponentMounted: boolean = false constructor(props: Props) { super(props) @@ -167,6 +170,7 @@ class TableGraph extends Component { } public componentWillUnmount() { + this.isComponentMounted = false window.removeEventListener('resize', this.handleResize) } @@ -179,7 +183,8 @@ class TableGraph extends Component { ) } - public componentDidMount() { + public async componentDidMount() { + this.isComponentMounted = true window.addEventListener('resize', this.handleResize) const sortField: string = _.get( @@ -196,13 +201,17 @@ class TableGraph extends Component { fieldOptions, decimalPlaces, } = this.props - const result = timeSeriesToTableGraph(data) + const result = await timeSeriesToTableGraph(data) const sortedLabels = result.sortedLabels const computedFieldOptions = computeFieldOptions(fieldOptions, sortedLabels) this.handleUpdateFieldOptions(computedFieldOptions) - const {transformedData, sortedTimeVals, columnWidths} = transformTableData( + const { + transformedData, + sortedTimeVals, + columnWidths, + } = await manager.tableTransform( result.data, sort, computedFieldOptions, @@ -234,12 +243,12 @@ class TableGraph extends Component { ) } - public componentWillReceiveProps(nextProps: Props) { + public async componentWillReceiveProps(nextProps: Props) { const {sort} = this.state let result = {} if (this.hasDataChanged(nextProps.data)) { - result = timeSeriesToTableGraph(nextProps.data) + result = await timeSeriesToTableGraph(nextProps.data) } const data = _.get(result, 'data', this.state.data) @@ -278,7 +287,7 @@ class TableGraph extends Component { transformedData, sortedTimeVals, columnWidths, - } = transformTableData( + } = await manager.tableTransform( data, sort, computedFieldOptions, @@ -296,6 +305,10 @@ class TableGraph extends Component { isTimeVisible = _.get(timeField, 'visible', this.state.isTimeVisible) } + if (!this.isComponentMounted) { + return + } + this.setState({ data, sortedLabels, diff --git a/ui/src/shared/components/time_series/TimeSeries.tsx b/ui/src/shared/components/time_series/TimeSeries.tsx index a3449f759..590f166ee 100644 --- a/ui/src/shared/components/time_series/TimeSeries.tsx +++ b/ui/src/shared/components/time_series/TimeSeries.tsx @@ -7,7 +7,14 @@ import uuid from 'uuid' import {fetchTimeSeries} from 'src/shared/apis/query' // 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 {GrabDataForDownloadHandler} from 'src/types/layout' @@ -23,6 +30,7 @@ interface RenderProps { interface Props { source: Source + cellType?: CellType queries: Query[] timeRange: TimeRange children: (r: RenderProps) => JSX.Element @@ -46,6 +54,7 @@ const GraphLoadingDots = () => (
) + class TimeSeries extends Component { public static defaultProps = { inView: true, @@ -53,25 +62,23 @@ class TimeSeries extends Component { } public static getDerivedStateFromProps(props: Props, state: State) { - let {isFirstFetch, timeRange} = state - const oldUpper = _.get(state, 'timeRange.upper', null) const oldLower = _.get(state, 'timeRange.lower', null) const newUpper = _.get(props, 'timeRange.upper', null) const newLower = _.get(props, 'timeRange.lower', null) if (oldUpper !== newUpper || oldLower !== newLower) { - isFirstFetch = true - timeRange = props.timeRange + return { + isFirstFetch: true, + timeRange: props.timeRange, + } } - return { - isFirstFetch, - timeRange, - } + return null } private latestUUID: string = uuid.v1() + private isComponentMounted: boolean = false constructor(props: Props) { super(props) @@ -83,12 +90,32 @@ class TimeSeries extends Component { } } + 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() { + this.isComponentMounted = true this.executeQueries() AutoRefresh.subscribe(this.executeQueries) } public componentWillUnmount() { + this.isComponentMounted = false AutoRefresh.unsubscribe(this.executeQueries) } @@ -105,7 +132,7 @@ class TimeSeries extends Component { this.setState({loading: RemoteDataState.Loading}, () => { window.setTimeout(() => { resolve() - }, 10) + }, 0) }) }) } @@ -153,21 +180,28 @@ class TimeSeries extends Component { response, })) + if (!this.isComponentMounted) { + return + } + this.setState({ timeSeries: newSeries, loading: RemoteDataState.Done, + isFirstFetch: false, }) if (grabDataForDownload) { grabDataForDownload(newSeries) } } catch (err) { + if (!this.isComponentMounted) { + return + } + this.setState({ timeSeries: [], loading: RemoteDataState.Error, }) - } finally { - this.setState({isFirstFetch: false}) } } diff --git a/ui/src/utils/ajax.ts b/ui/src/utils/ajax.ts index 0281e19dc..0d70c32ae 100644 --- a/ui/src/utils/ajax.ts +++ b/ui/src/utils/ajax.ts @@ -94,15 +94,9 @@ async function AJAX( excludeBasepath = false ): Promise<(T | T & {links: object}) | AxiosResponse> { 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) - if (resource) { + if (resource && links) { url = id ? addBasepath(`${links[resource]}/${id}`, excludeBasepath) : addBasepath(`${links[resource]}`, excludeBasepath) diff --git a/ui/src/utils/basepath.ts b/ui/src/utils/basepath.ts index ec8aca2ae..211b8028c 100644 --- a/ui/src/utils/basepath.ts +++ b/ui/src/utils/basepath.ts @@ -2,6 +2,10 @@ import {getRootNode} from 'src/utils/nodes' export const getBasepath = () => { const rootNode = getRootNode() + if (!rootNode) { + return '' + } + return rootNode.getAttribute('data-basepath') || '' } diff --git a/ui/src/utils/queryUrlGenerator.ts b/ui/src/utils/queryUrlGenerator.ts index 61040eefe..cfd46f98c 100644 --- a/ui/src/utils/queryUrlGenerator.ts +++ b/ui/src/utils/queryUrlGenerator.ts @@ -1,4 +1,4 @@ -import AJAX from 'src/utils/ajax' +import {manager} from 'src/worker/JobManager' interface ProxyQuery { source: string @@ -16,16 +16,8 @@ export async function proxy({ uuid, }: ProxyQuery) { try { - return await AJAX({ - method: 'POST', - url: source, - data: { - query, - db, - rp, - uuid, - }, - }) + const result = await manager.proxy(source, query, db, rp, uuid) + return result as T } catch (error) { console.error(error) throw error diff --git a/ui/src/utils/timeSeriesTransformers.ts b/ui/src/utils/timeSeriesTransformers.ts index 4e636b530..5bee9040d 100644 --- a/ui/src/utils/timeSeriesTransformers.ts +++ b/ui/src/utils/timeSeriesTransformers.ts @@ -1,11 +1,7 @@ -import {fastMap, fastReduce} from 'src/utils/fast' -import {groupByTimeSeriesTransform} from 'src/utils/groupByTimeSeriesTransform' +import {fastMap} from 'src/utils/fast' +import {manager} from 'src/worker/JobManager' -import { - TimeSeriesServerResponse, - TimeSeries, - TimeSeriesValue, -} from 'src/types/series' +import {TimeSeriesServerResponse, TimeSeriesValue} from 'src/types/series' import {DygraphSeries, DygraphValue} from 'src/types' interface Label { @@ -25,64 +21,22 @@ interface TimeSeriesToTableGraphReturnType { sortedLabels: Label[] } -export const timeSeriesToDygraph = ( +export const timeSeriesToDygraph = async ( raw: TimeSeriesServerResponse[], pathname: string = '' -): TimeSeriesToDyGraphReturnType => { - const isTable = false - const isInDataExplorer = pathname.includes('data-explorer') - const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform( - raw, - isTable +): Promise => { + const result = await manager.timeSeriesToDygraph(raw, pathname) + const {timeSeries} = result + const newTimeSeries = fastMap( + timeSeries, + ([time, ...values]) => [new Date(time), ...values] ) - const labels = [ - 'time', - ...fastMap(sortedLabels, ({label}) => label), - ] - - const timeSeries = fastMap( - sortedTimeSeries, - ({time, values}) => [new Date(time), ...values] - ) - - const dygraphSeries = fastReduce( - sortedLabels, - (acc, {label, responseIndex}) => { - if (!isInDataExplorer) { - acc[label] = { - axis: responseIndex === 0 ? 'y' : 'y2', - } - } - return acc - }, - {} - ) - - return {labels, timeSeries, dygraphSeries} + return {...result, timeSeries: newTimeSeries} } -export const timeSeriesToTableGraph = ( +export const timeSeriesToTableGraph = async ( raw: TimeSeriesServerResponse[] -): TimeSeriesToTableGraphReturnType => { - const isTable = true - const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform( - raw, - isTable - ) - - const labels = [ - 'time', - ...fastMap(sortedLabels, ({label}) => label), - ] - - const tableData = fastMap( - sortedTimeSeries, - ({time, values}) => [time, ...values] - ) - const data = tableData.length ? [labels, ...tableData] : [[]] - return { - data, - sortedLabels, - } +): Promise => { + return await manager.timeSeriesToTableGraph(raw) } diff --git a/ui/src/worker/Database.ts b/ui/src/worker/Database.ts new file mode 100644 index 000000000..4e528c241 --- /dev/null +++ b/ui/src/worker/Database.ts @@ -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'})) diff --git a/ui/src/worker/Deferred.ts b/ui/src/worker/Deferred.ts new file mode 100644 index 000000000..1aa0af60a --- /dev/null +++ b/ui/src/worker/Deferred.ts @@ -0,0 +1,14 @@ +class Deferred { + public promise: Promise + 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 diff --git a/ui/src/worker/JobManager.ts b/ui/src/worker/JobManager.ts new file mode 100644 index 000000000..af50d6d9a --- /dev/null +++ b/ui/src/worker/JobManager.ts @@ -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 { + const payload = { + data, + sort, + fieldOptions, + tableOptions, + timeFormat, + decimalPlaces, + } + + return this.publishDBJob('TABLETRANSFORM', payload) + } + + public proxy(url, query, db, rp, uuid): Promise { + if (getBasepath() !== '') { + url = `${getBasepath()}${url}` + } + return this.publishJob('PROXY', {url, query, db, rp, uuid}) + } + + public get(url: string): Promise { + if (getBasepath() !== '') { + url = `${getBasepath()}${url}` + } + return this.publishJob('GET', {url}) + } + + public timeSeriesToTableGraph = ( + raw: TimeSeriesServerResponse[] + ): Promise => { + return this.publishDBJob('TSTOTABLEGRAPH', {raw}) + } + + public timeSeriesToDygraph = ( + raw: TimeSeriesServerResponse[], + pathname: string = '' + ): Promise => { + 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() diff --git a/ui/src/worker/jobs/get.ts b/ui/src/worker/jobs/get.ts new file mode 100644 index 000000000..1bc16ce74 --- /dev/null +++ b/ui/src/worker/jobs/get.ts @@ -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 diff --git a/ui/src/worker/jobs/proxy.ts b/ui/src/worker/jobs/proxy.ts new file mode 100644 index 000000000..bad64fc2d --- /dev/null +++ b/ui/src/worker/jobs/proxy.ts @@ -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 diff --git a/ui/src/worker/jobs/tableTransform.ts b/ui/src/worker/jobs/tableTransform.ts new file mode 100644 index 000000000..8cb6f56ec --- /dev/null +++ b/ui/src/worker/jobs/tableTransform.ts @@ -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 diff --git a/ui/src/worker/jobs/timeSeriesToDygraph.ts b/ui/src/worker/jobs/timeSeriesToDygraph.ts new file mode 100644 index 000000000..83168227c --- /dev/null +++ b/ui/src/worker/jobs/timeSeriesToDygraph.ts @@ -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(sortedLabels, ({label}) => label), + ] + + const timeSeries = fastMap( + sortedTimeSeries, + ({time, values}) => [new Date(time), ...values] + ) + + const dygraphSeries = fastReduce( + 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 diff --git a/ui/src/worker/jobs/timeSeriesToTableGraph.ts b/ui/src/worker/jobs/timeSeriesToTableGraph.ts new file mode 100644 index 000000000..f8df807d6 --- /dev/null +++ b/ui/src/worker/jobs/timeSeriesToTableGraph.ts @@ -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(sortedLabels, ({label}) => label), + ] + + const tableData = fastMap( + 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 diff --git a/ui/src/worker/jobs/validateDygraphData.ts b/ui/src/worker/jobs/validateDygraphData.ts new file mode 100644 index 000000000..0d5a1569f --- /dev/null +++ b/ui/src/worker/jobs/validateDygraphData.ts @@ -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 => { + 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 diff --git a/ui/src/worker/types/index.ts b/ui/src/worker/types/index.ts new file mode 100644 index 000000000..830a1a786 --- /dev/null +++ b/ui/src/worker/types/index.ts @@ -0,0 +1,5 @@ +export interface Message { + id: string + type: string + payload: any +} diff --git a/ui/src/worker/utils/index.ts b/ui/src/worker/utils/index.ts new file mode 100644 index 000000000..7f27a4d10 --- /dev/null +++ b/ui/src/worker/utils/index.ts @@ -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 => { + await DB.del(msg.id) +} + +export const fetchData = async (msg: Message): Promise => { + 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', + }) +} diff --git a/ui/src/worker/worker.ts b/ui/src/worker/worker.ts new file mode 100644 index 000000000..04ca525ee --- /dev/null +++ b/ui/src/worker/worker.ts @@ -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 + +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) + } +} diff --git a/ui/test/setup.js b/ui/test/setup.js index 0b3506c4d..eb50a2fe7 100644 --- a/ui/test/setup.js +++ b/ui/test/setup.js @@ -3,6 +3,9 @@ import React from 'react' import Adapter from 'enzyme-adapter-react-16' +window.indexedDB = require('fake-indexeddb') +window.Worker = function() {} + configure({ adapter: new Adapter(), }) diff --git a/ui/test/utils/timeSeriesTransformers.test.ts b/ui/test/utils/timeSeriesTransformers.test.ts index 33ab77094..36ecb790f 100644 --- a/ui/test/utils/timeSeriesTransformers.test.ts +++ b/ui/test/utils/timeSeriesTransformers.test.ts @@ -1,7 +1,5 @@ -import { - timeSeriesToDygraph, - timeSeriesToTableGraph, -} from 'src/utils/timeSeriesTransformers' +import {timeSeriesToDygraphWork as timeSeriesToDygraph} from 'src/worker/jobs/timeSeriesToDygraph' +import {timeSeriesToTableGraphWork as timeSeriesToTableGraph} from 'src/worker/jobs/timeSeriesToTableGraph' import { filterTableColumns, diff --git a/ui/tsconfig.json b/ui/tsconfig.json index fd2634dfe..d1fb7955e 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -15,7 +15,7 @@ "target": "es6", "module": "es2015", "moduleResolution": "node", - "lib": ["es6", "es2017", "dom"], + "lib": ["es6", "es2017", "dom", "webworker"], "skipLibCheck": true, "isolatedModules": false, "jsx": "react", diff --git a/ui/yarn.lock b/ui/yarn.lock index 69775f2bd..55b60a025 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -78,6 +78,12 @@ version "22.2.3" 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": version "4.14.111" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.111.tgz#d926250baa9526c0ffe85914dd10363068e7893a" @@ -159,6 +165,12 @@ abbrev@1: version "1.1.1" 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: version "1.3.5" 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" 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: version "1.3.0" 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" 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" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" @@ -2452,6 +2468,13 @@ defaults@^1.0.3: dependencies: 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: version "1.1.2" 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" 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" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" dependencies: @@ -2702,6 +2725,16 @@ encodeurl@~1.0.2: version "1.0.2" 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: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -2758,6 +2791,12 @@ enzyme@^3.3.0: raf "^3.4.0" 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: version "1.3.2" 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" 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: version "2.1.0" 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" 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: version "1.0.0" 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" 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" 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" 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: version "2.1.0" 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" 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: version "0.22.5" 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" 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: version "1.0.2" 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" 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" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -7090,6 +7186,15 @@ readline2@^1.0.1: is-fullwidth-code-point "^1.0.0" 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: version "1.0.1" 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: punycode "^1.4.1" -tr46@^1.0.1: +tr46@^1.0.0, tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" dependencies: @@ -8293,6 +8398,12 @@ type-is@~1.6.15, type-is@~1.6.16: media-typer "0.3.0" 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: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -8301,6 +8412,19 @@ typescript@^2.7.2: version "2.9.2" 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: version "0.7.18" 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" 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: version "3.3.2" 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" 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" 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" 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: version "4.8.0" 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" 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" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"