[Maps][File upload] Fix geojson upload diacritic handling (#83122)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Aaron Caldwell 2020-12-15 17:31:49 -07:00 committed by GitHub
parent 28c8cfbff4
commit 767a052521
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 358 additions and 246 deletions

View file

@ -140,6 +140,8 @@
"@kbn/ui-framework": "link:packages/kbn-ui-framework",
"@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps",
"@kbn/utils": "link:packages/kbn-utils",
"@loaders.gl/core": "^2.3.1",
"@loaders.gl/json": "^2.3.1",
"@slack/webhook": "^5.0.0",
"@storybook/addons": "^6.0.16",
"@turf/circle": "6.0.1",
@ -375,6 +377,7 @@
"@kbn/test": "link:packages/kbn-test",
"@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector",
"@kbn/utility-types": "link:packages/kbn-utility-types",
"@loaders.gl/polyfills": "^2.3.5",
"@mapbox/geojson-rewind": "^0.5.0",
"@mapbox/mapbox-gl-draw": "^1.2.0",
"@mapbox/mapbox-gl-rtl-text": "^0.2.3",

View file

@ -10,7 +10,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { parseFile } from '../util/file_parser';
import { MAX_FILE_SIZE } from '../../common/constants/file_import';
import _ from 'lodash';
const ACCEPTABLE_FILETYPES = ['json', 'geojson'];
const acceptedFileTypeString = ACCEPTABLE_FILETYPES.map((type) => `.${type}`).join(',');
@ -134,14 +133,12 @@ export class JsonIndexFilePicker extends Component {
return fileNameOnly.toLowerCase();
}
// It's necessary to throttle progress. Updates that are too frequent cause
// issues (update failure) in the nested progress component
setFileProgress = _.debounce(({ featuresProcessed, bytesProcessed, totalBytes }) => {
setFileProgress = ({ featuresProcessed, bytesProcessed, totalBytes }) => {
const percentageProcessed = parseInt((100 * bytesProcessed) / totalBytes);
if (this.getFileParseActive()) {
this.setState({ featuresProcessed, percentageProcessed });
}
}, 150);
};
async _parseFile(file) {
const { currentFileTracker } = this.state;

View file

@ -7,111 +7,106 @@
import _ from 'lodash';
import { geoJsonCleanAndValidate } from './geo_json_clean_and_validate';
import { i18n } from '@kbn/i18n';
import { PatternReader } from './pattern_reader';
import { JSONLoader } from '@loaders.gl/json';
import { loadInBatches } from '@loaders.gl/core';
// In local testing, performance improvements leveled off around this size
export const FILE_BUFFER = 1024000;
const readSlice = (fileReader, file, start, stop) => {
const blob = file.slice(start, stop);
fileReader.readAsBinaryString(blob);
};
let prevFileReader;
let prevPatternReader;
export const fileHandler = async ({
file,
setFileProgress,
cleanAndValidate,
getFileParseActive,
fileReader = new FileReader(),
}) => {
if (!file) {
return Promise.reject(
new Error(
i18n.translate('xpack.fileUpload.fileParser.noFileProvided', {
defaultMessage: 'Error, no file provided',
})
)
);
}
// Halt any previous file reading & pattern checking activity
if (prevFileReader) {
prevFileReader.abort();
}
if (prevPatternReader) {
prevPatternReader.abortStream();
}
// Set up feature tracking
let featuresProcessed = 0;
const onFeatureRead = (feature) => {
// TODO: Add handling and tracking for cleanAndValidate fails
featuresProcessed++;
return cleanAndValidate(feature);
};
let start;
let stop = FILE_BUFFER;
prevFileReader = fileReader;
const filePromise = new Promise((resolve, reject) => {
const onStreamComplete = (fileResults) => {
if (!featuresProcessed) {
reject(
new Error(
i18n.translate('xpack.fileUpload.fileParser.noFeaturesDetected', {
defaultMessage: 'Error, no features detected',
})
)
);
} else {
resolve(fileResults);
}
};
const patternReader = new PatternReader({ onFeatureDetect: onFeatureRead, onStreamComplete });
prevPatternReader = patternReader;
fileReader.onloadend = ({ target: { readyState, result } }) => {
if (readyState === FileReader.DONE) {
if (!getFileParseActive() || !result) {
fileReader.abort();
patternReader.abortStream();
resolve(null);
return;
}
setFileProgress({
featuresProcessed,
bytesProcessed: stop || file.size,
totalBytes: file.size,
});
patternReader.writeDataToPatternStream(result);
if (!stop) {
return;
}
start = stop;
const newStop = stop + FILE_BUFFER;
// Check EOF
stop = newStop > file.size ? undefined : newStop;
readSlice(fileReader, file, start, stop);
}
};
fileReader.onerror = () => {
fileReader.abort();
patternReader.abortStream();
const filePromise = new Promise(async (resolve, reject) => {
if (!file) {
reject(
new Error(
i18n.translate('xpack.fileUpload.fileParser.errorReadingFile', {
defaultMessage: 'Error reading file',
i18n.translate('xpack.fileUpload.fileParser.noFileProvided', {
defaultMessage: 'Error, no file provided',
})
)
);
};
return;
}
const batches = await loadInBatches(file, JSONLoader, {
json: {
jsonpaths: ['$.features'],
_rootObjectBatches: true,
},
});
let featuresProcessed = 0;
const features = [];
const errors = [];
let boolGeometryErrs = false;
let parsedGeojson;
for await (const batch of batches) {
if (getFileParseActive()) {
switch (batch.batchType) {
case 'root-object-batch-complete':
if (!getFileParseActive()) {
resolve(null);
return;
}
if (featuresProcessed) {
parsedGeojson = { ...batch.container, features };
} else {
// Handle single feature geoJson
const cleanedSingleFeature = cleanAndValidate(batch.container);
if (cleanedSingleFeature.geometry && cleanedSingleFeature.geometry.type) {
parsedGeojson = cleanedSingleFeature;
featuresProcessed++;
}
}
break;
default:
for (const feature of batch.data) {
if (!feature.geometry || !feature.geometry.type) {
if (!boolGeometryErrs) {
boolGeometryErrs = true;
errors.push(
new Error(
i18n.translate('xpack.fileUpload.fileParser.featuresOmitted', {
defaultMessage: 'Some features without geometry omitted',
})
)
);
}
} else {
const cleanFeature = cleanAndValidate(feature);
features.push(cleanFeature);
featuresProcessed++;
}
}
}
setFileProgress({
featuresProcessed,
bytesProcessed: batch.bytesUsed,
totalBytes: file.size,
});
} else {
break;
}
}
if (!featuresProcessed && getFileParseActive()) {
reject(
new Error(
i18n.translate('xpack.fileUpload.fileParser.noFeaturesDetected', {
defaultMessage: 'Error, no features detected',
})
)
);
} else if (!getFileParseActive()) {
resolve(null);
} else {
resolve({
errors,
parsedGeojson,
});
}
});
readSlice(fileReader, file, start, stop);
return filePromise;
};

View file

@ -5,32 +5,11 @@
*/
import { fileHandler } from './file_parser';
jest.mock('./pattern_reader', () => ({}));
import '@loaders.gl/polyfills';
const cleanAndValidate = jest.fn((a) => a);
const setFileProgress = jest.fn((a) => a);
const getFileReader = () => {
const fileReader = {
abort: jest.fn(),
};
fileReader.readAsBinaryString = jest.fn((binaryString = '123') =>
fileReader.onloadend({ target: { readyState: FileReader.DONE, result: binaryString } })
);
return fileReader;
};
const getPatternReader = () => {
const patternReader = {
writeDataToPatternStream: jest.fn(),
abortStream: jest.fn(),
};
require('./pattern_reader').PatternReader = function () {
this.writeDataToPatternStream = () => patternReader.writeDataToPatternStream();
this.abortStream = () => patternReader.abortStream();
};
return patternReader;
};
const testJson = {
type: 'Feature',
geometry: {
@ -63,13 +42,13 @@ describe('parse file', () => {
});
it('should reject and throw error if no file provided', async () => {
expect(fileHandler(null)).rejects.toThrow();
await fileHandler({ file: null }).catch((e) => {
expect(e.message).toMatch('Error, no file provided');
});
});
it('should abort and resolve to null if file parse cancelled', async () => {
const fileRef = getFileRef();
const cancelledActionFileReader = getFileReader();
const cancelledActionPatternReader = getPatternReader();
// Cancel file parse
const getFileParseActive = getFileParseActiveFactory(false);
@ -79,53 +58,61 @@ describe('parse file', () => {
setFileProgress,
cleanAndValidate,
getFileParseActive,
fileReader: cancelledActionFileReader,
});
expect(fileHandlerResult).toBeNull();
expect(cancelledActionFileReader.abort.mock.calls.length).toEqual(1);
expect(cancelledActionPatternReader.abortStream.mock.calls.length).toEqual(1);
});
it('should abort on file reader error', () => {
it('should normally read single feature valid data', async () => {
const fileRef = getFileRef();
const fileReaderWithErrorCall = getFileReader();
const patternReaderWithErrorCall = getPatternReader();
// Trigger on error on read
fileReaderWithErrorCall.readAsBinaryString = () => fileReaderWithErrorCall.onerror();
const getFileParseActive = getFileParseActiveFactory();
expect(
fileHandler({
file: fileRef,
setFileProgress,
cleanAndValidate,
getFileParseActive,
fileReader: fileReaderWithErrorCall,
})
).rejects.toThrow();
expect(fileReaderWithErrorCall.abort.mock.calls.length).toEqual(1);
expect(patternReaderWithErrorCall.abortStream.mock.calls.length).toEqual(1);
});
// Expect 2 calls, one reads file, next is 'undefined' to
// both fileReader and patternReader
it('should normally read binary and emit to patternReader for valid data', async () => {
const fileRef = getFileRef();
const fileReaderForValidFile = getFileReader();
const patternReaderForValidFile = getPatternReader();
const getFileParseActive = getFileParseActiveFactory();
fileHandler({
const { errors } = await fileHandler({
file: fileRef,
setFileProgress,
cleanAndValidate,
cleanAndValidate: (x) => x,
getFileParseActive,
fileReader: fileReaderForValidFile,
});
expect(fileReaderForValidFile.readAsBinaryString.mock.calls.length).toEqual(2);
expect(patternReaderForValidFile.writeDataToPatternStream.mock.calls.length).toEqual(2);
expect(setFileProgress.mock.calls.length).toEqual(1);
expect(errors.length).toEqual(0);
});
it('should normally read a valid single feature file', async () => {
const testSinglePointJson = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [30, 10],
},
properties: {
name: 'Point island',
},
};
const fileRef = getFileRef(testSinglePointJson);
const getFileParseActive = getFileParseActiveFactory();
const { errors } = await fileHandler({
file: fileRef,
setFileProgress,
cleanAndValidate: (x) => x,
getFileParseActive,
});
expect(setFileProgress.mock.calls.length).toEqual(1);
expect(errors.length).toEqual(0);
});
it('should throw if no valid features', async () => {
const fileRef = getFileRef();
const getFileParseActive = getFileParseActiveFactory();
await fileHandler({
file: fileRef,
setFileProgress,
cleanAndValidate: () => ({ not: 'the correct content' }), // Simulate clean and validate fail
getFileParseActive,
}).catch((e) => {
expect(e.message).toMatch('Error, no features detected');
});
});
});

View file

@ -11,14 +11,14 @@ const geoJSONReader = new jsts.io.GeoJSONReader();
const geoJSONWriter = new jsts.io.GeoJSONWriter();
export function geoJsonCleanAndValidate(feature) {
const geometryReadResult = geoJSONReader.read(feature);
const cleanedGeometry = cleanGeometry(geometryReadResult);
// For now, return the feature unmodified
// TODO: Consider more robust UI feedback and general handling
// for features that fail cleaning and/or validation
if (!cleanedGeometry) {
let cleanedGeometry;
// Attempts to clean geometry. If this fails, don't generate errors at this
// point, these will be handled more accurately on write to ES with feedback
// given to user
try {
const geometryReadResult = geoJSONReader.read(feature);
cleanedGeometry = cleanGeometry(geometryReadResult);
} catch (e) {
return feature;
}

View file

@ -134,18 +134,13 @@ describe('geo_json_clean_and_validate', () => {
expect(clockwiseGeoJson).toEqual(clockwiseGeoJson2);
});
it('error out on invalid object', () => {
it('return same object if not cleanable', () => {
const invalidGeoJson = {
type: 'notMyType',
geometry: 'shmeometry',
};
const notEvenCloseToGeoJson = [1, 2, 3, 4];
const badObjectPassed = () => geoJsonCleanAndValidate(invalidGeoJson);
expect(badObjectPassed).toThrow();
const worseObjectPassed = () => geoJsonCleanAndValidate(notEvenCloseToGeoJson);
expect(worseObjectPassed).toThrow();
const uncleanableJson = geoJsonCleanAndValidate(invalidGeoJson);
expect(uncleanableJson).toEqual(invalidGeoJson);
});
});

View file

@ -1,63 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import oboe from 'oboe';
export class PatternReader {
constructor({ onFeatureDetect, onStreamComplete }) {
this._oboeStream = oboe();
this._registerFeaturePatternHandler(onFeatureDetect);
this._registerStreamCompleteHandler(onStreamComplete);
this._errors = [];
}
getErrors() {
return this._errors;
}
_registerFeaturePatternHandler(featurePatternCallback) {
this._oboeStream.node({
'features.*': (feature) => {
if (!feature.geometry || !feature.geometry.type) {
// Only add this error once
// TODO: Give feedback on which features failed
if (!this._errors.length) {
this._errors.push(
new Error(
i18n.translate('xpack.fileUpload.patternReader.featuresOmitted', {
defaultMessage: 'Some features without geometry omitted',
})
)
);
}
return oboe.drop;
}
return featurePatternCallback(feature);
},
// Handle single feature files
'!.geometry': (geom, path, ancestors) => {
const feature = ancestors[0];
const { geometry } = featurePatternCallback(feature);
return geometry;
},
});
}
_registerStreamCompleteHandler(streamCompleteCallback) {
this._oboeStream.done((parsedGeojson) => {
streamCompleteCallback({ parsedGeojson, errors: this.getErrors() });
});
}
writeDataToPatternStream(data) {
this._oboeStream.emit('data', data);
}
abortStream() {
this._oboeStream.abort();
}
}

View file

@ -7045,7 +7045,6 @@
"xpack.features.savedObjectsManagementFeatureName": "保存されたオブジェクトの管理",
"xpack.features.visualizeFeatureName": "可視化",
"xpack.fileUpload.enterIndexName": "インデックス名を入力",
"xpack.fileUpload.fileParser.errorReadingFile": "ファイルの読み込み中にエラーが発生しました",
"xpack.fileUpload.fileParser.noFeaturesDetected": "エラー、機能が検出されませんでした",
"xpack.fileUpload.fileParser.noFileProvided": "エラー、ファイルが提供されていません",
"xpack.fileUpload.fileParser.transformDetailsNotDefined": "{transformDetails}のインデックスオプションが定義されていません",
@ -7096,7 +7095,6 @@
"xpack.fileUpload.jsonUploadAndParse.indexPatternError": "インデックスパターンエラー",
"xpack.fileUpload.jsonUploadAndParse.writingToIndex": "インデックスに書き込み中",
"xpack.fileUpload.noIndexSuppliedErrorMessage": "インデックスが指定されていません。",
"xpack.fileUpload.patternReader.featuresOmitted": "ジオメトリのない一部の機能は省略されました",
"xpack.fleet.agentBulkActions.agentsSelected": "{count, plural, one {#個のエージェント} other {#個のエージェント}}が選択されました",
"xpack.fleet.agentBulkActions.clearSelection": "選択した項目をクリア",
"xpack.fleet.agentBulkActions.reassignPolicy": "新しいポリシーに割り当てる",

View file

@ -7051,7 +7051,6 @@
"xpack.features.savedObjectsManagementFeatureName": "已保存对象管理",
"xpack.features.visualizeFeatureName": "Visualize",
"xpack.fileUpload.enterIndexName": "输入索引名称",
"xpack.fileUpload.fileParser.errorReadingFile": "读取文件时出错",
"xpack.fileUpload.fileParser.noFeaturesDetected": "错误,未检测到功能",
"xpack.fileUpload.fileParser.noFileProvided": "错误,未提供任何文件",
"xpack.fileUpload.fileParser.transformDetailsNotDefined": "未定义 {transformDetails} 的索引选项",
@ -7102,7 +7101,6 @@
"xpack.fileUpload.jsonUploadAndParse.indexPatternError": "索引模式错误",
"xpack.fileUpload.jsonUploadAndParse.writingToIndex": "正在写入索引",
"xpack.fileUpload.noIndexSuppliedErrorMessage": "未提供任何索引。",
"xpack.fileUpload.patternReader.featuresOmitted": "不具有几何形状的一些特征已省略",
"xpack.fleet.agentBulkActions.agentsSelected": "已选择 {count, plural, one {# 个代理} other {# 个代理}}",
"xpack.fleet.agentBulkActions.clearSelection": "清除所选内容",
"xpack.fleet.agentBulkActions.reassignPolicy": "分配到新策略",

212
yarn.lock
View file

@ -2832,6 +2832,61 @@
version "0.0.0"
uid ""
"@loaders.gl/core@2.3.1", "@loaders.gl/core@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@loaders.gl/core/-/core-2.3.1.tgz#147037e17b014528dce00187aac0ec6ccb05938b"
integrity sha512-6d5VRx+C16NOZxanGx18vfT576m909/3ljyE6pXw/1UJRuF14ypapGrIrYWXF/V2lOZ4jrGvVRlGBDFQqMI7rQ==
dependencies:
"@babel/runtime" "^7.3.1"
"@loaders.gl/loader-utils" "2.3.1"
"@loaders.gl/gis@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@loaders.gl/gis/-/gis-2.3.1.tgz#f867dbd32e1287b81da888f5dca6c4fe5c1d2c64"
integrity sha512-nYaeQPAQ/juyhcFsqtxdYN3z39cpCu9kuQgIENeNu3AULcIRRf0w5qNlmgJow52XNHCWLE3uFxNCtiqtBI1RRg==
dependencies:
"@loaders.gl/loader-utils" "2.3.1"
"@mapbox/vector-tile" "^1.3.1"
pbf "^3.2.1"
"@loaders.gl/json@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@loaders.gl/json/-/json-2.3.1.tgz#d1707f95ca3a1413c37cfaaefb103cc14b2632ff"
integrity sha512-9nIpcgTmwbXLVXFfcPwLsx8vkw0kghgoV3POl67icaEm68MFlq9gIrP/JckA1nf/juIaIBSo5dUdZOOwAvvALw==
dependencies:
"@loaders.gl/gis" "2.3.1"
"@loaders.gl/loader-utils" "2.3.1"
"@loaders.gl/tables" "2.3.1"
"@loaders.gl/loader-utils@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@loaders.gl/loader-utils/-/loader-utils-2.3.1.tgz#ccf0d99541c8d3cb8dc123d2f8ca8480b9b93b8b"
integrity sha512-8h0k96Hxxao+hWZNNtd+9PbiK+5epufKoG8IoEddbFeDhlp7wvwRmi0uYf2Qxkn40Vlq23STmbE0CBI0no3pUQ==
dependencies:
"@babel/runtime" "^7.3.1"
"@probe.gl/stats" "^3.3.0"
"@loaders.gl/polyfills@^2.3.5":
version "2.3.5"
resolved "https://registry.yarnpkg.com/@loaders.gl/polyfills/-/polyfills-2.3.5.tgz#5f32b732c8f2a7e80c221f19ee2d01725f09b5c3"
integrity sha512-sSoeqtH2UJ1eqyM18AnYppLb5dapvWxt4hEv7xXmhXfVXxjBzUonLEg8d3UxfGEsxtxVmbS5DXHyL+uhYcLarw==
dependencies:
"@babel/runtime" "^7.3.1"
get-pixels "^3.3.2"
ndarray "^1.0.18"
save-pixels "^2.3.2"
stream-to-async-iterator "^0.2.0"
through "^2.3.8"
web-streams-polyfill "^3.0.0"
"@loaders.gl/tables@2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@loaders.gl/tables/-/tables-2.3.1.tgz#2095dba9b1ceeb633aa0a0a4f282c7c27d02db80"
integrity sha512-jCjBs/z/2dpisFjyFIEMpQCIT7qkIscRZI3lgjT9Sd8hn5mx2CBaf2/rvUgAafychEuTdjcVQYeKD9lYjnYmow==
dependencies:
"@loaders.gl/core" "2.3.1"
d3-dsv "^1.2.0"
"@mapbox/extent@0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@mapbox/extent/-/extent-0.4.0.tgz#3e591f32e1f0c3981c864239f7b0ac06e610f8a9"
@ -3427,6 +3482,13 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.2.tgz#7c6dc4ecef16149fd7a736710baa1b811017fdca"
integrity sha512-JlGTGRYHC2QK+DDbePyXdBdooxFq2+noLfWpRqJtkxcb/oYWzOF0kcbfvvbWrwevCC1l6hLUg1wHYT+ona5BWQ==
"@probe.gl/stats@^3.3.0":
version "3.3.0"
resolved "https://registry.yarnpkg.com/@probe.gl/stats/-/stats-3.3.0.tgz#66f684ead7cee1f2aad5ee5e9d297e84e08c5536"
integrity sha512-CV4c3EgallqZTO88u34/u9L5asL0nCVP1BEkb4qcXlh8Qz2Vmygbyjz1ViQsct6rSi2lJ52lo6W0PnlpZJJvcA==
dependencies:
"@babel/runtime" "^7.0.0"
"@reach/router@^1.3.3":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c"
@ -10070,6 +10132,13 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
contentstream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/contentstream/-/contentstream-1.0.0.tgz#0bdcfa46da30464a86ce8fa7ece565410dc6f9a5"
integrity sha1-C9z6RtowRkqGzo+n7OVlQQ3G+aU=
dependencies:
readable-stream "~1.0.33-1"
contour_plot@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/contour_plot/-/contour_plot-0.0.1.tgz#475870f032b8e338412aa5fc507880f0bf495c77"
@ -10685,6 +10754,13 @@ custom-event-polyfill@^0.3.0:
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-0.3.0.tgz#99807839be62edb446b645832e0d80ead6fa1888"
integrity sha1-mYB4Ob5i7bRGtkWDLg2A6tb6GIg=
cwise-compiler@^1.0.0, cwise-compiler@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/cwise-compiler/-/cwise-compiler-1.1.3.tgz#f4d667410e850d3a313a7d2db7b1e505bb034cc5"
integrity sha1-9NZnQQ6FDToxOn0tt7HlBbsDTMU=
dependencies:
uniq "^1.0.0"
cyclist@~0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
@ -10832,6 +10908,15 @@ d3-dispatch@1, "d3-dispatch@1 - 2", d3-dispatch@^1.0.3:
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58"
integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
d3-dsv@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c"
integrity sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==
dependencies:
commander "2"
iconv-lite "0.4"
rw "1"
d3-dsv@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-2.0.0.tgz#b37b194b6df42da513a120d913ad1be22b5fe7c5"
@ -11073,6 +11158,11 @@ dashify@^0.1.0:
resolved "https://registry.yarnpkg.com/dashify/-/dashify-0.1.0.tgz#107daf9cca5e326e30a8b39ffa5048b6684922ea"
integrity sha1-EH2vnMpeMm4wqLOf+lBItmhJIuo=
data-uri-to-buffer@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz#18ae979a6a0ca994b0625853916d2662bbae0b1a"
integrity sha1-GK6XmmoMqZSwYlhTkW0mYruuCxo=
data-urls@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe"
@ -14239,6 +14329,23 @@ get-nonce@^1.0.0:
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
get-pixels@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/get-pixels/-/get-pixels-3.3.2.tgz#3f62fb8811932c69f262bba07cba72b692b4ff03"
integrity sha512-6ar+8yPxRd1pskEcl2GSEu1La0+xYRjjnkby6AYiRDDwZ0tJbPQmHnSeH9fGLskT8kvR0OukVgtZLcsENF9YKQ==
dependencies:
data-uri-to-buffer "0.0.3"
jpeg-js "^0.3.2"
mime-types "^2.0.1"
ndarray "^1.0.13"
ndarray-pack "^1.1.1"
node-bitmap "0.0.1"
omggif "^1.0.5"
parse-data-uri "^0.2.0"
pngjs "^3.3.3"
request "^2.44.0"
through "^2.3.4"
get-port@^5.0.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
@ -14302,6 +14409,13 @@ gherkin@^5.0.0, gherkin@^5.1.0:
resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.1.0.tgz#684bbb03add24eaf7bdf544f58033eb28fb3c6d5"
integrity sha1-aEu7A63STq9731RPWAM+so+zxtU=
gif-encoder@~0.4.1:
version "0.4.3"
resolved "https://registry.yarnpkg.com/gif-encoder/-/gif-encoder-0.4.3.tgz#8a2b4fe8ca895a48e3a0b6cbb340a0a6a3571899"
integrity sha1-iitP6MqJWkjjoLbLs0CgpqNXGJk=
dependencies:
readable-stream "~1.1.9"
gifwrap@^0.9.2:
version "0.9.2"
resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.9.2.tgz#348e286e67d7cf57942172e1e6f05a71cee78489"
@ -16311,6 +16425,11 @@ io-ts@^2.0.5:
resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.0.5.tgz#e6e3db9df8b047f9cbd6b69e7d2ad3e6437a0b13"
integrity sha512-pL7uUptryanI5Glv+GUv7xh+aLBjxGEDmLwmEYNSx0yOD3djK0Nw5Bt0N6BAkv9LadOUU7QKpRsLcqnTh3UlLA==
iota-array@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/iota-array/-/iota-array-1.0.0.tgz#81ef57fe5d05814cd58c2483632a99c30a0e8087"
integrity sha1-ge9X/l0FgUzVjCSDYyqZwwoOgIc=
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@ -17823,6 +17942,16 @@ joi@^17.1.1:
"@hapi/pinpoint" "^2.0.0"
"@hapi/topo" "^5.0.0"
jpeg-js@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.0.4.tgz#06aaf47efec7af0b1924a59cd695a6d2b5ed870e"
integrity sha1-Bqr0fv7HrwsZJKWc1pWm0rXthw4=
jpeg-js@^0.3.2:
version "0.3.7"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.7.tgz#471a89d06011640592d314158608690172b1028d"
integrity sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==
jpeg-js@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.1.tgz#937a3ae911eb6427f151760f8123f04c8bfe6ef7"
@ -19683,7 +19812,7 @@ mime-db@1.44.0, mime-db@1.x.x, "mime-db@>= 1.40.0 < 2":
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
mime-types@^2.0.1, mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
version "2.1.27"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
@ -20278,6 +20407,29 @@ ncp@^2.0.0, ncp@~2.0.0:
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
ndarray-ops@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/ndarray-ops/-/ndarray-ops-1.2.2.tgz#59e88d2c32a7eebcb1bc690fae141579557a614e"
integrity sha1-WeiNLDKn7ryxvGkPrhQVeVV6YU4=
dependencies:
cwise-compiler "^1.0.0"
ndarray-pack@^1.1.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ndarray-pack/-/ndarray-pack-1.2.1.tgz#8caebeaaa24d5ecf70ff86020637977da8ee585a"
integrity sha1-jK6+qqJNXs9w/4YCBjeXfajuWFo=
dependencies:
cwise-compiler "^1.1.2"
ndarray "^1.0.13"
ndarray@^1.0.13, ndarray@^1.0.18:
version "1.0.19"
resolved "https://registry.yarnpkg.com/ndarray/-/ndarray-1.0.19.tgz#6785b5f5dfa58b83e31ae5b2a058cfd1ab3f694e"
integrity sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==
dependencies:
iota-array "^1.0.0"
is-buffer "^1.0.2"
nearley@^2.7.10:
version "2.16.0"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.16.0.tgz#77c297d041941d268290ec84b739d0ee297e83a7"
@ -20375,6 +20527,11 @@ nock@12.0.3:
lodash "^4.17.13"
propagate "^2.0.0"
node-bitmap@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/node-bitmap/-/node-bitmap-0.0.1.tgz#180eac7003e0c707618ef31368f62f84b2a69091"
integrity sha1-GA6scAPgxwdhjvMTaPYvhLKmkJE=
node-dir@^0.1.10:
version "0.1.17"
resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"
@ -20982,7 +21139,7 @@ octokit-pagination-methods@^1.1.0:
resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
omggif@^1.0.10, omggif@^1.0.9:
omggif@^1.0.10, omggif@^1.0.5, omggif@^1.0.9:
version "1.0.10"
resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19"
integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==
@ -21479,6 +21636,13 @@ parse-color@^1.0.0:
dependencies:
color-convert "~0.5.0"
parse-data-uri@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/parse-data-uri/-/parse-data-uri-0.2.0.tgz#bf04d851dd5c87b0ab238e5d01ace494b604b4c9"
integrity sha1-vwTYUd1ch7CrI45dAazklLYEtMk=
dependencies:
data-uri-to-buffer "0.0.3"
parse-entities@^1.1.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.1.tgz#2c761ced065ba7dc68148580b5a225e4918cdd69"
@ -21981,6 +22145,11 @@ png-js@^1.0.0:
resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.0.0.tgz#e5484f1e8156996e383aceebb3789fd75df1874d"
integrity sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==
pngjs-nozlib@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/pngjs-nozlib/-/pngjs-nozlib-1.0.0.tgz#9e64d602cfe9cce4d9d5997d0687429a73f0b7d7"
integrity sha1-nmTWAs/pzOTZ1Zl9BodCmnPwt9c=
pngjs@^3.0.0, pngjs@^3.3.3, pngjs@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
@ -23541,7 +23710,7 @@ read-pkg@^5.2.0:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0":
readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.33-1:
version "1.0.34"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
@ -23560,6 +23729,16 @@ readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@~1.1.9:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@~2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
@ -24167,7 +24346,7 @@ request-promise@^4.2.2:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
request@2.81.0, request@2.88.0, request@^2.87.0, request@^2.88.0, request@^2.88.2:
request@2.81.0, request@2.88.0, request@^2.44.0, request@^2.87.0, request@^2.88.0, request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@ -24686,6 +24865,19 @@ sass-resources-loader@^2.0.1:
glob "^7.1.1"
loader-utils "^1.0.4"
save-pixels@^2.3.2:
version "2.3.4"
resolved "https://registry.yarnpkg.com/save-pixels/-/save-pixels-2.3.4.tgz#49d349c06b8d7c0127dbf0da24b44aca5afb59fe"
integrity sha1-SdNJwGuNfAEn2/DaJLRKylr7Wf4=
dependencies:
contentstream "^1.0.0"
gif-encoder "~0.4.1"
jpeg-js "0.0.4"
ndarray "^1.0.18"
ndarray-ops "^1.2.2"
pngjs-nozlib "^1.0.0"
through "^2.3.4"
sax@>=0.6.0, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@ -25825,6 +26017,11 @@ stream-splicer@^2.0.0:
inherits "^2.0.1"
readable-stream "^2.0.2"
stream-to-async-iterator@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/stream-to-async-iterator/-/stream-to-async-iterator-0.2.0.tgz#bef5c885e9524f98b2fa5effecc357bd58483780"
integrity sha1-vvXIhelST5iy+l7/7MNXvVhIN4A=
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
@ -27578,7 +27775,7 @@ union-value@^1.0.0:
is-extendable "^0.1.1"
set-value "^2.0.1"
uniq@^1.0.1:
uniq@^1.0.0, uniq@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
@ -28821,6 +29018,11 @@ web-namespaces@^1.0.0:
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec"
integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==
web-streams-polyfill@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.0.1.tgz#1f836eea307e8f4af15758ee473c7af755eb879e"
integrity sha512-M+EmTdszMWINywOZaqpZ6VIEDUmNpRaTOuizF0ZKPjSDC8paMRe/jBBwFv0Yeyn5WYnM5pMqMQa82vpaE+IJRw==
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"