diff --git a/.i18nrc.json b/.i18nrc.json index b5fa1353f39db..fc748eaab5e6d 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -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", diff --git a/src/core_plugins/vega/public/data_model/ems_file_parser.js b/src/core_plugins/vega/public/data_model/ems_file_parser.js index bc6609dba33e9..da051cc1bdf95 100644 --- a/src/core_plugins/vega/public/data_model/ems_file_parser.js +++ b/src/core_plugins/vega/public/data_model/ems_file_parser.js @@ -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 diff --git a/src/core_plugins/vega/public/data_model/es_query_parser.js b/src/core_plugins/vega/public/data_model/es_query_parser.js index 4690af34f228f..5d94afd688b24 100644 --- a/src/core_plugins/vega/public/data_model/es_query_parser.js +++ b/src/core_plugins/vega/public/data_model/es_query_parser.js @@ -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; } diff --git a/src/core_plugins/vega/public/data_model/url_parser.js b/src/core_plugins/vega/public/data_model/url_parser.js index 71f0c3f84a38a..837a443404d1e 100644 --- a/src/core_plugins/vega/public/data_model/url_parser.js +++ b/src/core_plugins/vega/public/data_model/url_parser.js @@ -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); } diff --git a/src/core_plugins/vega/public/data_model/vega_parser.js b/src/core_plugins/vega/public/data_model/vega_parser.js index 8f5784891e67a..e7e78d1f6f385 100644 --- a/src/core_plugins/vega/public/data_model/vega_parser.js +++ b/src/core_plugins/vega/public/data_model/vega_parser.js @@ -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 { diff --git a/src/core_plugins/vega/public/vega_editor_template.html b/src/core_plugins/vega/public/vega_editor_template.html index 60cc33fb159b8..8d13a7bdfa953 100644 --- a/src/core_plugins/vega/public/vega_editor_template.html +++ b/src/core_plugins/vega/public/vega_editor_template.html @@ -24,7 +24,7 @@ id="vegaHelp" class="editor_action" dropdown-toggle - aria-label="Vega help" + aria-label="{{::'vega.editor.vegaHelpButtonAriaLabel' | i18n: {defaultMessage: 'Vega help'} }}" > @@ -34,19 +34,31 @@ aria-labelledby="vegaHelp" >