mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
parent
48aebf5a62
commit
5a019cc31c
10 changed files with 296 additions and 65 deletions
|
@ -6,6 +6,7 @@
|
|||
"kbnVislibVisTypes": "src/core_plugins/kbn_vislib_vis_types",
|
||||
"markdownVis": "src/core_plugins/markdown_vis",
|
||||
"metricVis": "src/core_plugins/metric_vis",
|
||||
"vega": "src/core_plugins/vega",
|
||||
"tableVis": "src/core_plugins/table_vis",
|
||||
"regionMap": "src/core_plugins/region_map",
|
||||
"statusPage": "src/core_plugins/status_page",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { bypassExternalUrlCheck } from '../vega_view/vega_base_view';
|
||||
|
||||
/**
|
||||
|
@ -35,7 +36,14 @@ export class EmsFileParser {
|
|||
*/
|
||||
parseUrl(obj, url) {
|
||||
if (typeof url.name !== 'string') {
|
||||
throw new Error(`data.url with {"%type%": "emsfile"} is missing the "name" of the file`);
|
||||
throw new Error(i18n.translate('vega.emsFileParser.missingNameOfFileErrorMessage', {
|
||||
defaultMessage: '{dataUrlParam} with {dataUrlParamValue} requires {nameParam} parameter (name of the file)',
|
||||
values: {
|
||||
dataUrlParam: '"data.url"',
|
||||
dataUrlParamValue: '{"%type%": "emsfile"}',
|
||||
nameParam: '"name"',
|
||||
},
|
||||
}));
|
||||
}
|
||||
// Optimization: so initiate remote request as early as we know that we will need it
|
||||
if (!this._fileLayersP) {
|
||||
|
@ -57,7 +65,10 @@ export class EmsFileParser {
|
|||
for (const { obj, name } of requests) {
|
||||
const foundLayer = layers.find(v => v.name === name);
|
||||
if (!foundLayer) {
|
||||
throw new Error(`emsfile ${JSON.stringify(name)} does not exist`);
|
||||
throw new Error(i18n.translate('vega.emsFileParser.emsFileNameDoesNotExistErrorMessage', {
|
||||
defaultMessage: '{emsfile} {emsfileName} does not exist',
|
||||
values: { emsfileName: JSON.stringify(name), emsfile: 'emsfile' },
|
||||
}));
|
||||
}
|
||||
|
||||
// This URL can bypass loader sanitization at the later stage
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const TIMEFILTER = '%timefilter%';
|
||||
const AUTOINTERVAL = '%autointerval%';
|
||||
|
@ -56,7 +57,10 @@ export class EsQueryParser {
|
|||
if (body === undefined) {
|
||||
url.body = body = {};
|
||||
} else if (!_.isPlainObject(body)) {
|
||||
throw new Error('url.body must be an object');
|
||||
throw new Error(i18n.translate('vega.esQueryParser.urlBodyValueTypeErrorMessage', {
|
||||
defaultMessage: '{configName} must be an object',
|
||||
values: { configName: 'url.body' },
|
||||
}));
|
||||
}
|
||||
|
||||
// Migrate legacy %context_query% into context & timefield values
|
||||
|
@ -64,12 +68,30 @@ export class EsQueryParser {
|
|||
delete url[LEGACY_CONTEXT];
|
||||
if (legacyContext !== undefined) {
|
||||
if (body.query !== undefined) {
|
||||
throw new Error(`Data url must not have legacy "${LEGACY_CONTEXT}" and "body.query" values at the same time`);
|
||||
throw new Error(i18n.translate('vega.esQueryParser.dataUrlMustNotHaveLegacyAndBodyQueryValuesAtTheSameTimeErrorMessage', {
|
||||
defaultMessage: '{dataUrlParam} must not have legacy {legacyContext} and {bodyQueryConfigName} values at the same time',
|
||||
values: {
|
||||
legacyContext: `"${LEGACY_CONTEXT}"`,
|
||||
bodyQueryConfigName: '"body.query"',
|
||||
dataUrlParam: '"data.url"',
|
||||
},
|
||||
}));
|
||||
} else if (usesContext) {
|
||||
throw new Error(`Data url must not have "${LEGACY_CONTEXT}" together with "${CONTEXT}" or "${TIMEFIELD}"`);
|
||||
throw new Error(i18n.translate('vega.esQueryParser.dataUrlMustNotHaveLegacyContextTogetherWithContextOrTimefieldErrorMessage', {
|
||||
defaultMessage: '{dataUrlParam} must not have {legacyContext} together with {context} or {timefield}',
|
||||
values: {
|
||||
legacyContext: `"${LEGACY_CONTEXT}"`,
|
||||
context: `"${CONTEXT}"`,
|
||||
timefield: `"${TIMEFIELD}"`,
|
||||
dataUrlParam: '"data.url"',
|
||||
},
|
||||
}));
|
||||
} else if (legacyContext !== true && (typeof legacyContext !== 'string' || legacyContext.length === 0)) {
|
||||
throw new Error(`Legacy "${LEGACY_CONTEXT}" can either be true (ignores time range picker), ` +
|
||||
'or it can be the name of the time field, e.g. "@timestamp"');
|
||||
throw new Error(i18n.translate('vega.esQueryParser.legacyContextCanBeTrueErrorMessage', {
|
||||
// eslint-disable-next-line max-len
|
||||
defaultMessage: 'Legacy {legacyContext} can either be {trueValue} (ignores time range picker), or it can be the name of the time field, e.g. {timestampParam}',
|
||||
values: { legacyContext: `"${LEGACY_CONTEXT}"`, trueValue: 'true', timestampParam: '"@timestamp"' },
|
||||
}));
|
||||
}
|
||||
|
||||
usesContext = true;
|
||||
|
@ -81,13 +103,26 @@ export class EsQueryParser {
|
|||
}
|
||||
result += '}';
|
||||
|
||||
this._onWarning(
|
||||
`Legacy "url": {"${LEGACY_CONTEXT}": ${JSON.stringify(legacyContext)}} should change to ${result}`);
|
||||
this._onWarning(i18n.translate('vega.esQueryParser.legacyUrlShouldChangeToWarningMessage', {
|
||||
defaultMessage: 'Legacy {urlParam}: {legacyUrl} should change to {result}',
|
||||
values: {
|
||||
legacyUrl: `"${LEGACY_CONTEXT}": ${JSON.stringify(legacyContext)}`,
|
||||
result,
|
||||
urlParam: '"url"',
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if (body.query !== undefined) {
|
||||
if (usesContext) {
|
||||
throw new Error(`url.${CONTEXT} and url.${TIMEFIELD} must not be used when url.body.query is set`);
|
||||
throw new Error(i18n.translate('vega.esQueryParser.urlContextAndUrlTimefieldMustNotBeUsedErrorMessage', {
|
||||
defaultMessage: '{urlContext} and {timefield} must not be used when {queryParam} is set',
|
||||
values: {
|
||||
timefield: `url.${TIMEFIELD}`,
|
||||
urlContext: `url.${CONTEXT}`,
|
||||
queryParam: 'url.body.query',
|
||||
},
|
||||
}));
|
||||
}
|
||||
this._injectContextVars(body.query, true);
|
||||
} else if (usesContext) {
|
||||
|
@ -168,7 +203,13 @@ export class EsQueryParser {
|
|||
if (size === true) {
|
||||
size = 50; // by default, try to get ~80 values
|
||||
} else if (typeof size !== 'number') {
|
||||
throw new Error(`"${AUTOINTERVAL}" must be either true or a number`);
|
||||
throw new Error(i18n.translate('vega.esQueryParser.autointervalValueTypeErrorMessage', {
|
||||
defaultMessage: '{autointerval} must be either {trueValue} or a number',
|
||||
values: {
|
||||
autointerval: `"${AUTOINTERVAL}"`,
|
||||
trueValue: 'true',
|
||||
},
|
||||
}));
|
||||
}
|
||||
const bounds = this._timeCache.getTimeBounds();
|
||||
obj.interval = EsQueryParser._roundInterval((bounds.max - bounds.min) / size);
|
||||
|
@ -190,7 +231,15 @@ export class EsQueryParser {
|
|||
this._injectContextVars(subObj, isQuery);
|
||||
continue;
|
||||
default:
|
||||
throw new Error(`"${TIMEFILTER}" property must be set to true, "min", or "max"`);
|
||||
throw new Error(i18n.translate('vega.esQueryParser.timefilterValueErrorMessage', {
|
||||
defaultMessage: '{timefilter} property must be set to {trueValue}, {minValue}, or {maxValue}',
|
||||
values: {
|
||||
timefilter: `"${TIMEFILTER}"`,
|
||||
trueValue: 'true',
|
||||
minValue: '"min"',
|
||||
maxValue: '"max"',
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +276,12 @@ export class EsQueryParser {
|
|||
if (opts.shift) {
|
||||
const shift = opts.shift;
|
||||
if (typeof shift !== 'number') {
|
||||
throw new Error('shift must be a numeric value');
|
||||
throw new Error(i18n.translate('vega.esQueryParser.shiftMustValueTypeErrorMessage', {
|
||||
defaultMessage: '{shiftParam} must be a numeric value',
|
||||
values: {
|
||||
shiftParam: '"shift"',
|
||||
},
|
||||
}));
|
||||
}
|
||||
let multiplier;
|
||||
switch (opts.unit || 'd') {
|
||||
|
@ -252,7 +306,13 @@ export class EsQueryParser {
|
|||
multiplier = 1000;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown unit value. Must be one of: [week, day, hour, minute, second]');
|
||||
throw new Error(i18n.translate('vega.esQueryParser.unknownUnitValueErrorMessage', {
|
||||
defaultMessage: 'Unknown {unitParamName} value. Must be one of: [{unitParamValues}]',
|
||||
values: {
|
||||
unitParamName: '"unit"',
|
||||
unitParamValues: '"week", "day", "hour", "minute", "second"',
|
||||
},
|
||||
}));
|
||||
}
|
||||
result += shift * multiplier;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
/**
|
||||
* This class processes all Vega spec customizations,
|
||||
|
@ -36,12 +37,25 @@ export class UrlParser {
|
|||
parseUrl(obj, urlObj) {
|
||||
let url = urlObj.url;
|
||||
if (!url) {
|
||||
throw new Error(`data.url requires a url parameter in a form 'https://example.org/path/subpath'`);
|
||||
throw new Error(i18n.translate('vega.urlParser.dataUrlRequiresUrlParameterInFormErrorMessage', {
|
||||
defaultMessage: '{dataUrlParam} requires a {urlParam} parameter in a form "{formLink}"',
|
||||
values: {
|
||||
dataUrlParam: '"data.url"',
|
||||
urlParam: '"url"',
|
||||
formLink: 'https://example.org/path/subpath',
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const query = urlObj.query;
|
||||
if (!query) {
|
||||
this._onWarning(`Using a "url": {"%type%": "url", "url": ...} should have a "query" sub-object`);
|
||||
this._onWarning(i18n.translate('vega.urlParser.urlShouldHaveQuerySubObjectWarningMessage', {
|
||||
defaultMessage: 'Using a {urlObject} should have a {subObjectName} sub-object',
|
||||
values: {
|
||||
urlObject: '"url": {"%type%": "url", "url": ...}',
|
||||
subObjectName: '"query"',
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
url += (url.includes('?') ? '&' : '?') + $.param(query);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import { Utils } from './utils';
|
|||
import { EmsFileParser } from './ems_file_parser';
|
||||
import { UrlParser } from './url_parser';
|
||||
import { VISUALIZATION_COLORS } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
// Set default single color to match other Kibana visualizations
|
||||
const defaultColor = VISUALIZATION_COLORS[0];
|
||||
|
@ -77,7 +78,9 @@ export class VegaParser {
|
|||
this.spec = hjson.parse(this.spec, { legacyRoot: false });
|
||||
}
|
||||
if (!_.isPlainObject(this.spec)) {
|
||||
throw new Error('Invalid Vega spec');
|
||||
throw new Error(i18n.translate('vega.vegaParser.invalidVegaSpecErrorMessage', {
|
||||
defaultMessage: 'Invalid Vega specification',
|
||||
}));
|
||||
}
|
||||
this.isVegaLite = this._parseSchema();
|
||||
this.useHover = !this.isVegaLite;
|
||||
|
@ -128,7 +131,9 @@ export class VegaParser {
|
|||
this.spec.projections.length !== 1 ||
|
||||
this.spec.projections[0].name !== 'projection'
|
||||
) {
|
||||
throw new Error('Internal error: VL compiler should have generated a single projection object');
|
||||
throw new Error(i18n.translate('vega.vegaParser.VLCompilerShouldHaveGeneratedSingleProtectionObjectErrorMessage', {
|
||||
defaultMessage: 'Internal error: Vega-Lite compiler should have generated a single projection object',
|
||||
}));
|
||||
}
|
||||
delete this.spec.projections;
|
||||
}
|
||||
|
@ -180,7 +185,14 @@ export class VegaParser {
|
|||
delete this.spec.width;
|
||||
delete this.spec.height;
|
||||
} else {
|
||||
this._onWarning(`The 'width' and 'height' params are ignored with autosize=fit`);
|
||||
this._onWarning(i18n.translate('vega.vegaParser.widthAndHeightParamsAreIngroredWithAutosizeFitWarningMessage', {
|
||||
defaultMessage: 'The {widthParam} and {heightParam} params are ignored with {autosizeParam}',
|
||||
values: {
|
||||
autosizeParam: 'autosize=fit',
|
||||
widthParam: '"width"',
|
||||
heightParam: '"height"',
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,13 +207,18 @@ export class VegaParser {
|
|||
if (this._config.controlsLocation === undefined) {
|
||||
this.containerDir = 'column';
|
||||
} else {
|
||||
throw new Error('Unrecognized controlsLocation value. Expecting one of ["' +
|
||||
locToDirMap.keys().join('", "') + '"]');
|
||||
throw new Error(i18n.translate('vega.vegaParser.unrecognizedControlsLocationValueErrorMessage', {
|
||||
defaultMessage: 'Unrecognized {controlsLocationParam} value. Expecting one of [{locToDirMap}]',
|
||||
values: { locToDirMap: `"${locToDirMap.keys().join('", "')}"`, controlsLocationParam: 'controlsLocation' }
|
||||
}));
|
||||
}
|
||||
}
|
||||
const dir = this._config.controlsDirection;
|
||||
if (dir !== undefined && dir !== 'horizontal' && dir !== 'vertical') {
|
||||
throw new Error('Unrecognized dir value. Expecting one of ["horizontal", "vertical"]');
|
||||
throw new Error(i18n.translate('vega.vegaParser.unrecognizedDirValueErrorMessage', {
|
||||
defaultMessage: 'Unrecognized {dirParam} value. Expecting one of [{expectedValues}]',
|
||||
values: { expectedValues: '"horizontal", "vertical"', dirParam: 'dir' },
|
||||
}));
|
||||
}
|
||||
this.controlsDir = dir === 'horizontal' ? 'row' : 'column';
|
||||
}
|
||||
|
@ -217,15 +234,27 @@ export class VegaParser {
|
|||
result = this.spec._hostConfig;
|
||||
delete this.spec._hostConfig;
|
||||
if (!_.isPlainObject(result)) {
|
||||
throw new Error('If present, _hostConfig must be an object');
|
||||
throw new Error(i18n.translate('vega.vegaParser.hostConfigValueTypeErrorMessage', {
|
||||
defaultMessage: 'If present, {configName} must be an object',
|
||||
values: { configName: '"_hostConfig"' },
|
||||
}));
|
||||
}
|
||||
this._onWarning('_hostConfig has been deprecated. Use config.kibana instead.');
|
||||
this._onWarning(i18n.translate('vega.vegaParser.hostConfigIsDeprecatedWarningMessage', {
|
||||
defaultMessage: '{deprecatedConfigName} has been deprecated. Use {newConfigName} instead.',
|
||||
values: {
|
||||
deprecatedConfigName: '"_hostConfig"',
|
||||
newConfigName: 'config.kibana',
|
||||
},
|
||||
}));
|
||||
}
|
||||
if (_.isPlainObject(this.spec.config) && this.spec.config.kibana !== undefined) {
|
||||
result = this.spec.config.kibana;
|
||||
delete this.spec.config.kibana;
|
||||
if (!_.isPlainObject(result)) {
|
||||
throw new Error('If present, config.kibana must be an object');
|
||||
throw new Error(i18n.translate('vega.vegaParser.kibanaConfigValueTypeErrorMessage', {
|
||||
defaultMessage: 'If present, {configName} must be an object',
|
||||
values: { configName: 'config.kibana' },
|
||||
}));
|
||||
}
|
||||
}
|
||||
return result || {};
|
||||
|
@ -241,13 +270,19 @@ export class VegaParser {
|
|||
if (result.position === undefined) {
|
||||
result.position = 'top';
|
||||
} else if (['top', 'right', 'bottom', 'left'].indexOf(result.position) === -1) {
|
||||
throw new Error('Unexpected value for the result.position configuration');
|
||||
throw new Error(i18n.translate('vega.vegaParser.unexpectedValueForPositionConfigurationErrorMessage', {
|
||||
defaultMessage: 'Unexpected value for the {configurationName} configuration',
|
||||
values: { configurationName: 'result.position' },
|
||||
}));
|
||||
}
|
||||
|
||||
if (result.padding === undefined) {
|
||||
result.padding = 16;
|
||||
} else if (typeof result.padding !== 'number') {
|
||||
throw new Error('config.kibana.result.padding is expected to be a number');
|
||||
throw new Error(i18n.translate('vega.vegaParser.paddingConfigValueTypeErrorMessage', {
|
||||
defaultMessage: '{configName} is expected to be a number',
|
||||
values: { configName: 'config.kibana.result.padding' },
|
||||
}));
|
||||
}
|
||||
|
||||
if (result.centerOnMark === undefined) {
|
||||
|
@ -256,7 +291,10 @@ export class VegaParser {
|
|||
} else if (typeof result.centerOnMark === 'boolean') {
|
||||
result.centerOnMark = result.centerOnMark ? Number.MAX_VALUE : -1;
|
||||
} else if (typeof result.centerOnMark !== 'number') {
|
||||
throw new Error('config.kibana.result.centerOnMark is expected to be true, false, or a number');
|
||||
throw new Error(i18n.translate('vega.vegaParser.centerOnMarkConfigValueTypeErrorMessage', {
|
||||
defaultMessage: '{configName} is expected to be {trueValue}, {falseValue}, or a number',
|
||||
values: { configName: 'config.kibana.result.centerOnMark', trueValue: 'true', falseValue: 'false' },
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -280,7 +318,10 @@ export class VegaParser {
|
|||
res[name] = parsed;
|
||||
return;
|
||||
}
|
||||
this._onWarning(`config.kibana.${name} is not valid`);
|
||||
this._onWarning(i18n.translate('vega.vegaParser.someKibanaConfigurationIsNoValidWarningMessage', {
|
||||
defaultMessage: '{configName} is not valid',
|
||||
values: { configName: `config.kibana.${name}` },
|
||||
}));
|
||||
}
|
||||
if (!isZoom) res[name] = 0;
|
||||
};
|
||||
|
@ -294,7 +335,14 @@ export class VegaParser {
|
|||
// `false` is a valid value
|
||||
res.mapStyle = this._config.mapStyle === undefined ? `default` : this._config.mapStyle;
|
||||
if (res.mapStyle !== `default` && res.mapStyle !== false) {
|
||||
this._onWarning(`config.kibana.mapStyle may either be false or "default"`);
|
||||
this._onWarning(i18n.translate('vega.vegaParser.mapStyleValueTypeWarningMessage', {
|
||||
defaultMessage: '{mapStyleConfigName} may either be {mapStyleConfigFirstAllowedValue} or {mapStyleConfigSecondAllowedValue}',
|
||||
values: {
|
||||
mapStyleConfigName: 'config.kibana.mapStyle',
|
||||
mapStyleConfigFirstAllowedValue: 'false',
|
||||
mapStyleConfigSecondAllowedValue: '"default"',
|
||||
},
|
||||
}));
|
||||
res.mapStyle = `default`;
|
||||
}
|
||||
|
||||
|
@ -306,7 +354,12 @@ export class VegaParser {
|
|||
if (!Array.isArray(maxBounds) || maxBounds.length !== 4 ||
|
||||
!maxBounds.every(v => typeof v === 'number' && Number.isFinite(v))
|
||||
) {
|
||||
this._onWarning(`config.kibana.maxBounds must be an array with four numbers`);
|
||||
this._onWarning(i18n.translate('vega.vegaParser.maxBoundsValueTypeWarningMessage', {
|
||||
defaultMessage: '{maxBoundsConfigName} must be an array with four numbers',
|
||||
values: {
|
||||
maxBoundsConfigName: 'config.kibana.maxBounds',
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
res.maxBounds = maxBounds;
|
||||
}
|
||||
|
@ -320,7 +373,12 @@ export class VegaParser {
|
|||
if (val === undefined) {
|
||||
dstObj[paramName] = dflt;
|
||||
} else if (typeof val !== 'boolean') {
|
||||
this._onWarning(`config.kibana.${paramName} must be a boolean value`);
|
||||
this._onWarning(i18n.translate('vega.vegaParser.someKibanaParamValueTypeWarningMessage', {
|
||||
defaultMessage: '{configName} must be a boolean value',
|
||||
values: {
|
||||
configName: `config.kibana.${paramName}`
|
||||
},
|
||||
}));
|
||||
dstObj[paramName] = dflt;
|
||||
} else {
|
||||
dstObj[paramName] = val;
|
||||
|
@ -334,7 +392,10 @@ export class VegaParser {
|
|||
*/
|
||||
_parseSchema() {
|
||||
if (!this.spec.$schema) {
|
||||
this._onWarning(`The input spec does not specify a "$schema", defaulting to "${DEFAULT_SCHEMA}"`);
|
||||
this._onWarning(i18n.translate('vega.vegaParser.inputSpecDoesNotSpecifySchemaWarningMessage', {
|
||||
defaultMessage: 'The input spec does not specify a {schemaParam}, defaulting to {defaultSchema}',
|
||||
values: { defaultSchema: `"${DEFAULT_SCHEMA}"`, schemaParam: '"$schema"' },
|
||||
}));
|
||||
this.spec.$schema = DEFAULT_SCHEMA;
|
||||
}
|
||||
|
||||
|
@ -343,10 +404,14 @@ export class VegaParser {
|
|||
const libVersion = isVegaLite ? vegaLite.version : vega.version;
|
||||
|
||||
if (versionCompare(schema.version, libVersion) > 0) {
|
||||
this._onWarning(
|
||||
`The input spec uses ${schema.library} ${schema.version}, but ` +
|
||||
`current version of ${schema.library} is ${libVersion}.`
|
||||
);
|
||||
this._onWarning(i18n.translate('vega.vegaParser.notValidLibraryVersionForInputSpecWarningMessage', {
|
||||
defaultMessage: 'The input spec uses {schemaLibrary} {schemaVersion}, but current version of {schemaLibrary} is {libraryVersion}.',
|
||||
values: {
|
||||
schemaLibrary: schema.library,
|
||||
schemaVersion: schema.version,
|
||||
libraryVersion: libVersion,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
return isVegaLite;
|
||||
|
@ -371,7 +436,12 @@ export class VegaParser {
|
|||
|
||||
const parser = this._urlParsers[type];
|
||||
if (parser === undefined) {
|
||||
throw new Error(`url: {"%type%": "${type}"} is not supported`);
|
||||
throw new Error(i18n.translate('vega.vegaParser.notSupportedUrlTypeErrorMessage', {
|
||||
defaultMessage: '{urlObject} is not supported',
|
||||
values: {
|
||||
urlObject: 'url: {"%type%": "${type}"}',
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
let pendingArr = pending[type];
|
||||
|
@ -405,7 +475,14 @@ export class VegaParser {
|
|||
if (key === 'data' && _.isPlainObject(obj.url)) {
|
||||
// Assume that any "data": {"url": {...}} is a request for data
|
||||
if (obj.values !== undefined || obj.source !== undefined) {
|
||||
throw new Error('Data must not have more than one of "url", "values", and "source"');
|
||||
throw new Error(i18n.translate('vega.vegaParser.dataExceedsSomeParamsUseTimesLimitErrorMessage', {
|
||||
defaultMessage: 'Data must not have more than one of {urlParam}, {valuesParam}, and {sourceParam}',
|
||||
values: {
|
||||
urlParam: '"url"',
|
||||
valuesParam: '"values"',
|
||||
sourceParam: '"source"',
|
||||
},
|
||||
}));
|
||||
}
|
||||
onFind(obj);
|
||||
} else {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
id="vegaHelp"
|
||||
class="editor_action"
|
||||
dropdown-toggle
|
||||
aria-label="Vega help"
|
||||
aria-label="{{::'vega.editor.vegaHelpButtonAriaLabel' | i18n: {defaultMessage: 'Vega help'} }}"
|
||||
>
|
||||
<span class="kuiIcon fa-question-circle"></span>
|
||||
</button>
|
||||
|
@ -34,19 +34,31 @@
|
|||
aria-labelledby="vegaHelp"
|
||||
>
|
||||
<li role="menuitem">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://www.elastic.co/guide/en/kibana/master/vega-graph.html">
|
||||
Kibana Vega Help
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://www.elastic.co/guide/en/kibana/master/vega-graph.html"
|
||||
i18n-id="vega.editor.vegaHelpLinkText"
|
||||
i18n-default-message="Kibana Vega Help"
|
||||
></a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://vega.github.io/vega-lite/docs/">
|
||||
Vega-Lite Documentation
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://vega.github.io/vega-lite/docs/"
|
||||
i18n-id="vega.editor.vegaLiteDocumentationLinkText"
|
||||
i18n-default-message="Vega-Lite Documentation"
|
||||
></a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://vega.github.io/vega/docs/">
|
||||
Vega Documentation
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://vega.github.io/vega/docs/"
|
||||
i18n-id="vega.editor.vegaDocumentationLinkText"
|
||||
i18n-default-message="Vega Documentation"
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
@ -55,7 +67,7 @@
|
|||
id="vegaOptions"
|
||||
class="editor_action"
|
||||
dropdown-toggle
|
||||
aria-label="Vega editor options"
|
||||
aria-label="{{::'vega.editor.vegaEditorOptionsButtonAriaLabel' | i18n: {defaultMessage: 'Vega editor options'} }}"
|
||||
>
|
||||
<span class="kuiIcon fa-wrench"></span>
|
||||
</button>
|
||||
|
@ -65,10 +77,18 @@
|
|||
aria-labelledby="vegaOptions"
|
||||
>
|
||||
<li role="menuitem">
|
||||
<button ng-click="formatHJson($event)">Reformat as HJSON</button>
|
||||
<button
|
||||
ng-click="formatHJson($event)"
|
||||
i18n-id="vega.editor.reformatAsHJSONButtonLabel"
|
||||
i18n-default-message="Reformat as HJSON"
|
||||
></button>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<button ng-click="formatJson($event)">Reformat as JSON, delete comments</button>
|
||||
<button
|
||||
ng-click="formatJson($event)"
|
||||
i18n-id="vega.editor.reformatAsJSONButtonLabel"
|
||||
i18n-default-message="Reformat as JSON, delete comments"
|
||||
></button>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
import { VisFactoryProvider } from 'ui/vis/vis_factory';
|
||||
import { CATEGORY } from 'ui/vis/vis_category';
|
||||
|
@ -41,8 +42,12 @@ VisTypesRegistryProvider.register((Private) => {
|
|||
|
||||
return VisFactory.createBaseVisualization({
|
||||
name: 'vega',
|
||||
title: 'Vega',
|
||||
description: 'Create custom visualizations using Vega and VegaLite',
|
||||
title: i18n.translate('vega.type.vegaTitle', {
|
||||
defaultMessage: 'Vega',
|
||||
}),
|
||||
description: i18n.translate('vega.type.vegaВescription', {
|
||||
defaultMessage: 'Create custom visualizations using Vega and Vega-Lite',
|
||||
}),
|
||||
icon: 'visVega',
|
||||
category: CATEGORY.OTHER,
|
||||
visConfig: { defaults: { spec: defaultSpec } },
|
||||
|
|
|
@ -24,6 +24,7 @@ import * as vega from 'vega-lib';
|
|||
import * as vegaLite from 'vega-lite';
|
||||
import { Utils } from '../data_model/utils';
|
||||
import { VISUALIZATION_COLORS } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TooltipHandler } from './vega_tooltip';
|
||||
import { buildQueryFilter } from 'ui/filter_manager/lib';
|
||||
|
||||
|
@ -145,7 +146,13 @@ export class VegaBaseView {
|
|||
// because user can only supply pure JSON data structure.
|
||||
uri = uri.url;
|
||||
} else if (!this._vegaConfig.enableExternalUrls) {
|
||||
throw new Error('External URLs are not enabled. Add vega.enableExternalUrls: true to kibana.yml');
|
||||
throw new Error(i18n.translate('vega.vegaParser.baseView.externalUrlsAreNotEnabledErrorMessage', {
|
||||
defaultMessage: 'External URLs are not enabled. Add {enableExternalUrls} to {kibanaConfigFileName}',
|
||||
values: {
|
||||
enableExternalUrls: 'vega.enableExternalUrls: true',
|
||||
kibanaConfigFileName: 'kibana.yml',
|
||||
},
|
||||
}));
|
||||
}
|
||||
return originalSanitize(uri, options);
|
||||
};
|
||||
|
@ -233,7 +240,10 @@ export class VegaBaseView {
|
|||
const handlerFunc = vegaFunctions[funcName];
|
||||
if (!handlerFunc || !this[handlerFunc]) {
|
||||
// in case functions don't match the list above
|
||||
throw new Error(`${funcName}() is not defined for this graph`);
|
||||
throw new Error(i18n.translate('vega.vegaParser.baseView.functionIsNotDefinedForGraphErrorMessage', {
|
||||
defaultMessage: '{funcName} is not defined for this graph',
|
||||
values: { funcName: `${funcName}()` },
|
||||
}));
|
||||
}
|
||||
await this[handlerFunc](...args);
|
||||
} catch (err) {
|
||||
|
@ -308,8 +318,13 @@ export class VegaBaseView {
|
|||
const startDate = dateMath.parse(start);
|
||||
const endDate = dateMath.parse(end);
|
||||
if (!startDate || !endDate || !startDate.isValid() || !endDate.isValid()) {
|
||||
throw new Error(`Error setting time filter: both time values must be either relative or absolute dates. ` +
|
||||
`start=${JSON.stringify(start)}, end=${JSON.stringify(end)}`);
|
||||
throw new Error(i18n.translate('vega.vegaParser.baseView.timeValuesTypeErrorMessage', {
|
||||
defaultMessage: 'Error setting time filter: both time values must be either relative or absolute dates. {start}, {end}',
|
||||
values: {
|
||||
start: `start=${JSON.stringify(start)}`,
|
||||
end: `end=${JSON.stringify(end)}`,
|
||||
},
|
||||
}));
|
||||
}
|
||||
reverse = startDate.isAfter(endDate);
|
||||
if (isValidAbsStart || isValidAbsEnd) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import { KibanaMap } from 'ui/vis/map/kibana_map';
|
|||
import * as vega from 'vega-lib';
|
||||
import { VegaBaseView } from './vega_base_view';
|
||||
import { VegaMapLayer } from './vega_map_layer';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export class VegaMapView extends VegaBaseView {
|
||||
|
||||
|
@ -37,7 +38,10 @@ export class VegaMapView extends VegaBaseView {
|
|||
const mapStyle = mapConfig.mapStyle === 'default' ? 'road_map' : mapConfig.mapStyle;
|
||||
baseMapOpts = tmsServices.find((s) => s.id === mapStyle);
|
||||
if (!baseMapOpts) {
|
||||
this.onWarn(`mapStyle ${JSON.stringify(mapStyle)} was not found`);
|
||||
this.onWarn(i18n.translate('vega.mapView.mapStyleNotFoundWarningMessage', {
|
||||
defaultMessage: '{mapStyleParam} was not found',
|
||||
values: { mapStyleParam: `"mapStyle": ${JSON.stringify(mapStyle)}` },
|
||||
}));
|
||||
} else {
|
||||
limitMinZ = baseMapOpts.minZoom;
|
||||
limitMaxZ = baseMapOpts.maxZoom;
|
||||
|
@ -48,10 +52,16 @@ export class VegaMapView extends VegaBaseView {
|
|||
if (value === undefined) {
|
||||
value = dflt;
|
||||
} else if (value < min) {
|
||||
this.onWarn(`Resetting ${name} to ${min}`);
|
||||
this.onWarn(i18n.translate('vega.mapView.resettingPropertyToMinValueWarningMessage', {
|
||||
defaultMessage: 'Resetting {name} to {min}',
|
||||
values: { name: `"${name}"`, min },
|
||||
}));
|
||||
value = min;
|
||||
} else if (value > max) {
|
||||
this.onWarn(`Resetting ${name} to ${max}`);
|
||||
this.onWarn(i18n.translate('vega.mapView.resettingPropertyToMaxValueWarningMessage', {
|
||||
defaultMessage: 'Resetting {name} to {max}',
|
||||
values: { name: `"${name}"`, max },
|
||||
}));
|
||||
value = max;
|
||||
}
|
||||
return value;
|
||||
|
@ -60,7 +70,13 @@ export class VegaMapView extends VegaBaseView {
|
|||
let minZoom = validate('minZoom', mapConfig.minZoom, limitMinZ, limitMinZ, limitMaxZ);
|
||||
let maxZoom = validate('maxZoom', mapConfig.maxZoom, limitMaxZ, limitMinZ, limitMaxZ);
|
||||
if (minZoom > maxZoom) {
|
||||
this.onWarn('minZoom and maxZoom have been swapped');
|
||||
this.onWarn(i18n.translate('vega.mapView.minZoomAndMaxZoomHaveBeenSwappedWarningMessage', {
|
||||
defaultMessage: '{minZoomPropertyName} and {maxZoomPropertyName} have been swapped',
|
||||
values: {
|
||||
minZoomPropertyName: '"minZoom"',
|
||||
maxZoomPropertyName: '"maxZoom"',
|
||||
},
|
||||
}));
|
||||
[minZoom, maxZoom] = [maxZoom, minZoom];
|
||||
}
|
||||
const zoom = validate('zoom', mapConfig.zoom, 2, minZoom, maxZoom);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { toastNotifications, Notifier } from 'ui/notify';
|
||||
import { VegaView } from './vega_view/vega_view';
|
||||
import { VegaMapView } from './vega_view/vega_map_view';
|
||||
|
@ -43,10 +44,19 @@ export function VegaVisualizationProvider(Private, vegaConfig, serviceSettings,
|
|||
let idxObj;
|
||||
if (index) {
|
||||
idxObj = await findObjectByTitle(savedObjectsClient, 'index-pattern', index);
|
||||
if (!idxObj) throw new Error(`Index "${index}" not found`);
|
||||
if (!idxObj) {
|
||||
throw new Error(i18n.translate('vega.visualization.indexNotFoundErrorMessage', {
|
||||
defaultMessage: 'Index {index} not found',
|
||||
values: { index: `"${index}"` },
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
idxObj = await this._vis.API.indexPatterns.getDefault();
|
||||
if (!idxObj) throw new Error('Unable to find default index');
|
||||
if (!idxObj) {
|
||||
throw new Error(i18n.translate('vega.visualization.unableToFindDefaultIndexErrorMessage', {
|
||||
defaultMessage: 'Unable to find default index',
|
||||
}));
|
||||
}
|
||||
}
|
||||
return idxObj.id;
|
||||
}
|
||||
|
@ -59,7 +69,9 @@ export function VegaVisualizationProvider(Private, vegaConfig, serviceSettings,
|
|||
*/
|
||||
async render(visData, status) {
|
||||
if (!visData && !this._vegaView) {
|
||||
toastNotifications.addWarning('Unable to render without data');
|
||||
toastNotifications.addWarning(i18n.translate('vega.visualization.unableToRenderWithoutDataWarningMessage', {
|
||||
defaultMessage: 'Unable to render without data',
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue