diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 695a4d4f45b02..80fddacb69470 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -50,13 +50,12 @@ adapt to the interval between measurements. Keys are http://en.wikipedia.org/wik `fields:popularLimit`:: The top N most popular fields to show. `filterEditor:suggestValues`:: Set this property to `false` to prevent the filter editor from suggesting values for fields. `filters:pinnedByDefault`:: Set this property to `true` to make filters have a global state (be pinned) by default. -`format:bytes:defaultPattern`:: The default http://numeraljs.com/[numeral format] for the "bytes" format. -`format:currency:defaultPattern`:: The default http://numeraljs.com/[numeral format] for the "currency" format. `format:defaultTypeMap`:: A map of the default format name for each field type. Field types that are not explicitly mentioned use "\_default_". -`format:number:defaultLocale`:: The http://numeraljs.com/[numeral language] locale. +`format:currency:defaultCurrency`:: The currency used for all currency formatting by default. Currency can be changed for a single field using the index management section. If a currency is unlisted here, it can also be set in the index management section. +`format:defaultLocale`:: Default number display locale. This locale will affect number formatting for most formatters. When "detect" is chosen, it will use the locale preference from the browser when formatting numbers. +`format:number:defaultLocale`:: The http://numeraljs.com/[numeral language] locale. This affects the custom numeral.js formatter and the bytes formatter. `format:number:defaultPattern`:: The default http://numeraljs.com/[numeral format] for the "number" format. -`format:percent:defaultPattern`:: The default http://numeraljs.com/[numeral format] for the "percent" format. `histogram:barTarget`:: When date histograms use the `auto` interval, Kibana attempts to generate this number of bars. `histogram:maxBars`:: Date histograms are not generated with more bars than the value of this property, scaling values when necessary. diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc index 308e61abf70e5..536c9537dd477 100644 --- a/docs/management/managing-fields.asciidoc +++ b/docs/management/managing-fields.asciidoc @@ -5,8 +5,9 @@ The fields for the index pattern are listed in a table. Click a column header to the *Controls* button in the rightmost column for a given field to edit the field's properties. You can manually set the field's format from the *Format* drop-down. Format options vary based on the field's type. -You can also set the field's popularity value in the *Popularity* text entry box to any desired value. Click the -*Update Field* button to confirm your changes or *Cancel* to return to the list of fields. +You can also set the field's popularity value in the *Popularity* text entry box to any desired value. If a field has a +popularity value, it will be sorted higher in Discover. Click the *Update Field* button to confirm your changes or +*Cancel* to return to the list of fields. Kibana has field formatters for the following field types: @@ -46,18 +47,23 @@ include::field-formatters/string-formatter.asciidoc[] [[field-formatters-numeric]] === Numeric Field Formatters -Numeric fields support the `Url`, `Bytes`, `Duration`, `Number`, `Percentage`, `String`, and `Color` formatters. +Numeric fields support the `Default Number`, `Percent`, `Bytes`, `Short Number`, `Currency`, `Custom Numeral.js format`, +`Duration`, `String`, `Url`, and `Color` formatters. -include::field-formatters/url-formatter.asciidoc[] +The advanced setting `format:defaultLocale` affects the localization of numbers with these formats: `Default Number`, +`Percent`, `Short Number`, and `Currency`. The advanced setting `format:number:defaultLocale` affects the localization of +numbers with the `Bytes` and `Custom Numeral.js format`. -include::field-formatters/string-formatter.asciidoc[] +The `Custom Numeral.js format` enables customization of display using the https://adamwdraper.github.io/Numeral-js/[numeral.js] +standard format definitions. include::field-formatters/duration-formatter.asciidoc[] -include::field-formatters/color-formatter.asciidoc[] +include::field-formatters/url-formatter.asciidoc[] + +include::field-formatters/string-formatter.asciidoc[] -The `Bytes`, `Number`, and `Percentage` formatters enable you to choose the display formats of numbers in this field using -the https://adamwdraper.github.io/Numeral-js/[numeral.js] standard format definitions. +include::field-formatters/color-formatter.asciidoc[] [[scripted-fields]] === Scripted Fields @@ -65,7 +71,7 @@ the https://adamwdraper.github.io/Numeral-js/[numeral.js] standard format defini Scripted fields compute data on the fly from the data in your Elasticsearch indices. Scripted field data is shown on the Discover tab as part of the document data, and you can use scripted fields in your visualizations. Scripted field values are computed at query time so they aren't indexed and cannot be searched using Kibana's default -query language. However they can be queried using Kibana's new <>. Scripted +query language. However they can be queried using Kibana's new <>. Scripted fields are also supported in the filter bar. WARNING: Computing data on the fly with scripted fields can be very resource intensive and can have a direct impact on diff --git a/docs/user/reporting/development/pdf-integration.asciidoc b/docs/user/reporting/development/pdf-integration.asciidoc index dc9e63f34b25e..256477b019823 100644 --- a/docs/user/reporting/development/pdf-integration.asciidoc +++ b/docs/user/reporting/development/pdf-integration.asciidoc @@ -11,6 +11,7 @@ interface jobParameters { objectType: string; title: string; browserTimezone: string; + browserLocales: string[]; relativeUrls: string[]; layout: { id: string; @@ -23,6 +24,7 @@ interface jobParameters { ---- `jobParameters.browserTimezone` is a string that appears in the tz database +`jobParameters.browserLocales` is an ordered array of locale codes such as "en-US" `jobParameters.layout.id` presently only accepts "print" and "preserve_layout" `jobParameters.layout.dimensions` is only currently used by "preserve_layout" diff --git a/package.json b/package.json index 9707d3863d295..96bbbad276349 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "@elastic/eui": "18.2.1", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", - "@elastic/numeral": "2.3.3", + "@elastic/numeral": "2.3.5", "@elastic/request-crypto": "^1.0.2", "@elastic/ui-ace": "0.2.3", "@hapi/wreck": "^15.0.1", diff --git a/packages/kbn-i18n/src/angular/provider.ts b/packages/kbn-i18n/src/angular/provider.ts index 7333cf4bb5865..c0ba951e35385 100644 --- a/packages/kbn-i18n/src/angular/provider.ts +++ b/packages/kbn-i18n/src/angular/provider.ts @@ -31,6 +31,9 @@ export class I18nProvider implements angular.IServiceProvider { public setFormats = i18n.setFormats; public getFormats = i18n.getFormats; public getRegisteredLocales = i18n.getRegisteredLocales; + public getKnownCurrencies = i18n.getKnownCurrencies; + public getKnownLocales = i18n.getKnownLocales; + public getKnownLocalesWithDisplay = i18n.getKnownLocalesWithDisplay; public init = i18n.init; public load = i18n.load; public $get = () => i18n.translate; diff --git a/packages/kbn-i18n/src/core/i18n.ts b/packages/kbn-i18n/src/core/i18n.ts index c66555ab015dc..b21e93d3cc310 100644 --- a/packages/kbn-i18n/src/core/i18n.ts +++ b/packages/kbn-i18n/src/core/i18n.ts @@ -25,9 +25,11 @@ import { Translation } from '../translation'; import { Formats, formats as EN_FORMATS } from './formats'; import { hasValues, isObject, isString, mergeAll } from './helper'; import { isPseudoLocale, translateUsingPseudoLocale } from './pseudo_locale'; +import { knownLocales } from './known_locales'; // Add all locale data to `IntlMessageFormat`. -import './locales.js'; +// This has a side effect./register_localest of registering locale data to numeral-js +import './register_locales'; const EN_LOCALE = 'en'; const translationsForLocale: Record = {}; @@ -163,6 +165,278 @@ export function getRegisteredLocales() { return Object.keys(translationsForLocale); } +/** + * Top 35 most traded currencies per https://en.wikipedia.org/wiki/Template:Most_traded_currencies + * This list is not a full list of currencies, but the ISO standard full list of currencies + * does not provide some of the amenities of this Wiki list- a mashup of sources would be required + * to provide country name, currency name, and currency symbol. + * The full ISO reference: https://www.currency-iso.org/en/home/tables/table-a1.html + */ +export function getKnownCurrencies() { + return [ + { + name: translate('common.ui.fieldEditor.currency.currencies.USD', { + defaultMessage: 'United States dollar', + }), + code: 'USD', + symbol: 'US$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.EUR', { + defaultMessage: 'Euro', + }), + code: 'EUR', + symbol: '€', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.JPY', { + defaultMessage: 'Japanese yen', + }), + code: 'JPY', + symbol: '¥', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.GBP', { + defaultMessage: 'Pound sterling', + }), + code: 'GBP', + symbol: '£', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.AUD', { + defaultMessage: 'Australian dollar', + }), + code: 'AUD', + symbol: 'A$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.CAD', { + defaultMessage: 'Canadian dollar', + }), + code: 'CAD', + symbol: 'C$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.CHF', { + defaultMessage: 'Swiss franc', + }), + code: 'CHF', + symbol: 'CHF', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.CNY', { + defaultMessage: 'Renminbi', + }), + code: 'CNY', + symbol: '元', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.HKD', { + defaultMessage: 'Hong Kong dollar', + }), + code: 'HKD', + symbol: 'HK$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.NZD', { + defaultMessage: 'New Zealand dollar', + }), + code: 'NZD', + symbol: 'NZ$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.SEK', { + defaultMessage: 'Swedish krona', + }), + code: 'SEK', + symbol: 'kr', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.KRW', { + defaultMessage: 'South Korean won', + }), + code: 'KRW', + symbol: '₩', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.SGD', { + defaultMessage: 'Singapore dollar', + }), + code: 'SGD', + symbol: 'S$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.NOK', { + defaultMessage: 'Norwegian krone', + }), + code: 'NOK', + symbol: 'kr', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.MXN', { + defaultMessage: 'Mexican peso', + }), + code: 'MXN', + symbol: '$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.INR', { + defaultMessage: 'Indian rupee', + }), + code: 'INR', + symbol: '₹', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.RUB', { + defaultMessage: 'Russian ruble', + }), + code: 'RUB', + symbol: '₽', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.ZAR', { + defaultMessage: 'South African rand', + }), + code: 'ZAR', + symbol: 'R', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.TRY', { + defaultMessage: 'Turkish lira', + }), + code: 'TRY', + symbol: '₺', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.BRL', { + defaultMessage: 'Brazilian real', + }), + code: 'BRL', + symbol: 'R$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.TWD', { + defaultMessage: 'New Taiwan dollar', + }), + code: 'TWD', + symbol: 'NT$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.DKK', { + defaultMessage: 'Danish krone', + }), + code: 'DKK', + symbol: 'kr', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.PLN', { + defaultMessage: 'Polish zloty', + }), + code: 'PLN', + symbol: 'zł', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.THB', { + defaultMessage: 'Thai baht', + }), + code: 'THB', + symbol: '฿', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.IDR', { + defaultMessage: 'Indonesian rupiah', + }), + code: 'IDR', + symbol: 'Rp', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.HUF', { + defaultMessage: 'Hungarian forint', + }), + code: 'HUF', + symbol: 'Ft', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.CZK', { + defaultMessage: 'Czech koruna', + }), + code: 'CZK', + symbol: 'Kč', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.ILS', { + defaultMessage: 'Israeli new shekel', + }), + code: 'ILS', + symbol: '₪', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.CLP', { + defaultMessage: 'Chilean peso', + }), + code: 'CLP', + symbol: 'CLP$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.PHP', { + defaultMessage: 'Philippine peso', + }), + code: 'PHP', + symbol: '₱', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.AED', { + defaultMessage: 'UAE dirham', + }), + code: 'AED', + symbol: 'د.إ', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.COP', { + defaultMessage: 'Colombian peso', + }), + code: 'COP', + symbol: 'COL$', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.SAR', { + defaultMessage: 'Saudi riyal', + }), + code: 'SAR', + symbol: '﷼', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.MYR', { + defaultMessage: 'Malaysian ringgit', + }), + code: 'MYR', + symbol: 'RM', + }, + { + name: translate('common.ui.fieldEditor.currency.currencies.RON', { + defaultMessage: 'Romanian leu', + }), + code: 'RON', + symbol: 'L', + }, + ]; +} + +/** + * Returns array of locales that can be used with i18n, even if there is no translation. + * This could be used for number formatting. + */ +export function getKnownLocales() { + return Object.keys(knownLocales); +} + +/** + * Returns { [locale]: { name: [display], native: '', currency: '' } } map. + */ +export function getKnownLocalesWithDisplay() { + return knownLocales; +} + interface TranslateArguments { values?: Record; defaultMessage: string; diff --git a/packages/kbn-i18n/src/core/known_currencies.ts b/packages/kbn-i18n/src/core/known_currencies.ts new file mode 100644 index 0000000000000..50c079fa1ab2c --- /dev/null +++ b/packages/kbn-i18n/src/core/known_currencies.ts @@ -0,0 +1,166 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Top 35 most traded currencies per https://en.wikipedia.org/wiki/Template:Most_traded_currencies +// This list is not a full list of currencies, but the ISO standard full list of currencies +// does not provide some of the amenities of this Wiki list- a mashup of sources would be required +// to provide country name, currency name, and currency symbol. +// The full ISO reference: https://www.currency-iso.org/en/home/tables/table-a1.html +export const topCurrencies = [ + { + code: 'USD', + symbol: 'US$', + }, + { + code: 'EUR', + symbol: '€', + }, + { + code: 'JPY', + symbol: '¥', + }, + { + code: 'GBP', + symbol: '£', + }, + { + code: 'AUD', + symbol: 'A$', + }, + { + code: 'CAD', + symbol: 'C$', + }, + { + code: 'CHF', + symbol: 'CHF', + }, + { + code: 'CNY', + symbol: '元', + }, + { + code: 'HKD', + symbol: 'HK$', + }, + { + code: 'NZD', + symbol: 'NZ$', + }, + { + code: 'SEK', + symbol: 'kr', + }, + { + code: 'KRW', + symbol: '₩', + }, + { + code: 'SGD', + symbol: 'S$', + }, + { + code: 'NOK', + symbol: 'kr', + }, + { + code: 'MXN', + symbol: '$', + }, + { + code: 'INR', + symbol: '₹', + }, + { + code: 'RUB', + symbol: '₽', + }, + { + code: 'ZAR', + symbol: 'R', + }, + { + code: 'TRY', + symbol: '₺', + }, + { + code: 'BRL', + symbol: 'R$', + }, + { + code: 'TWD', + symbol: 'NT$', + }, + { + code: 'DKK', + symbol: 'kr', + }, + { + code: 'PLN', + symbol: 'zł', + }, + { + code: 'THB', + symbol: '฿', + }, + { + code: 'IDR', + symbol: 'Rp', + }, + { + code: 'HUF', + symbol: 'Ft', + }, + { + code: 'CZK', + symbol: 'Kč', + }, + { + code: 'ILS', + symbol: '₪', + }, + { + code: 'CLP', + symbol: 'CLP$', + }, + { + code: 'PHP', + symbol: '₱', + }, + { + code: 'AED', + symbol: 'د.إ', + }, + { + code: 'COP', + symbol: 'COL$', + }, + { + code: 'SAR', + symbol: '﷼', + }, + { + code: 'MYR', + symbol: 'RM', + }, + { + code: 'RON', + symbol: 'L', + }, +]; diff --git a/packages/kbn-i18n/src/core/known_locales.ts b/packages/kbn-i18n/src/core/known_locales.ts new file mode 100644 index 0000000000000..6cf37c81ca8ab --- /dev/null +++ b/packages/kbn-i18n/src/core/known_locales.ts @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const knownLocales = { + // Default + en: { name: 'English' }, + // Americas + 'en-US': { name: 'English (US)', currency: 'USD' }, + 'en-CA': { name: 'English (Canadian)', currency: 'CAD' }, + 'es-419': { name: 'Spanish (Latin America)', native: 'Español (Latinoamérica)' }, + 'fr-CA': { name: 'French (Canada)', native: 'Français (Canadien)', currency: 'CAD' }, + 'pt-BR': { name: 'Portuguese (Brazil)', native: 'Português (do Brasil)', currency: 'BRL' }, + // Asia Pacific + bn: { name: 'Bengali', native: 'বাংলা', currency: 'BDT' }, + fil: { name: 'Filipino', native: 'Pilipino', currency: 'PHP' }, + gu: { name: 'Gujarati', native: 'ગુજરાતી', currency: 'INR' }, + hi: { name: 'Hindi', native: 'हिन्दी', currency: 'INR' }, + id: { name: 'Indonesian', native: 'Bahasa Indonesia', currency: 'IDR' }, + ja: { name: 'Japanese', native: '日本語', currency: 'JPY' }, + kn: { name: 'Kannada', native: 'ಕನ್ನಡ', currency: 'INR' }, + ko: { name: 'Korean', native: '한국어', currency: 'KRW' }, + ml: { name: 'Malayalam', native: 'മലയാളം', currency: 'INR' }, + mr: { name: 'Marathi', native: 'मराठी', currency: 'INR' }, + ms: { name: 'Malay', native: 'Bahasa Melayu', currency: 'MYR' }, + si: { name: 'Sinhalese', native: 'සිංහල', currency: 'LKR' }, + ta: { name: 'Tamil', native: 'தமிழ்', currency: 'INR' }, + 'ta-LK': { name: 'Tamil (Sri Lanka)', native: 'தமிழ் (இலங்கை)', currency: 'LKR' }, + te: { name: 'Telugu', native: 'తెలుగు', currency: 'INR' }, + th: { name: 'Thai', native: 'ไทย', currency: 'THB' }, + vi: { name: 'Vietnamese', native: 'Tiếng Việt', currency: 'VND' }, + 'zh-CN': { name: 'Chinese (Simplified)', native: '中文 (简体)', currency: 'CNY' }, + 'zh-TW': { name: 'Chinese (Traditional)', native: '正體中文 (繁體)', currency: 'CNY' }, + // Europe + 'en-GB': { name: 'English (British)', native: '', currency: 'GBP' }, + bg: { name: 'Bulgarian', native: 'Български', currency: 'BGN' }, + ca: { name: 'Catalan', native: 'català', currency: 'EUR' }, + cs: { name: 'Czech', native: 'Čeština', currency: 'CZK' }, + da: { name: 'Danish', native: 'Dansk', currency: 'DKK' }, + de: { name: 'German', native: 'Deutsch', currency: 'EUR' }, + el: { name: 'Greek', native: 'Ελληνικά', currency: 'EUR' }, + es: { name: 'Spanish', native: 'Español', currency: 'EUR' }, + et: { name: 'Estonian', native: 'eesti keel', currency: 'EUR' }, + fi: { name: 'Finnish', native: 'suomi', currency: 'EUR' }, + fr: { name: 'French', native: 'Français', currency: 'EUR' }, + hr: { name: 'Croatian', native: 'Hrvatski', currency: 'HRK' }, + hu: { name: 'Hungarian', native: 'Magyar', currency: 'HUF' }, + it: { name: 'Italian', native: 'Italiano', currency: 'EUR' }, + lt: { name: 'Lithuanian', native: 'lietuvių kalba', currency: 'EUR' }, + lv: { name: 'Latvian', native: 'Latv', currency: 'EUR' }, + no: { name: 'Norwegian', native: 'Norsk', currency: 'NOK' }, + 'nl-NL': { name: 'Dutch (Netherlands)', native: 'Nederlands', currency: 'EUR' }, + // Dutch (Belgium) has the wrong code in numeral.js, indicating the locale is in Belarus instead of Belgium + 'nl-BE': { name: 'Dutch (Belgium)', native: 'Belgisch-Nederlands', currency: 'EUR' }, + pl: { name: 'Polish', native: 'Polski', currency: 'PLN' }, + 'pt-PT': { name: 'Portuguese (Portugal)', native: 'Português (Europeu)', currency: 'EUR' }, + ro: { name: 'Romanian', native: 'română', currency: 'RON' }, + ru: { name: 'Russian', native: 'Русский', currency: 'RUB' }, + sk: { name: 'Slovak', native: 'slovenčina', currency: 'EUR' }, + sl: { name: 'Slovenian', native: 'slovenščina', currency: 'EUR' }, + sr: { name: 'Serbian', native: 'Српски', currency: 'RSD' }, + sv: { name: 'Swedish', native: 'Svenska', currency: 'SEK' }, + tr: { name: 'Turkish', native: 'Türkçe', currency: 'TRY' }, + uk: { name: 'Ukrainian', native: 'Українська', currency: 'UAH' }, + // Middle East and Africa + ar: { name: 'Arabic', native: 'عربي' }, + fa: { name: 'Persian', native: 'فارسی' }, + he: { name: 'Hebrew', native: 'עברית', currency: 'ILS' }, + sw: { name: 'Swahili', native: 'Kiswahili' }, +}; diff --git a/packages/kbn-i18n/src/core/locales.js b/packages/kbn-i18n/src/core/register_locales.js similarity index 100% rename from packages/kbn-i18n/src/core/locales.js rename to packages/kbn-i18n/src/core/register_locales.js diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js index 29b6e632d19fd..64895a2580a42 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.js @@ -463,25 +463,58 @@ function migrateSubTypeAndParentFieldProperties(doc) { if (!doc.attributes.fields) return doc; const fieldsString = doc.attributes.fields; - const fields = JSON.parse(fieldsString); - const migratedFields = fields.map(field => { - if (field.subType === 'multi') { - return { - ...omit(field, 'parent'), - subType: { multi: { parent: field.parent } }, - }; - } + try { + const fields = JSON.parse(fieldsString); + const migratedFields = fields.map(field => { + if (field.subType === 'multi') { + return { + ...omit(field, 'parent'), + subType: { multi: { parent: field.parent } }, + }; + } - return field; - }); + return field; + }); - return { - ...doc, - attributes: { - ...doc.attributes, - fields: JSON.stringify(migratedFields), - }, - }; + return { + ...doc, + attributes: { + ...doc.attributes, + fields: JSON.stringify(migratedFields), + }, + }; + } catch (e) { + return doc; + } +} + +function migrateNumeralFieldFormatters(doc) { + if (!doc.attributes.fieldFormatMap) return doc; + + const fieldFormatMap = doc.attributes.fieldFormatMap; + try { + const fieldFormats = JSON.parse(fieldFormatMap); + + const newFormats = Object.fromEntries( + Object.entries(fieldFormats).map(([field, format]) => { + if (format.pattern && (format.id === 'bytes' || format.id === 'percent')) { + // Migrate custom numeral.js patterns to 'number' + return [field, { ...format, id: 'number' }]; + } + return [field, format]; + }) + ); + + return { + ...doc, + attributes: { + ...doc.attributes, + fieldFormatMap: JSON.stringify(newFormats), + }, + }; + } catch (e) { + return doc; + } } const executeMigrations720 = flow( @@ -508,7 +541,7 @@ export const migrations = { doc.attributes.typeMeta = doc.attributes.typeMeta || undefined; return doc; }, - '7.6.0': flow(migrateSubTypeAndParentFieldProperties), + '7.6.0': flow(migrateSubTypeAndParentFieldProperties, migrateNumeralFieldFormatters), }, visualization: { /** diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/legacy/core_plugins/kibana/migrations/migrations.test.js index e39bc59201e7f..fa940e0dbf866 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.test.js @@ -78,6 +78,36 @@ Object { expect(migrate(input)).toEqual(expected); }); + + it('should handle malformed fieldFormatMap', () => { + const input = { + attributes: { + title: 'test', + fieldFormatMap: '{"nochange":{"id":"percen', + }, + }; + + expect(migrate(input)).toEqual(input); + }); + + it('should rewrite fieldFormatMap if there are custom numeraljs patterns', () => { + const input = { + attributes: { + title: 'test', + fieldFormatMap: + '{"nochange":{"id":"percent"},"change":{"id":"bytes","pattern":"0.[000]b"}}', + }, + }; + const expected = { + attributes: { + title: 'test', + fieldFormatMap: + '{"nochange":{"id":"percent"},"change":{"id":"number","pattern":"0.[000]b"}}', + }, + }; + + expect(migrate(input)).toEqual(expected); + }); }); }); diff --git a/src/legacy/core_plugins/kibana/server/field_formats/register.js b/src/legacy/core_plugins/kibana/server/field_formats/register.js index 34dc06aab44ac..2813b81994e89 100644 --- a/src/legacy/core_plugins/kibana/server/field_formats/register.js +++ b/src/legacy/core_plugins/kibana/server/field_formats/register.js @@ -20,7 +20,10 @@ import { UrlFormat, StringFormat, - NumberFormat, + DefaultNumberFormat, + CustomNumberFormat, + CurrencyFormat, + ShortNumberFormat, BytesFormat, TruncateFormat, RelativeDateFormat, @@ -43,8 +46,11 @@ export function registerFieldFormats(server) { server.registerFieldFormat(RelativeDateFormat); server.registerFieldFormat(DurationFormat); server.registerFieldFormat(IpFormat); - server.registerFieldFormat(NumberFormat); + server.registerFieldFormat(DefaultNumberFormat); + server.registerFieldFormat(CustomNumberFormat); server.registerFieldFormat(PercentFormat); + server.registerFieldFormat(CurrencyFormat); + server.registerFieldFormat(ShortNumberFormat); server.registerFieldFormat(StringFormat); server.registerFieldFormat(SourceFormat); server.registerFieldFormat(ColorFormat); diff --git a/src/legacy/core_plugins/kibana/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/ui_setting_defaults.js index 9b848666541ce..ca5ad76fdde9d 100644 --- a/src/legacy/core_plugins/kibana/ui_setting_defaults.js +++ b/src/legacy/core_plugins/kibana/ui_setting_defaults.js @@ -22,6 +22,8 @@ import numeralLanguages from '@elastic/numeral/languages'; import { i18n } from '@kbn/i18n'; import { DEFAULT_QUERY_LANGUAGE } from '../../../plugins/data/common'; +const topCurrencies = i18n.getKnownCurrencies(); + export function getUiSettingDefaults() { const weekdays = moment.weekdays().slice(); const [defaultWeekday] = weekdays; @@ -34,6 +36,28 @@ export function getUiSettingDefaults() { }), ]; + const locales = ['numeral', 'detect', ...i18n.getKnownLocales()]; + const localeDisplay = Object.fromEntries( + [ + [ + 'numeral', + i18n.translate('kbn.advancedSettings.locale.useNumeralLocale', { + defaultMessage: 'Use the same value as format:number:defaultLocale', + }), + ], + [ + 'detect', + i18n.translate('kbn.advancedSettings.locale.detectFromBrowserLabel', { + defaultMessage: 'Detect locale from browser locale', + }), + ], + ].concat( + Object.entries(i18n.getKnownLocalesWithDisplay()).map(([id, { name, native }]) => { + return [id, native ? `${native} - ${name}` : name]; + }) + ) + ); + const luceneQueryLanguageLabel = i18n.translate( 'kbn.advancedSettings.searchQueryLanguageLucene', { @@ -708,7 +732,7 @@ export function getUiSettingDefaults() { "ip": { "id": "ip", "params": {} }, "date": { "id": "date", "params": {} }, "date_nanos": { "id": "date_nanos", "params": {}, "es": true }, - "number": { "id": "number", "params": {} }, + "number": { "id": "default_number", "params": {} }, "boolean": { "id": "boolean", "params": {} }, "_source": { "id": "_source", "params": {} }, "_default_": { "id": "string", "params": {} } @@ -723,110 +747,96 @@ export function getUiSettingDefaults() { }, }), }, - 'format:number:defaultPattern': { - name: i18n.translate('kbn.advancedSettings.format.numberFormatTitle', { - defaultMessage: 'Number format', + 'format:defaultLocale': { + name: i18n.translate('kbn.advancedSettings.format.formattingLocaleTitle', { + defaultMessage: 'Default number display locale', }), - value: '0,0.[000]', - type: 'string', - description: i18n.translate('kbn.advancedSettings.format.numberFormatText', { - defaultMessage: 'Default {numeralFormatLink} for the "number" format', - description: - 'Part of composite text: kbn.advancedSettings.format.numberFormatText + ' + - 'kbn.advancedSettings.format.numberFormat.numeralFormatLinkText', - values: { - numeralFormatLink: - '' + - i18n.translate('kbn.advancedSettings.format.numberFormat.numeralFormatLinkText', { - defaultMessage: 'numeral format', - }) + - '', - }, + value: 'numeral', + type: 'select', + options: locales, + optionLabels: localeDisplay, + description: i18n.translate('kbn.advancedSettings.format.formattingLocaleText', { + defaultMessage: `This locale will affect number formatting for most formatters.`, }), }, - 'format:bytes:defaultPattern': { - name: i18n.translate('kbn.advancedSettings.format.bytesFormatTitle', { - defaultMessage: 'Bytes format', + 'format:default_number:maxDecimals': { + name: i18n.translate('kbn.advancedSettings.format.default_number.maxDecimalsNumber', { + defaultMessage: 'Number maximum decimal places', }), - value: '0,0.[0]b', - type: 'string', - description: i18n.translate('kbn.advancedSettings.format.bytesFormatText', { - defaultMessage: 'Default {numeralFormatLink} for the "bytes" format', - description: - 'Part of composite text: kbn.advancedSettings.format.bytesFormatText + ' + - 'kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText', - values: { - numeralFormatLink: - '' + - i18n.translate('kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText', { - defaultMessage: 'numeral format', - }) + - '', - }, + value: 3, + type: 'number', + description: i18n.translate( + 'kbn.advancedSettings.format.default_number.maxDecimalsDescription', + { + defaultMessage: `Does not affect currency, bytes, or any formatter using numeral.js`, + } + ), + }, + 'format:currency:maxDecimals': { + name: i18n.translate('kbn.advancedSettings.format.currency.maxDecimalsNumber', { + defaultMessage: 'Currency maximum decimal places', }), + value: 2, + type: 'number', }, - 'format:percent:defaultPattern': { - name: i18n.translate('kbn.advancedSettings.format.percentFormatTitle', { - defaultMessage: 'Percent format', + 'format:number:defaultLocale': { + name: i18n.translate('kbn.advancedSettings.format.number.formattingLocaleTitle', { + defaultMessage: 'Numeral.js formatting locale', }), - value: '0,0.[000]%', - type: 'string', - description: i18n.translate('kbn.advancedSettings.format.percentFormatText', { - defaultMessage: 'Default {numeralFormatLink} for the "percent" format', + value: 'en', + type: 'select', + options: numeralLanguageIds, + optionLabels: Object.fromEntries( + numeralLanguages.map(language => [language.id, language.name]) + ), + description: i18n.translate('kbn.advancedSettings.format.number.formattingLocaleText', { + defaultMessage: `{numeralLanguageLink} locale. This affects the custom numeral.js formatter and the bytes formatter.`, description: - 'Part of composite text: kbn.advancedSettings.format.percentFormatText + ' + - 'kbn.advancedSettings.format.percentFormat.numeralFormatLinkText', + 'Part of composite text: kbn.advancedSettings.format.number.formattingLocaleText + ' + + 'kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText', values: { - numeralFormatLink: + numeralLanguageLink: '' + - i18n.translate('kbn.advancedSettings.format.percentFormat.numeralFormatLinkText', { - defaultMessage: 'numeral format', + i18n.translate('kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText', { + defaultMessage: 'Numeral language', }) + '', }, }), }, - 'format:currency:defaultPattern': { - name: i18n.translate('kbn.advancedSettings.format.currencyFormatTitle', { - defaultMessage: 'Currency format', + 'format:number:defaultPattern': { + name: i18n.translate('kbn.advancedSettings.format.numberFormatTitle', { + defaultMessage: 'numeral.js default pattern', }), - value: '($0,0.[00])', + value: '0,0.[000]', type: 'string', - description: i18n.translate('kbn.advancedSettings.format.currencyFormatText', { - defaultMessage: 'Default {numeralFormatLink} for the "currency" format', + description: i18n.translate('kbn.advancedSettings.format.numberFormatText', { + defaultMessage: + 'Default {numeralFormatLink} for the "Custom numeral.js" format. Does not affect other formats.', description: - 'Part of composite text: kbn.advancedSettings.format.currencyFormatText + ' + - 'kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText', + 'Part of composite text: kbn.advancedSettings.format.numberFormatText + ' + + 'kbn.advancedSettings.format.numberFormat.numeralFormatLinkText', values: { numeralFormatLink: '' + - i18n.translate('kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText', { + i18n.translate('kbn.advancedSettings.format.numberFormat.numeralFormatLinkText', { defaultMessage: 'numeral format', }) + '', }, }), }, - 'format:number:defaultLocale': { - name: i18n.translate('kbn.advancedSettings.format.formattingLocaleTitle', { - defaultMessage: 'Formatting locale', + 'format:currency:defaultCurrency': { + name: i18n.translate('kbn.advancedSettings.format.currencyTitle', { + defaultMessage: 'Default currency', }), - value: 'en', + value: 'USD', type: 'select', - options: numeralLanguageIds, - description: i18n.translate('kbn.advancedSettings.format.formattingLocaleText', { - defaultMessage: `{numeralLanguageLink} locale`, - description: - 'Part of composite text: kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText + ' + - 'kbn.advancedSettings.format.formattingLocaleText', - values: { - numeralLanguageLink: - '' + - i18n.translate('kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText', { - defaultMessage: 'Numeral language', - }) + - '', - }, + options: topCurrencies.map(c => c.code), + optionLabels: Object.fromEntries(topCurrencies.map(c => [c.code, `${c.name} (${c.symbol})`])), + description: i18n.translate('kbn.advancedSettings.format.currencyDescription', { + defaultMessage: + 'The currency used for all currency formatting by default. Currency can be changed for a single field using the index management section. If a currency is unlisted here, it can also be set in the index management section.', }), }, 'savedObjects:perPage': { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js index 76d3cff17343e..8dde64e75b2c0 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js @@ -56,17 +56,13 @@ describe('createTickFormatter(format, template)', () => { }); test('returns a percent with percent formatter', () => { - const config = { - 'format:percent:defaultPattern': '0.[00]%', - }; + const config = {}; const fn = createTickFormatter('percent', null, key => config[key]); expect(fn(0.5556)).toEqual('55.56%'); }); - test('returns a byte formatted string with byte formatter', () => { - const config = { - 'format:bytes:defaultPattern': '0.0b', - }; + it('returns a byte formatted string with byte formatter', () => { + const config = {}; const fn = createTickFormatter('bytes', null, key => config[key]); expect(fn(1500 ^ 10)).toEqual('1.5KB'); }); diff --git a/src/legacy/ui/field_formats/mixin/field_formats_service.test.ts b/src/legacy/ui/field_formats/mixin/field_formats_service.test.ts index 4ca181d3f69d4..10e3012f7e0e7 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_service.test.ts +++ b/src/legacy/ui/field_formats/mixin/field_formats_service.test.ts @@ -18,13 +18,13 @@ */ import { FieldFormatsService } from './field_formats_service'; -import { NumberFormat } from '../../../../plugins/data/public'; +import { DefaultNumberFormat, CustomNumberFormat } from '../../../../plugins/data/public'; const getConfig = (key: string) => { switch (key) { case 'format:defaultTypeMap': return { - number: { id: 'number', params: {} }, + number: { id: 'default_number', params: {} }, _default_: { id: 'string', params: {} }, }; case 'format:number:defaultPattern': @@ -36,7 +36,7 @@ describe('FieldFormatsService', () => { let fieldFormatsService: FieldFormatsService; beforeEach(() => { - const fieldFormatClasses = [NumberFormat]; + const fieldFormatClasses = [DefaultNumberFormat, CustomNumberFormat]; fieldFormatsService = new FieldFormatsService(fieldFormatClasses, getConfig); }); @@ -50,7 +50,7 @@ describe('FieldFormatsService', () => { test('getDefaultInstance returns default FieldFormat instance for fieldType', () => { const instance = fieldFormatsService.getDefaultInstance('number'); - expect(instance.type.id).toBe('number'); + expect(instance.type.id).toBe('default_number'); expect(instance.convert('0.33333')).toBe('0.333'); }); }); diff --git a/src/legacy/ui/field_formats/mixin/field_formats_service.ts b/src/legacy/ui/field_formats/mixin/field_formats_service.ts index c5bc25333985b..6c00df854271d 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_service.ts +++ b/src/legacy/ui/field_formats/mixin/field_formats_service.ts @@ -63,7 +63,6 @@ export class FieldFormatsService { * @return {FieldFormat} */ getInstance(conf: FieldFormatConfig): FieldFormat { - // @ts-ignore return new this._fieldFormats[conf.id](conf.params, this.getConfig); } diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts index d07cf84aef4d9..a967ce6ab16f0 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts @@ -68,7 +68,7 @@ describe('AggConfig Filters', () => { expect(filter.range).toHaveProperty('bytes'); expect(filter.range.bytes).toHaveProperty('gte', 2048); expect(filter.range.bytes).toHaveProperty('lt', 3072); - expect(filter.meta).toHaveProperty('formattedValue', '2,048'); + expect(filter.meta).toHaveProperty('formattedValue', '2KB'); }); }); }); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts index dc02b773edc42..d8ada0b9544f1 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts @@ -72,7 +72,7 @@ describe('AggConfig Filters', () => { expect(filter.range).toHaveProperty('bytes'); expect(filter.range.bytes).toHaveProperty('gte', 1024.0); expect(filter.range.bytes).toHaveProperty('lt', 2048.0); - expect(filter.meta).toHaveProperty('formattedValue', '≥ 1,024 and < 2,048'); + expect(filter.meta).toHaveProperty('formattedValue', '≥ 1KB and < 2KB'); }); }); }); diff --git a/src/legacy/ui/public/agg_types/buckets/range.test.ts b/src/legacy/ui/public/agg_types/buckets/range.test.ts index 5db7eb3c2d8e9..6acdff76d24af 100644 --- a/src/legacy/ui/public/agg_types/buckets/range.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/range.test.ts @@ -19,7 +19,7 @@ import { AggConfigs } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { NumberFormat } from '../../../../../plugins/data/public'; +import { BytesFormat } from '../../../../../plugins/data/public'; jest.mock('ui/new_platform'); @@ -47,12 +47,7 @@ describe('Range Agg', () => { const getAggConfigs = () => { const field = { name: 'bytes', - format: new NumberFormat( - { - pattern: '0,0.[000] b', - }, - () => {} - ), + format: new BytesFormat({}, () => {}), }; const indexPattern = { @@ -90,9 +85,9 @@ describe('Range Agg', () => { const format = (val: any) => agg.fieldFormatter()(agg.getKey(val)); - expect(format(buckets[0])).toBe('≥ -∞ and < 1 KB'); - expect(format(buckets[1])).toBe('≥ 1 KB and < 2.5 KB'); - expect(format(buckets[2])).toBe('≥ 2.5 KB and < +∞'); + expect(format(buckets[0])).toBe('≥ -∞ and < 1KB'); + expect(format(buckets[1])).toBe('≥ 1KB and < 2.5KB'); + expect(format(buckets[2])).toBe('≥ 2.5KB and < +∞'); }); }); }); diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.js.snap index 1f77660c9784c..9d60e0142a713 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.js.snap @@ -2,53 +2,6 @@ exports[`BytesFormatEditor should render normally 1`] = ` - - - -   - - - - } - isInvalid={false} - label={ - - 0,0.[000]b - , - } - } - /> - } - labelType="label" - > - - + + + ); + } } diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/__snapshots__/currency.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/__snapshots__/currency.test.js.snap new file mode 100644 index 0000000000000..66616b20c1502 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/__snapshots__/currency.test.js.snap @@ -0,0 +1,255 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CurrencyFormatEditor should render normally 1`] = ` + + + } + labelType="label" + > + + + + } + labelType="label" + > + + + + } + labelType="label" + > + + + + +`; diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/currency.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/currency.js new file mode 100644 index 0000000000000..3271e416f7ca3 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/currency.js @@ -0,0 +1,125 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFormRow, EuiComboBox, EuiFieldText } from '@elastic/eui'; + +import { DefaultNumberFormatEditor } from '../default_number'; +import { FormatEditorSamples } from '../../samples'; + +const topCurrencies = i18n.getKnownCurrencies(); + +export class CurrencyFormatEditor extends DefaultNumberFormatEditor { + static formatId = 'currency'; + + constructor(props) { + super(props); + + this.state = { + ...this.state, + sampleInputs: [1234, 99.9999, 5150000.0001, 0.00005], + hasOther: false, + }; + } + + getCurrencyName(currency) { + return `${currency.name} (${currency.code}) ${currency.symbol}`; + } + + render() { + const { formatParams } = this.props; + const { samples } = this.state; + const currencyCode = formatParams.currencyCode; + const currencyMatch = topCurrencies.find(cur => cur.code === currencyCode); + let currencyLabel = currencyCode; + if (currencyMatch) { + currencyLabel = this.getCurrencyName(currencyMatch); + } + + const otherLabel = { + value: null, + label: i18n.translate('common.ui.fieldEditor.currency.otherCurrencyLabel', { + defaultMessage: 'Other currency', + }), + }; + + return ( + + + } + > + ({ + value: cur.code, + label: this.getCurrencyName(cur), + })) + .concat([otherLabel])} + onChange={choices => { + if (choices[0].value) { + // There is no value for the "Other" currency + this.onChange({ currencyCode: choices[0].value }); + this.setState({ hasOther: false }); + } else { + this.setState({ hasOther: true }); + } + }} + /> + + + {this.state.hasOther ? ( + + { + this.onChange({ currencyCode: e.target.value ? e.target.value.toUpperCase() : '' }); + }} + /> + + ) : null} + + {this.renderDecimalSelector()} + + + + ); + } +} diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/currency.test.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/currency.test.js new file mode 100644 index 0000000000000..736d27bbe2987 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/currency.test.js @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { CurrencyFormatEditor } from './currency'; + +const fieldType = 'number'; +const format = { + getConverterFor: jest.fn().mockImplementation(() => input => input * 2), + getParamDefaults: jest.fn().mockImplementation(() => { + return { currencyCode: 'EUR' }; + }), +}; +const formatParams = {}; +const onChange = jest.fn(); +const onError = jest.fn(); + +describe('CurrencyFormatEditor', () => { + it('should have a formatId', () => { + expect(CurrencyFormatEditor.formatId).toEqual('currency'); + }); + + it('should render normally', async () => { + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + + it('should error if an incomplete other currency is added', async () => { + const component = shallow( + + ); + const options = component.find('[data-test-subj="fieldEditor-currency-currencies"]'); + options.prop('onChange')([{ value: null }]); + + const otherInput = component.find('[data-test-subj="fieldEditor-currency-otherInput"]'); + otherInput.prop('onChange')({ target: { value: 'AB' } }); + expect(onError).toHaveBeenCalled(); + + onError.mockClear(); + + otherInput.prop('onChange')({ target: { value: 'ABC' } }); + expect(onError).not.toHaveBeenCalled(); + }); +}); diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/index.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/index.js new file mode 100644 index 0000000000000..126c4c1c56980 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/currency/index.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { CurrencyFormatEditor } from './currency'; diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/__snapshots__/default_number.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/__snapshots__/default_number.test.js.snap new file mode 100644 index 0000000000000..85f0931715bf8 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/__snapshots__/default_number.test.js.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DefaultNumberFormatEditor should render normally 1`] = ` + + + } + labelType="label" + > + + + + } + labelType="label" + > + + + + +`; diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/default_number.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/default_number.js new file mode 100644 index 0000000000000..bfc4f4421e228 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/default_number.js @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Fragment } from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; + +import { DefaultFormatEditor } from '../default'; + +import { FormatEditorSamples } from '../../samples'; + +export class DefaultNumberFormatEditor extends DefaultFormatEditor { + static formatId = 'default_number'; + + constructor(props) { + super(props); + this.state = { + ...this.state, + sampleInputs: [ + 10000, + 12.345678, + -1, + -999, + 0.52, + 0.00000000000000123456789, + 19900000000000000000000, + ], + }; + } + + renderDecimalSelector = () => { + const { formatParams } = this.props; + + return ( + <> + + } + > + { + this.onChange({ minDecimals: Number(e.target.value) || 0 }); + }} + /> + + + + } + > + { + this.onChange({ maxDecimals: Number(e.target.value) || 0 }); + }} + /> + + + ); + }; + + render() { + const { samples } = this.state; + + return ( + + {this.renderDecimalSelector()} + + + + ); + } +} diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/default_number.test.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/default_number.test.js new file mode 100644 index 0000000000000..0f850c85254b3 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/default_number.test.js @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DefaultNumberFormatEditor } from './default_number'; + +const fieldType = 'number'; +const format = { + getConverterFor: jest.fn().mockImplementation(() => input => input * 2), + getParamDefaults: jest.fn().mockImplementation(() => { + return {}; + }), +}; +const formatParams = {}; +const onChange = jest.fn(); +const onError = jest.fn(); + +describe('DefaultNumberFormatEditor', () => { + it('should have a formatId', () => { + expect(DefaultNumberFormatEditor.formatId).toEqual('default_number'); + }); + + it('should render normally', async () => { + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/index.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/index.js new file mode 100644 index 0000000000000..e5c11a1752585 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/default_number/index.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { DefaultNumberFormatEditor } from './default_number'; diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/__snapshots__/number.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/__snapshots__/number.test.js.snap index f0c331d808a00..02771895d90e5 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/__snapshots__/number.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/__snapshots__/number.test.js.snap @@ -73,6 +73,14 @@ exports[`NumberFormatEditor should render normally 1`] = ` "input": 0.52, "output": 1.04, }, + Object { + "input": 1.23456789e-15, + "output": 2.46913578e-15, + }, + Object { + "input": 1.99e+22, + "output": 3.98e+22, + }, ] } /> diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/index.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/index.js index a3317d93cdd80..56ce9efa3ca69 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/index.js +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/index.js @@ -17,4 +17,4 @@ * under the License. */ -export { NumberFormatEditor } from './number'; +export { CustomNumberFormatEditor } from './number'; diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/number.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/number.js index 509508590780f..99f21f580852a 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/number.js +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/number.js @@ -27,12 +27,20 @@ import { FormatEditorSamples } from '../../samples'; import { FormattedMessage } from '@kbn/i18n/react'; -export class NumberFormatEditor extends DefaultFormatEditor { +export class CustomNumberFormatEditor extends DefaultFormatEditor { static formatId = 'number'; constructor(props) { super(props); - this.state.sampleInputs = [10000, 12.345678, -1, -999, 0.52]; + this.state.sampleInputs = [ + 10000, + 12.345678, + -1, + -999, + 0.52, + 0.00000000000000123456789, + 19900000000000000000000, + ]; } render() { @@ -74,6 +82,7 @@ export class NumberFormatEditor extends DefaultFormatEditor { isInvalid={!!error} /> + ); diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/number.test.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/number.test.js index 9d97feaa75a6a..e568004b150ae 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/number.test.js +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/number/number.test.js @@ -20,7 +20,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { NumberFormatEditor } from './number'; +import { CustomNumberFormatEditor } from './number'; const fieldType = 'number'; const format = { @@ -35,12 +35,12 @@ const onError = jest.fn(); describe('NumberFormatEditor', () => { it('should have a formatId', () => { - expect(NumberFormatEditor.formatId).toEqual('number'); + expect(CustomNumberFormatEditor.formatId).toEqual('number'); }); it('should render normally', async () => { const component = shallow( - - - -   - - - + label={ + } - isInvalid={false} + labelType="label" + > + + + - 0,0.[000]% - , - } - } + defaultMessage="Maximum decimal places" + id="common.ui.fieldEditor.defaultNumber.maxDecimalPlacesLabel" + values={Object {}} /> } labelType="label" > - + {this.renderDecimalSelector()} + + + + ); + } } diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/short_number/index.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/short_number/index.js new file mode 100644 index 0000000000000..0e0bf0a691087 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/short_number/index.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ShortNumberFormatEditor } from './short_number'; diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/short_number/short_number.js b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/short_number/short_number.js new file mode 100644 index 0000000000000..a60f7151b34b5 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/short_number/short_number.js @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Fragment } from 'react'; +import { DefaultNumberFormatEditor } from '../default_number'; +import { FormatEditorSamples } from '../../samples'; + +export class ShortNumberFormatEditor extends DefaultNumberFormatEditor { + static formatId = 'short_number'; + + constructor(props) { + super(props); + + this.state = { + ...this.state, + sampleInputs: [1234, 99.9999, 5150000.0001, 0.00005, 199000000], + }; + } + + render() { + const { samples } = this.state; + + return ( + + + + ); + } +} diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/get_editors.js b/src/legacy/ui/public/field_editor/components/field_format_editor/get_editors.js new file mode 100644 index 0000000000000..33eba864711d0 --- /dev/null +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/get_editors.js @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BytesFormatEditor } from './editors/bytes'; +import { ColorFormatEditor } from './editors/color'; +import { CurrencyFormatEditor } from './editors/currency'; +import { DateFormatEditor } from './editors/date'; +import { DateNanosFormatEditor } from './editors/date_nanos'; +import { DefaultNumberFormatEditor } from './editors/default_number'; +import { CustomNumberFormatEditor } from './editors/number'; +import { DurationFormatEditor } from './editors/duration'; +import { ShortNumberFormatEditor } from './editors/short_number'; +import { PercentFormatEditor } from './editors/percent'; +import { StaticLookupFormatEditor } from './editors/static_lookup'; +import { StringFormatEditor } from './editors/string'; +import { TruncateFormatEditor } from './editors/truncate'; +import { UrlFormatEditor } from './editors/url/url'; + +export const editors = [ + BytesFormatEditor, + ColorFormatEditor, + CurrencyFormatEditor, + CustomNumberFormatEditor, + DateFormatEditor, + DateNanosFormatEditor, + DefaultNumberFormatEditor, + DurationFormatEditor, + PercentFormatEditor, + ShortNumberFormatEditor, + StaticLookupFormatEditor, + StringFormatEditor, + TruncateFormatEditor, + UrlFormatEditor, +]; diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/index.js b/src/legacy/ui/public/field_editor/components/field_format_editor/index.js index ccfc98b2d5882..acdd8ba565d33 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/index.js +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/index.js @@ -18,3 +18,4 @@ */ export { FieldFormatEditor } from './field_format_editor'; +export { editors } from './get_editors'; diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/register.js b/src/legacy/ui/public/field_editor/components/field_format_editor/register.js index 3062a3ba8ac14..93c8d75feafb5 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/register.js +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/register.js @@ -18,26 +18,6 @@ */ import { RegistryFieldFormatEditorsProvider } from 'ui/registry/field_format_editors'; -import { BytesFormatEditor } from './editors/bytes'; -import { ColorFormatEditor } from './editors/color'; -import { DateFormatEditor } from './editors/date'; -import { DateNanosFormatEditor } from './editors/date_nanos'; -import { DurationFormatEditor } from './editors/duration'; -import { NumberFormatEditor } from './editors/number'; -import { PercentFormatEditor } from './editors/percent'; -import { StaticLookupFormatEditor } from './editors/static_lookup'; -import { StringFormatEditor } from './editors/string'; -import { TruncateFormatEditor } from './editors/truncate'; -import { UrlFormatEditor } from './editors/url/url'; +import { editors } from './get_editors'; -RegistryFieldFormatEditorsProvider.register(() => BytesFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => ColorFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => DateFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => DateNanosFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => DurationFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => NumberFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => PercentFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => StaticLookupFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => StringFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => TruncateFormatEditor); -RegistryFieldFormatEditorsProvider.register(() => UrlFormatEditor); +editors.forEach(editor => RegistryFieldFormatEditorsProvider.register(() => editor)); diff --git a/src/legacy/ui/public/field_editor/lib/__tests__/get_default_format.test.js b/src/legacy/ui/public/field_editor/lib/__tests__/get_default_format.test.js index e4084797c1b1e..e7bf431032dc2 100644 --- a/src/legacy/ui/public/field_editor/lib/__tests__/get_default_format.test.js +++ b/src/legacy/ui/public/field_editor/lib/__tests__/get_default_format.test.js @@ -18,7 +18,7 @@ */ import { getDefaultFormat } from '../get_default_format'; -import { NumberFormat } from '../../../../../../plugins/data/public'; +import { DefaultNumberFormat } from '../../../../../../plugins/data/public'; const getConfig = () => { return '0,0.[000]'; @@ -26,12 +26,12 @@ const getConfig = () => { describe('getDefaultFormat', () => { it('should create default format', () => { - const DefaultFormat = getDefaultFormat(NumberFormat); + const DefaultFormat = getDefaultFormat(DefaultNumberFormat); const defaultFormatObject = new DefaultFormat(null, getConfig); - const formatObject = new NumberFormat(null, getConfig); + const formatObject = new DefaultNumberFormat(null, getConfig); expect(DefaultFormat.id).toEqual(''); - expect(DefaultFormat.resolvedTitle).toEqual(NumberFormat.title); + expect(DefaultFormat.resolvedTitle).toEqual(DefaultNumberFormat.title); expect(DefaultFormat.title).toEqual('- Default -'); expect(JSON.stringify(defaultFormatObject.params())).toEqual( JSON.stringify(formatObject.params()) diff --git a/src/plugins/data/common/field_formats/converters/boolean.ts b/src/plugins/data/common/field_formats/converters/boolean.ts index 6cc6c71465d50..b2ef6f21202a2 100644 --- a/src/plugins/data/common/field_formats/converters/boolean.ts +++ b/src/plugins/data/common/field_formats/converters/boolean.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; @@ -24,7 +25,9 @@ import { asPrettyString } from '../utils'; export class BoolFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.BOOLEAN; - static title = 'Boolean'; + static title = i18n.translate('data.common.fieldFormats.boolean.title', { + defaultMessage: 'Boolean', + }); static fieldType = [KBN_FIELD_TYPES.BOOLEAN, KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING]; textConvert: TextContextTypeConvert = value => { diff --git a/src/plugins/data/common/field_formats/converters/bytes.test.ts b/src/plugins/data/common/field_formats/converters/bytes.test.ts index 8dad9fc206e72..56b2c48766c95 100644 --- a/src/plugins/data/common/field_formats/converters/bytes.test.ts +++ b/src/plugins/data/common/field_formats/converters/bytes.test.ts @@ -20,21 +20,24 @@ import { BytesFormat } from './bytes'; describe('BytesFormat', () => { - const config: Record = {}; - - config['format:bytes:defaultPattern'] = '0,0.[000]b'; + let config: Record = {}; const getConfig = (key: string) => config[key]; + beforeEach(() => { + config = {}; + }); + test('default pattern', () => { const formatter = new BytesFormat({}, getConfig); - expect(formatter.convert(5150000)).toBe('4.911MB'); + expect(formatter.convert(5150123)).toBe('4.9MB'); }); - test('custom pattern', () => { - const formatter = new BytesFormat({ pattern: '0,0b' }, getConfig); + test('custom pattern and locale', () => { + config['format:number:defaultLocale'] = 'de'; + const formatter = new BytesFormat({ pattern: '0,0.[000]b' }, getConfig); - expect(formatter.convert('5150000')).toBe('5MB'); + expect(formatter.convert('10513')).toBe('10,267KB'); }); }); diff --git a/src/plugins/data/common/field_formats/converters/bytes.ts b/src/plugins/data/common/field_formats/converters/bytes.ts index f1110add3e7de..e322a836b72e8 100644 --- a/src/plugins/data/common/field_formats/converters/bytes.ts +++ b/src/plugins/data/common/field_formats/converters/bytes.ts @@ -17,14 +17,22 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { NumeralFormat } from './numeral'; import { FIELD_FORMAT_IDS } from '../types'; export class BytesFormat extends NumeralFormat { static id = FIELD_FORMAT_IDS.BYTES; - static title = 'Bytes'; + static title = i18n.translate('data.common.fieldFormats.bytes.title', { + defaultMessage: 'Bytes', + }); id = BytesFormat.id; title = BytesFormat.title; + + getParamDefaults = () => ({ + pattern: '0,0.[0]b', + }); + allowsNumericalAggregations = true; } diff --git a/src/plugins/data/common/field_formats/converters/color.ts b/src/plugins/data/common/field_formats/converters/color.ts index ffc72ba9a2c30..bf9d05585089a 100644 --- a/src/plugins/data/common/field_formats/converters/color.ts +++ b/src/plugins/data/common/field_formats/converters/color.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { findLast, cloneDeep, template, escape } from 'lodash'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; @@ -28,7 +29,9 @@ const convertTemplate = template('<%- val %>') export class ColorFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.COLOR; - static title = 'Color'; + static title = i18n.translate('data.common.fieldFormats.color.title', { + defaultMessage: 'Color', + }); static fieldType = [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING]; getParamDefaults() { diff --git a/src/plugins/data/common/field_formats/converters/currency.test.ts b/src/plugins/data/common/field_formats/converters/currency.test.ts new file mode 100644 index 0000000000000..15ac075615067 --- /dev/null +++ b/src/plugins/data/common/field_formats/converters/currency.test.ts @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import 'intl'; +import { CurrencyFormat } from './currency'; + +describe('CurrencyFormat', () => { + let config: Record = {}; + + const getConfig = (key: string) => config[key]; + + beforeEach(() => { + config = {}; + }); + + test('default currency', () => { + // This locale is not supported in node + config['format:defaultLocale'] = 'pt-PT'; + config['format:currency:defaultCurrency'] = 'EUR'; + + const formatter = new CurrencyFormat({}, getConfig); + + expect(formatter.convert(5150000)).toBe('€5,150,000'); + }); + + test('default currency decimals', () => { + config['format:currency:defaultCurrency'] = 'USD'; + + const formatter = new CurrencyFormat({}, getConfig); + + expect(formatter.convert(1234.56789)).toBe('$1,234.57'); + }); + + test('override default currency', () => { + config['format:currency:defaultCurrency'] = 'USD'; + + const formatter = new CurrencyFormat({ currencyCode: 'EUR' }, getConfig); + + expect(formatter.convert(1234.56789)).toBe('€1,234.57'); + }); + + test('config for decimal places', () => { + config['format:currency:defaultCurrency'] = 'USD'; + config['format:currency:maxDecimals'] = 0; + + const formatter = new CurrencyFormat({}, getConfig); + + expect(formatter.convert(1234.5678)).toBe('$1,235'); + }); +}); diff --git a/src/plugins/data/common/field_formats/converters/currency.ts b/src/plugins/data/common/field_formats/converters/currency.ts new file mode 100644 index 0000000000000..6ada47376ccfd --- /dev/null +++ b/src/plugins/data/common/field_formats/converters/currency.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { IntlNumberFormat } from './intl_number_format'; +import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; +import { FIELD_FORMAT_IDS } from '../types'; + +export class CurrencyFormat extends IntlNumberFormat { + static id = FIELD_FORMAT_IDS.CURRENCY; + static title = i18n.translate('data.common.fieldFormats.currency.title', { + defaultMessage: 'Currency', + }); + static fieldType = KBN_FIELD_TYPES.NUMBER; + + getParamDefaults = () => ({ + currencyCode: this.getConfig?.('format:currency:defaultCurrency'), + minDecimals: 0, + maxDecimals: this.getConfig?.('format:currency:maxDecimals'), + }); + + id = CurrencyFormat.id; + title = CurrencyFormat.title; + + getArguments = () => { + return { + style: 'currency', + currency: this.param('currencyCode'), + minimumFractionDigits: this.param('minDecimals'), + maximumFractionDigits: this.param('maxDecimals'), + }; + }; +} diff --git a/src/plugins/data/common/field_formats/converters/date.ts b/src/plugins/data/common/field_formats/converters/date.ts index 06af64d9c17c2..b2213e74096bf 100644 --- a/src/plugins/data/common/field_formats/converters/date.ts +++ b/src/plugins/data/common/field_formats/converters/date.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { memoize, noop } from 'lodash'; import moment from 'moment'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; @@ -25,7 +26,9 @@ import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class DateFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.DATE; - static title = 'Date'; + static title = i18n.translate('data.common.fieldFormats.date.title', { + defaultMessage: 'Date', + }); static fieldType = KBN_FIELD_TYPES.DATE; private memoizedConverter: Function = noop; diff --git a/src/plugins/data/common/field_formats/converters/date_nanos.ts b/src/plugins/data/common/field_formats/converters/date_nanos.ts index 8b0f8b111694e..420f9807b54c2 100644 --- a/src/plugins/data/common/field_formats/converters/date_nanos.ts +++ b/src/plugins/data/common/field_formats/converters/date_nanos.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import moment, { Moment } from 'moment'; import { memoize, noop } from 'lodash'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; @@ -70,7 +71,9 @@ export function formatWithNanos( export class DateNanosFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.DATE_NANOS; - static title = 'Date Nanos'; + static title = i18n.translate('data.common.fieldFormats.date_nanos.title', { + defaultMessage: 'Date nanos', + }); static fieldType = KBN_FIELD_TYPES.DATE; private memoizedConverter: Function = noop; diff --git a/src/plugins/data/common/field_formats/converters/date_server.ts b/src/plugins/data/common/field_formats/converters/date_server.ts index 34278ea9fe641..ca2a35de8aaf5 100644 --- a/src/plugins/data/common/field_formats/converters/date_server.ts +++ b/src/plugins/data/common/field_formats/converters/date_server.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { memoize, noop } from 'lodash'; import moment from 'moment-timezone'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; @@ -25,14 +26,16 @@ import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class DateFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.DATE; - static title = 'Date'; + static title = i18n.translate('data.common.fieldFormats.date.title', { + defaultMessage: 'Date', + }); static fieldType = KBN_FIELD_TYPES.DATE; private memoizedConverter: Function = noop; private memoizedPattern: string = ''; private timeZone: string = ''; - constructor(params: IFieldFormatMetaParams, getConfig: Function) { + constructor(params: IFieldFormatMetaParams, getConfig?: Function) { super(params, getConfig); this.memoizedConverter = memoize((val: any) => { diff --git a/src/plugins/data/common/field_formats/converters/default_number.test.ts b/src/plugins/data/common/field_formats/converters/default_number.test.ts new file mode 100644 index 0000000000000..3227fcad2154f --- /dev/null +++ b/src/plugins/data/common/field_formats/converters/default_number.test.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { DefaultNumberFormat } from './default_number'; + +describe('DefaultNumberFormat', () => { + let config: Record = {}; + + const getConfig = (key: string) => config[key]; + + beforeEach(() => { + config = {}; + }); + + test('default number', () => { + // Node only contains locale data for `en`, so this is ignored. The fallback is tested here + config['format:defaultLocale'] = 'ch-DE'; + + const formatter = new DefaultNumberFormat({}, getConfig); + + expect(formatter.convert(5150000)).toBe(`5,150,000`); + }); + + test('decimal parameter', () => { + // Node only contains locale data for `en`, so this is ignored. The fallback is tested here + config['format:defaultLocale'] = 'ch-DE'; + + const formatter = new DefaultNumberFormat({ minDecimals: 2 }, getConfig); + + expect(formatter.convert(5150000)).toBe(`5,150,000.00`); + }); + + test('max decimals setting', () => { + config['format:default_number:maxDecimals'] = 0; + + const formatter = new DefaultNumberFormat({}, getConfig); + + expect(formatter.convert(10.123456)).toBe(`10`); + }); +}); diff --git a/src/plugins/data/common/field_formats/converters/default_number.ts b/src/plugins/data/common/field_formats/converters/default_number.ts new file mode 100644 index 0000000000000..d8c25a97b591c --- /dev/null +++ b/src/plugins/data/common/field_formats/converters/default_number.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +import { FIELD_FORMAT_IDS } from '../types'; +import { IntlNumberFormat } from './intl_number_format'; + +export class DefaultNumberFormat extends IntlNumberFormat { + static id = FIELD_FORMAT_IDS.DEFAULT_NUMBER; + static title = i18n.translate('data.common.fieldFormats.default_number.title', { + defaultMessage: 'Default number format', + }); + + id = DefaultNumberFormat.id; + title = DefaultNumberFormat.title; + + getParamDefaults = () => ({ + minDecimals: 0, + maxDecimals: this.getConfig?.('format:default_number:maxDecimals'), + }); + + getArguments = () => ({ + style: 'decimal', + minimumFractionDigits: this.param('minDecimals'), + maximumFractionDigits: this.param('maxDecimals'), + }); +} diff --git a/src/plugins/data/common/field_formats/converters/duration.ts b/src/plugins/data/common/field_formats/converters/duration.ts index 8caa11be5ca79..caac73e132338 100644 --- a/src/plugins/data/common/field_formats/converters/duration.ts +++ b/src/plugins/data/common/field_formats/converters/duration.ts @@ -167,7 +167,9 @@ function parseInputAsDuration(val: number, inputFormat: string) { export class DurationFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.DURATION; - static title = 'Duration'; + static title = i18n.translate('data.common.fieldFormats.duration.title', { + defaultMessage: 'Duration', + }); static fieldType = KBN_FIELD_TYPES.NUMBER; static inputFormats = inputFormats; static outputFormats = outputFormats; @@ -189,7 +191,12 @@ export class DurationFormat extends FieldFormat { const outputFormat = this.param('outputFormat') as keyof Duration; const outputPrecision = this.param('outputPrecision'); const human = this.isHuman(); - const prefix = val < 0 && human ? 'minus ' : ''; + const prefix = + val < 0 && human + ? i18n.translate('data.common.fieldFormats.duration.negativeLabel', { + defaultMessage: 'minus', + }) + ' ' + : ''; const duration = parseInputAsDuration(val, inputFormat) as Record; const formatted = duration[outputFormat](); const precise = human ? formatted : formatted.toFixed(outputPrecision); diff --git a/src/plugins/data/common/field_formats/converters/index.ts b/src/plugins/data/common/field_formats/converters/index.ts index f7e50539b44d8..e7bb0a4f8c180 100644 --- a/src/plugins/data/common/field_formats/converters/index.ts +++ b/src/plugins/data/common/field_formats/converters/index.ts @@ -19,12 +19,15 @@ export { UrlFormat } from './url'; export { BytesFormat } from './bytes'; +export { CurrencyFormat } from './currency'; +export { CustomNumberFormat } from './number'; +export { DefaultNumberFormat } from './default_number'; export { DateFormat } from './date_server'; export { DateNanosFormat } from './date_nanos'; export { RelativeDateFormat } from './relative_date'; export { DurationFormat } from './duration'; export { IpFormat } from './ip'; -export { NumberFormat } from './number'; +export { ShortNumberFormat } from './short_number'; export { PercentFormat } from './percent'; export { StringFormat } from './string'; export { SourceFormat } from './source'; diff --git a/src/plugins/data/common/field_formats/converters/intl_number_format.ts b/src/plugins/data/common/field_formats/converters/intl_number_format.ts new file mode 100644 index 0000000000000..e670f7b2bf690 --- /dev/null +++ b/src/plugins/data/common/field_formats/converters/intl_number_format.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FieldFormat } from '../field_format'; +import { TextContextTypeConvert } from '../types'; +import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; + +const numeralLocaleToBrowser: Record = { + 'be-nl': 'nl-BE', + chs: 'zh-CN', +}; + +export abstract class IntlNumberFormat extends FieldFormat { + static fieldType = KBN_FIELD_TYPES.NUMBER; + + abstract id: string; + abstract title: string; + + getParamDefaults = () => ({}); + allowsNumericalAggregations = true; + + abstract getArguments: () => Record; + + protected getConvertedValue(val: any): string { + if (val === -Infinity) return '-∞'; + if (val === +Infinity) return '+∞'; + if (typeof val !== 'number') { + val = parseFloat(val); + } + + if (isNaN(val)) return ''; + + const locales = this.getLocales(); + const inst = new Intl.NumberFormat(locales, this.getArguments()); + + return inst.format(val); + } + + getLocales = () => { + const numeralLocale = this.getConfig?.('format:number:defaultLocale'); + const defaultLocale = this.getConfig?.('format:defaultLocale'); + let locales; + if (defaultLocale === 'numeral') { + // Special case where numeral uses an invalid locale + locales = [numeralLocaleToBrowser[numeralLocale] || numeralLocale, 'en']; + } else if (defaultLocale === 'detect') { + locales = navigator.languages + ? navigator.languages.concat(['en']) + : [navigator.language, defaultLocale, 'en']; + } else if (defaultLocale) { + locales = [defaultLocale, 'en']; + } else { + locales = ['en']; + } + return locales; + }; + + textConvert: TextContextTypeConvert = val => { + return this.getConvertedValue(val); + }; +} diff --git a/src/plugins/data/common/field_formats/converters/ip.ts b/src/plugins/data/common/field_formats/converters/ip.ts index 3e011e8d7dde8..903a6e4151f75 100644 --- a/src/plugins/data/common/field_formats/converters/ip.ts +++ b/src/plugins/data/common/field_formats/converters/ip.ts @@ -17,13 +17,16 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class IpFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.IP; - static title = 'IP Address'; + static title = i18n.translate('data.common.fieldFormats.ip.title', { + defaultMessage: 'IP address', + }); static fieldType = KBN_FIELD_TYPES.IP; textConvert: TextContextTypeConvert = val => { diff --git a/src/plugins/data/common/field_formats/converters/number.test.ts b/src/plugins/data/common/field_formats/converters/number.test.ts index fe36d5b12e873..3676400a26d0e 100644 --- a/src/plugins/data/common/field_formats/converters/number.test.ts +++ b/src/plugins/data/common/field_formats/converters/number.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { NumberFormat } from './number'; +import { CustomNumberFormat } from './number'; describe('NumberFormat', () => { const config: Record = {}; @@ -27,13 +27,13 @@ describe('NumberFormat', () => { const getConfig = (key: string) => config[key]; test('default pattern', () => { - const formatter = new NumberFormat({}, getConfig); + const formatter = new CustomNumberFormat({}, getConfig); expect(formatter.convert(12.345678)).toBe('12.346'); }); test('custom pattern', () => { - const formatter = new NumberFormat({ pattern: '0,0' }, getConfig); + const formatter = new CustomNumberFormat({ pattern: '0,0' }, getConfig); expect(formatter.convert('12.345678')).toBe('12'); }); diff --git a/src/plugins/data/common/field_formats/converters/number.ts b/src/plugins/data/common/field_formats/converters/number.ts index 686329e887682..7bbfc45c8478f 100644 --- a/src/plugins/data/common/field_formats/converters/number.ts +++ b/src/plugins/data/common/field_formats/converters/number.ts @@ -17,14 +17,17 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { NumeralFormat } from './numeral'; import { FIELD_FORMAT_IDS } from '../types'; -export class NumberFormat extends NumeralFormat { - static id = FIELD_FORMAT_IDS.NUMBER; - static title = 'Number'; +export class CustomNumberFormat extends NumeralFormat { + static id = FIELD_FORMAT_IDS.CUSTOM_NUMBER; + static title = i18n.translate('data.common.fieldFormats.number.title', { + defaultMessage: 'Custom numeral.js format', + }); - id = NumberFormat.id; - title = NumberFormat.title; + id = CustomNumberFormat.id; + title = CustomNumberFormat.title; allowsNumericalAggregations = true; } diff --git a/src/plugins/data/common/field_formats/converters/percent.test.ts b/src/plugins/data/common/field_formats/converters/percent.test.ts index 8b26564814af3..d6154392b9226 100644 --- a/src/plugins/data/common/field_formats/converters/percent.test.ts +++ b/src/plugins/data/common/field_formats/converters/percent.test.ts @@ -22,8 +22,6 @@ import { PercentFormat } from './percent'; describe('PercentFormat', () => { const config: Record = {}; - config['format:percent:defaultPattern'] = '0,0.[000]%'; - const getConfig = (key: string) => config[key]; test('default pattern', () => { @@ -32,8 +30,8 @@ describe('PercentFormat', () => { expect(formatter.convert(0.99999)).toBe('99.999%'); }); - test('custom pattern', () => { - const formatter = new PercentFormat({ pattern: '0,0%' }, getConfig); + test('custom number of digits', () => { + const formatter = new PercentFormat({ maxDecimals: 0 }, getConfig); expect(formatter.convert('0.99999')).toBe('100%'); }); diff --git a/src/plugins/data/common/field_formats/converters/percent.ts b/src/plugins/data/common/field_formats/converters/percent.ts index d839a54dd0c2c..b553fd658bd66 100644 --- a/src/plugins/data/common/field_formats/converters/percent.ts +++ b/src/plugins/data/common/field_formats/converters/percent.ts @@ -17,29 +17,28 @@ * under the License. */ -import { NumeralFormat } from './numeral'; -import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; +import { i18n } from '@kbn/i18n'; +import { IntlNumberFormat } from './intl_number_format'; +import { FIELD_FORMAT_IDS } from '../types'; -export class PercentFormat extends NumeralFormat { +export class PercentFormat extends IntlNumberFormat { static id = FIELD_FORMAT_IDS.PERCENT; - static title = 'Percentage'; + static title = i18n.translate('data.common.fieldFormats.percent.title', { + defaultMessage: 'Percent', + }); id = PercentFormat.id; title = PercentFormat.title; allowsNumericalAggregations = true; getParamDefaults = () => ({ - pattern: this.getConfig!('format:percent:defaultPattern'), - fractional: true, + minDecimals: 0, + maxDecimals: 3, }); - textConvert: TextContextTypeConvert = val => { - const formatted = super.getConvertedValue(val); - - if (this.param('fractional')) { - return formatted; - } - - return String(Number(formatted) / 100); - }; + getArguments = () => ({ + style: 'percent', + minimumFractionDigits: this.param('minDecimals'), + maximumFractionDigits: this.param('maxDecimals'), + }); } diff --git a/src/plugins/data/common/field_formats/converters/relative_date.ts b/src/plugins/data/common/field_formats/converters/relative_date.ts index 273b2cef28a03..f3aa8cac821e0 100644 --- a/src/plugins/data/common/field_formats/converters/relative_date.ts +++ b/src/plugins/data/common/field_formats/converters/relative_date.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import moment from 'moment'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; @@ -24,7 +25,9 @@ import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; export class RelativeDateFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.RELATIVE_DATE; - static title = 'Relative Date'; + static title = i18n.translate('data.common.fieldFormats.relative_date.title', { + defaultMessage: 'Relative date', + }); static fieldType = KBN_FIELD_TYPES.DATE; textConvert: TextContextTypeConvert = val => { diff --git a/src/plugins/data/common/field_formats/converters/short_number.test.ts b/src/plugins/data/common/field_formats/converters/short_number.test.ts new file mode 100644 index 0000000000000..b275a0d9ec25a --- /dev/null +++ b/src/plugins/data/common/field_formats/converters/short_number.test.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ShortNumberFormat } from './short_number'; + +describe('ShortNumberFormat', () => { + const config: Record = {}; + + const getConfig = (key: string) => config[key]; + + test('default', () => { + const formatter = new ShortNumberFormat({}, getConfig); + + // This expectation is only for node, in the browser the same number is formatted as '1.234M' + // This is because of limited ICU support in node + expect(formatter.convert(1234567)).toBe('1,234,567'); + }); +}); diff --git a/src/plugins/data/common/field_formats/converters/short_number.ts b/src/plugins/data/common/field_formats/converters/short_number.ts new file mode 100644 index 0000000000000..a79d274c7c716 --- /dev/null +++ b/src/plugins/data/common/field_formats/converters/short_number.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { IntlNumberFormat } from './intl_number_format'; +import { FIELD_FORMAT_IDS } from '../types'; + +export class ShortNumberFormat extends IntlNumberFormat { + static id = FIELD_FORMAT_IDS.SHORT_NUMBER; + static title = i18n.translate('data.common.fieldFormats.short_number.title', { + defaultMessage: 'Short number', + }); + + id = ShortNumberFormat.id; + title = ShortNumberFormat.title; + + getArguments = () => ({ + style: 'decimal', + notation: 'compact', + }); +} diff --git a/src/plugins/data/common/field_formats/converters/static_lookup.ts b/src/plugins/data/common/field_formats/converters/static_lookup.ts index 419e7c786640b..719a1c2c75cce 100644 --- a/src/plugins/data/common/field_formats/converters/static_lookup.ts +++ b/src/plugins/data/common/field_formats/converters/static_lookup.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; @@ -33,7 +34,9 @@ function convertLookupEntriesToMap(lookupEntries: any[]) { export class StaticLookupFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.STATIC_LOOKUP; - static title = 'Static Lookup'; + static title = i18n.translate('data.common.fieldFormats.static_lookup.title', { + defaultMessage: 'Static lookup', + }); static fieldType = [ KBN_FIELD_TYPES.STRING, KBN_FIELD_TYPES.NUMBER, diff --git a/src/plugins/data/common/field_formats/converters/string.ts b/src/plugins/data/common/field_formats/converters/string.ts index b2d92cf475a16..203d2d74993e4 100644 --- a/src/plugins/data/common/field_formats/converters/string.ts +++ b/src/plugins/data/common/field_formats/converters/string.ts @@ -72,7 +72,9 @@ const DEFAULT_TRANSFORM_OPTION = false; export class StringFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.STRING; - static title = 'String'; + static title = i18n.translate('data.common.fieldFormats.string.title', { + defaultMessage: 'String', + }); static fieldType = [ KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.BOOLEAN, diff --git a/src/plugins/data/common/field_formats/converters/truncate.ts b/src/plugins/data/common/field_formats/converters/truncate.ts index dc25d71ec95d7..ac8401ded25d5 100644 --- a/src/plugins/data/common/field_formats/converters/truncate.ts +++ b/src/plugins/data/common/field_formats/converters/truncate.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { trunc } from 'lodash'; import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { FieldFormat } from '../field_format'; @@ -26,7 +27,9 @@ const omission = '...'; export class TruncateFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.TRUNCATE; - static title = 'Truncated String'; + static title = i18n.translate('data.common.fieldFormats.truncated_string.title', { + defaultMessage: 'Truncated string', + }); static fieldType = KBN_FIELD_TYPES.STRING; textConvert: TextContextTypeConvert = val => { diff --git a/src/plugins/data/common/field_formats/converters/url.ts b/src/plugins/data/common/field_formats/converters/url.ts index 21688dd8d1138..e4f7a06412afb 100644 --- a/src/plugins/data/common/field_formats/converters/url.ts +++ b/src/plugins/data/common/field_formats/converters/url.ts @@ -51,7 +51,9 @@ const DEFAULT_URL_TYPE = 'a'; export class UrlFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.URL; - static title = 'Url'; + static title = i18n.translate('data.common.fieldFormats.url.title', { + defaultMessage: 'Url', + }); static fieldType = [ KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.BOOLEAN, diff --git a/src/plugins/data/common/field_formats/types.ts b/src/plugins/data/common/field_formats/types.ts index dce3c66b0f886..2fdb84f359ef6 100644 --- a/src/plugins/data/common/field_formats/types.ts +++ b/src/plugins/data/common/field_formats/types.ts @@ -54,11 +54,14 @@ export enum FIELD_FORMAT_IDS { BYTES = 'bytes', COLOR = 'color', CUSTOM = 'custom', + CUSTOM_NUMBER = 'number', // Backwards compatible + DEFAULT_NUMBER = 'default_number', + CURRENCY = 'currency', DATE = 'date', DATE_NANOS = 'date_nanos', DURATION = 'duration', IP = 'ip', - NUMBER = 'number', + SHORT_NUMBER = 'short_number', PERCENT = 'percent', RELATIVE_DATE = 'relative_date', STATIC_LOOKUP = 'static_lookup', diff --git a/src/plugins/data/public/field_formats_provider/field_formats_service.ts b/src/plugins/data/public/field_formats_provider/field_formats_service.ts index 42abeecc6fda0..a5ca4d8b50f18 100644 --- a/src/plugins/data/public/field_formats_provider/field_formats_service.ts +++ b/src/plugins/data/public/field_formats_provider/field_formats_service.ts @@ -24,11 +24,14 @@ import { BoolFormat, BytesFormat, ColorFormat, + CurrencyFormat, DateFormat, DateNanosFormat, DurationFormat, IpFormat, - NumberFormat, + ShortNumberFormat, + DefaultNumberFormat, + CustomNumberFormat, PercentFormat, RelativeDateFormat, SourceFormat, @@ -48,11 +51,14 @@ export class FieldFormatsService { BoolFormat, BytesFormat, ColorFormat, + CurrencyFormat, DateFormat, DateNanosFormat, DurationFormat, IpFormat, - NumberFormat, + ShortNumberFormat, + DefaultNumberFormat, + CustomNumberFormat, PercentFormat, RelativeDateFormat, SourceFormat, diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 19ba246ce02dd..757f8fe66d722 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -72,7 +72,9 @@ export { FieldFormat, getHighlightRequest, // only used in search source IpFormat, - NumberFormat, + DefaultNumberFormat, + CustomNumberFormat, + ShortNumberFormat, PercentFormat, RelativeDateFormat, SourceFormat, diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 3cd088744a439..e62d2e1dad5d2 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -71,13 +71,15 @@ export { BoolFormat, BytesFormat, ColorFormat, + CurrencyFormat, DateFormat, DateNanosFormat, DEFAULT_CONVERTER_COLOR, DurationFormat, FieldFormat, IpFormat, - NumberFormat, + DefaultNumberFormat, + CustomNumberFormat, PercentFormat, RelativeDateFormat, SourceFormat, diff --git a/src/test_utils/public/stub_field_formats.ts b/src/test_utils/public/stub_field_formats.ts index ea46710c0dc84..40111aa75f0c8 100644 --- a/src/test_utils/public/stub_field_formats.ts +++ b/src/test_utils/public/stub_field_formats.ts @@ -27,7 +27,9 @@ import { DateNanosFormat, DurationFormat, IpFormat, - NumberFormat, + ShortNumberFormat, + DefaultNumberFormat, + CustomNumberFormat, PercentFormat, RelativeDateFormat, SourceFormat, @@ -44,11 +46,13 @@ export const getFieldFormatsRegistry = (core: CoreSetup) => { BoolFormat, BytesFormat, ColorFormat, + ShortNumberFormat, + DefaultNumberFormat, + CustomNumberFormat, DateFormat, DateNanosFormat, DurationFormat, IpFormat, - NumberFormat, PercentFormat, RelativeDateFormat, SourceFormat, diff --git a/test/functional/apps/visualize/_area_chart.js b/test/functional/apps/visualize/_area_chart.js index e52cfdf478c33..804c643b0133a 100644 --- a/test/functional/apps/visualize/_area_chart.js +++ b/test/functional/apps/visualize/_area_chart.js @@ -234,26 +234,26 @@ export default function({ getService, getPageObjects }) { it('does not scale top hit agg', async () => { const expectedTableData = [ - ['2015-09-20 00:00', '6', '9.035KB'], - ['2015-09-20 01:00', '9', '5.854KB'], - ['2015-09-20 02:00', '22', '4.588KB'], - ['2015-09-20 03:00', '31', '8.349KB'], - ['2015-09-20 04:00', '52', '2.637KB'], - ['2015-09-20 05:00', '119', '1.712KB'], - ['2015-09-20 06:00', '181', '9.157KB'], - ['2015-09-20 07:00', '218', '8.192KB'], - ['2015-09-20 08:00', '341', '12.384KB'], - ['2015-09-20 09:00', '440', '4.482KB'], - ['2015-09-20 10:00', '480', '9.449KB'], + ['2015-09-20 00:00', '6', '9KB'], + ['2015-09-20 01:00', '9', '5.9KB'], + ['2015-09-20 02:00', '22', '4.6KB'], + ['2015-09-20 03:00', '31', '8.3KB'], + ['2015-09-20 04:00', '52', '2.6KB'], + ['2015-09-20 05:00', '119', '1.7KB'], + ['2015-09-20 06:00', '181', '9.2KB'], + ['2015-09-20 07:00', '218', '8.2KB'], + ['2015-09-20 08:00', '341', '12.4KB'], + ['2015-09-20 09:00', '440', '4.5KB'], + ['2015-09-20 10:00', '480', '9.4KB'], ['2015-09-20 11:00', '517', '213B'], ['2015-09-20 12:00', '522', '638B'], - ['2015-09-20 13:00', '446', '7.421KB'], - ['2015-09-20 14:00', '403', '4.854KB'], - ['2015-09-20 15:00', '321', '4.132KB'], + ['2015-09-20 13:00', '446', '7.4KB'], + ['2015-09-20 14:00', '403', '4.9KB'], + ['2015-09-20 15:00', '321', '4.1KB'], ['2015-09-20 16:00', '258', '601B'], - ['2015-09-20 17:00', '172', '4.239KB'], - ['2015-09-20 18:00', '95', '6.272KB'], - ['2015-09-20 19:00', '55', '2.053KB'], + ['2015-09-20 17:00', '172', '4.2KB'], + ['2015-09-20 18:00', '95', '6.3KB'], + ['2015-09-20 19:00', '55', '2.1KB'], ]; await PageObjects.visEditor.clickBucket('Y-axis', 'metrics'); diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index 0a9ff1e77a2ef..b54e2901680e2 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -77,15 +77,15 @@ export default function({ getService, getPageObjects }) { it('should show correct data', function() { const expectedChartData = [ ['0B', '2,088'], - ['1.953KB', '2,748'], - ['3.906KB', '2,707'], - ['5.859KB', '2,876'], - ['7.813KB', '2,863'], - ['9.766KB', '147'], - ['11.719KB', '148'], - ['13.672KB', '129'], - ['15.625KB', '161'], - ['17.578KB', '137'], + ['2KB', '2,748'], + ['3.9KB', '2,707'], + ['5.9KB', '2,876'], + ['7.8KB', '2,863'], + ['9.8KB', '147'], + ['11.7KB', '148'], + ['13.7KB', '129'], + ['15.6KB', '161'], + ['17.6KB', '137'], ]; return retry.try(async function() { @@ -145,9 +145,9 @@ export default function({ getService, getPageObjects }) { const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql([ '≥ 0 and < 1000', - '344.094B', + '344.1B', '≥ 1000 and < 2000', - '1.697KB', + '1.7KB', ]); }); @@ -373,16 +373,16 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.clickGo(); const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ - ['jpg', '9,109', '5.469KB', 'CN', '1,718', '5.477KB'], - ['jpg', '9,109', '5.469KB', 'IN', '1,511', '5.456KB'], - ['jpg', '9,109', '5.469KB', 'US', '770', '5.371KB'], - ['jpg', '9,109', '5.469KB', 'ID', '314', '5.424KB'], - ['jpg', '9,109', '5.469KB', 'PK', '244', '5.41KB'], - ['css', '2,159', '5.566KB', 'CN', '422', '5.712KB'], - ['css', '2,159', '5.566KB', 'IN', '346', '5.754KB'], - ['css', '2,159', '5.566KB', 'US', '189', '5.333KB'], - ['css', '2,159', '5.566KB', 'ID', '68', '4.82KB'], - ['css', '2,159', '5.566KB', 'BR', '58', '5.915KB'], + ['jpg', '9,109', '5.5KB', 'CN', '1,718', '5.5KB'], + ['jpg', '9,109', '5.5KB', 'IN', '1,511', '5.5KB'], + ['jpg', '9,109', '5.5KB', 'US', '770', '5.4KB'], + ['jpg', '9,109', '5.5KB', 'ID', '314', '5.4KB'], + ['jpg', '9,109', '5.5KB', 'PK', '244', '5.4KB'], + ['css', '2,159', '5.6KB', 'CN', '422', '5.7KB'], + ['css', '2,159', '5.6KB', 'IN', '346', '5.8KB'], + ['css', '2,159', '5.6KB', 'US', '189', '5.3KB'], + ['css', '2,159', '5.6KB', 'ID', '68', '4.8KB'], + ['css', '2,159', '5.6KB', 'BR', '58', '5.9KB'], ]); }); }); diff --git a/test/functional/apps/visualize/_data_table_nontimeindex.js b/test/functional/apps/visualize/_data_table_nontimeindex.js index 3db3cd094a81b..d20587434607e 100644 --- a/test/functional/apps/visualize/_data_table_nontimeindex.js +++ b/test/functional/apps/visualize/_data_table_nontimeindex.js @@ -78,15 +78,15 @@ export default function({ getService, getPageObjects }) { it('should show correct data', function() { const expectedChartData = [ ['0B', '2,088'], - ['1.953KB', '2,748'], - ['3.906KB', '2,707'], - ['5.859KB', '2,876'], - ['7.813KB', '2,863'], - ['9.766KB', '147'], - ['11.719KB', '148'], - ['13.672KB', '129'], - ['15.625KB', '161'], - ['17.578KB', '137'], + ['2KB', '2,748'], + ['3.9KB', '2,707'], + ['5.9KB', '2,876'], + ['7.8KB', '2,863'], + ['9.8KB', '147'], + ['11.7KB', '148'], + ['13.7KB', '129'], + ['15.6KB', '161'], + ['17.6KB', '137'], ]; return retry.try(async function() { diff --git a/test/functional/apps/visualize/_embedding_chart.js b/test/functional/apps/visualize/_embedding_chart.js index 940aa3eb5d462..1b2dcf7e00069 100644 --- a/test/functional/apps/visualize/_embedding_chart.js +++ b/test/functional/apps/visualize/_embedding_chart.js @@ -61,31 +61,31 @@ export default function({ getService, getPageObjects }) { '0B', '5', '2015-09-20 00:00', - '1.953KB', + '2KB', '5', '2015-09-20 00:00', - '3.906KB', + '3.9KB', '9', '2015-09-20 00:00', - '5.859KB', + '5.9KB', '4', '2015-09-20 00:00', - '7.813KB', + '7.8KB', '14', '2015-09-20 03:00', '0B', '32', '2015-09-20 03:00', - '1.953KB', + '2KB', '33', '2015-09-20 03:00', - '3.906KB', + '3.9KB', '45', '2015-09-20 03:00', - '5.859KB', + '5.9KB', '31', '2015-09-20 03:00', - '7.813KB', + '7.8KB', '48', ]); }); @@ -102,31 +102,31 @@ export default function({ getService, getPageObjects }) { '0B', '7', '2015-09-21 00:00', - '1.953KB', + '2KB', '9', '2015-09-21 00:00', - '3.906KB', + '3.9KB', '9', '2015-09-21 00:00', - '5.859KB', + '5.9KB', '6', '2015-09-21 00:00', - '7.813KB', + '7.8KB', '10', '2015-09-21 00:00', - '11.719KB', + '11.7KB', '1', '2015-09-21 03:00', '0B', '28', '2015-09-21 03:00', - '1.953KB', + '2KB', '39', '2015-09-21 03:00', - '3.906KB', + '3.9KB', '36', '2015-09-21 03:00', - '5.859KB', + '5.9KB', '43', ]); }); @@ -143,31 +143,31 @@ export default function({ getService, getPageObjects }) { '0B', '1', '03:00', - '1.953KB', + '2KB', '1', '03:00', - '3.906KB', + '3.9KB', '1', '03:00', - '5.859KB', + '5.9KB', '2', '03:10', '0B', '1', '03:10', - '5.859KB', + '5.9KB', '1', '03:10', - '7.813KB', + '7.8KB', '1', '03:15', '0B', '1', '03:15', - '1.953KB', + '2KB', '1', '03:20', - '1.953KB', + '2KB', '1', ]); }); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 68285971e5c4a..fc4cb5a5b30cf 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -35,7 +35,6 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { await esArchiver.load('visualize'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', - 'format:bytes:defaultPattern': '0,0.[000]b', }); }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts index 7654774901ff0..29eae0876d10c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts @@ -6,7 +6,6 @@ import { compose, withProps } from 'recompose'; import { NumberFormatArgInput as Component, Props as ComponentProps } from './number_format'; -import { AdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; // @ts-ignore untyped local lib import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { ArgumentFactory } from '../../../../types/arguments'; @@ -15,11 +14,11 @@ import { ArgumentStrings } from '../../../../i18n'; const { NumberFormat: strings } = ArgumentStrings; const formatMap = { - NUMBER: AdvancedSettings.get('format:number:defaultPattern'), - PERCENT: AdvancedSettings.get('format:percent:defaultPattern'), - CURRENCY: AdvancedSettings.get('format:currency:defaultPattern'), + NUMBER: '0,0.[000]', + PERCENT: '0,0.[000]%', + CURRENCY: '$0,0.[00]', DURATION: '00:00:00', - BYTES: AdvancedSettings.get('format:bytes:defaultPattern'), + BYTES: '0,0.[000]b', }; const numberFormats = [ diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts index ceaf82c1c07d6..63a595b3269fb 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts @@ -17,7 +17,7 @@ test('getPdfUrl returns the correct url', () => { const url = getPdfUrl(workpad, { pageCount: 2 }, addBasePath); expect(url).toMatchInlineSnapshot( - `"basepath//api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FPhoenix,layout:(dimensions:(height:0,width:0),id:preserve_layout),objectType:'canvas%20workpad',relativeUrls:!(%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F1,%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F2),title:'base%20workpad')"` + `"basepath//api/reporting/generate/printablePdf?jobParams=(browserLocales:!(en-US,en),browserTimezone:America%2FPhoenix,layout:(dimensions:(height:0,width:0),id:preserve_layout),objectType:'canvas%20workpad',relativeUrls:!(%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F1,%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F2),title:'base%20workpad')"` ); }); @@ -31,7 +31,7 @@ test('createPdf posts to create the pdf', () => { expect(args[0]).toMatchInlineSnapshot(`"basepath//api/reporting/generate/printablePdf"`); expect(args[1]).toMatchInlineSnapshot(` Object { - "jobParams": "(browserTimezone:America/Phoenix,layout:(dimensions:(height:0,width:0),id:preserve_layout),objectType:'canvas workpad',relativeUrls:!(/app/canvas#/export/workpad/pdf/base-workpad/page/1,/app/canvas#/export/workpad/pdf/base-workpad/page/2),title:'base workpad')", + "jobParams": "(browserLocales:!(en-US,en),browserTimezone:America/Phoenix,layout:(dimensions:(height:0,width:0),id:preserve_layout),objectType:'canvas workpad',relativeUrls:!(/app/canvas#/export/workpad/pdf/base-workpad/page/1,/app/canvas#/export/workpad/pdf/base-workpad/page/2),title:'base workpad')", } `); }); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts index f7f191a48de82..76e9af4763c0d 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts @@ -52,6 +52,7 @@ function getPdfUrlParts( const jobParams = { browserTimezone: 'America/Phoenix', // TODO: get browser timezone, or Kibana setting? + browserLocales: ['en-US', 'en'], // TODO: get browser locales layout: { dimensions: { width, height }, id: PDF_LAYOUT_TYPE, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts index b83021d5e38dd..801a6666507c9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts @@ -32,9 +32,10 @@ export function screenshotsObservableFactory( conditionalHeaders, layout, browserTimezone, + browserLocales, }: ScreenshotObservableOpts): Rx.Observable { const create$ = browserDriverFactory.createPage( - { viewport: layout.getBrowserViewport(), browserTimezone }, + { viewport: layout.getBrowserViewport(), browserTimezone, browserLocales }, logger ); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts index 78cd42f0cae2f..2b7e446bd416a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts @@ -14,6 +14,7 @@ export interface ScreenshotObservableOpts { conditionalHeaders: ConditionalHeaders; layout: LayoutInstance; browserTimezone: string; + browserLocales: string[]; } export interface TimeRange { diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts index b3384dd6ca51d..de57effd0bb96 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.test.ts @@ -7,11 +7,16 @@ import expect from '@kbn/expect'; import { FieldFormatsService } from '../../../../../../../../src/legacy/ui/field_formats/mixin/field_formats_service'; // Reporting uses an unconventional directory structure so the linter marks this as a violation -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { BytesFormat, NumberFormat } from '../../../../../../../../src/plugins/data/server'; +import { + DateFormat, + BytesFormat, + DefaultNumberFormat, + StaticLookupFormat, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../../src/plugins/data/server'; import { fieldFormatMapFactory } from './field_format_map'; -type ConfigValue = { number: { id: string; params: {} } } | string; +type ConfigValue = { [key: string]: { id: string; params: {} } } | string; describe('field format map', function() { const indexPatternSavedObject = { @@ -22,33 +27,43 @@ describe('field format map', function() { title: 'logstash-*', timeFieldName: '@timestamp', notExpandable: true, - fields: '[{"name":"field1","type":"number"}, {"name":"field2","type":"number"}]', - fieldFormatMap: '{"field1":{"id":"bytes","params":{"pattern":"0,0.[0]b"}}}', + fields: + '[{"name":"field1","type":"number"}, {"name":"field2","type":"date"}, {"name":"field3","type":"string"}]', + fieldFormatMap: + '{"field1":{"id":"bytes"},"field3":{"id":"static_lookup","params":{"lookupEntries":[{"key":"test","value":"tested"}]}}}', }, }; const configMock: Record = {}; + configMock.dateFormat = 'YYYY-MM-DD'; + configMock['dateFormat:tz'] = 'Europe/London'; configMock['format:defaultTypeMap'] = { - number: { id: 'number', params: {} }, + number: { id: 'default_number', params: {} }, + string: { id: 'string', params: {} }, + date: { id: 'date', params: {} }, + _default_: { id: 'string', params: {} }, }; - configMock['format:number:defaultPattern'] = '0,0.[000]'; const getConfig = (key: string) => configMock[key]; - const testValue = '4000'; + const testValue = 'test'; - const fieldFormats = new FieldFormatsService([BytesFormat, NumberFormat], getConfig); + const fieldFormats = new FieldFormatsService( + [DefaultNumberFormat, BytesFormat, StaticLookupFormat, DateFormat], + getConfig + ); const formatMap = fieldFormatMapFactory(indexPatternSavedObject, fieldFormats); - it('should build field format map with entry per index pattern field', function() { - expect(formatMap.has('field1')).to.be(true); + it('should build field format map with entry per index pattern field, excluding numbers', function() { + expect(formatMap.has('field1')).to.be(false); expect(formatMap.has('field2')).to.be(true); + expect(formatMap.has('field3')).to.be(true); expect(formatMap.has('field_not_in_index')).to.be(false); }); it('should create custom FieldFormat for fields with configured field formatter', function() { - expect(formatMap.get('field1').convert(testValue)).to.be('3.9KB'); + expect(formatMap.get('field3').convert(testValue)).to.be('tested'); }); it('should create default FieldFormat for fields with no field formatter', function() { - expect(formatMap.get('field2').convert(testValue)).to.be('4,000'); + expect(formatMap.get('field2').convert(1578948388064)).to.be('2020-01-13'); }); }); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts index d013b5e75ee4d..4b8a37b5d6b13 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/field_format_map.ts @@ -47,6 +47,10 @@ export function fieldFormatMapFactory( if (!formatsMap.has(field.name)) { formatsMap.set(field.name, fieldFormats.getDefaultInstance(field.type)); } + + if (field.type === 'number') { + formatsMap.delete(field.name); + } }); return formatsMap; diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts index 3f03246106d3e..8353789263fdc 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts @@ -21,7 +21,7 @@ export const createJobFactory: CreateJobFactory { const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const browserTimezone = 'UTC'; + const browserLocales = ['de']; await executeJob( 'pngJobId', - { relativeUrl: '/app/kibana#/something', browserTimezone, headers: encryptedHeaders }, + { + relativeUrl: '/app/kibana#/something', + browserTimezone, + browserLocales, + headers: encryptedHeaders, + }, cancellationToken ); @@ -80,6 +86,7 @@ test(`passes browserTimezone to generatePng`, async () => { expect.any(LevelLogger), 'http://localhost:5601/sbp/app/kibana#/something', browserTimezone, + browserLocales, expect.anything(), undefined ); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index c2fda05fbe3e9..62372ecfcf3f8 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -45,6 +45,7 @@ export const executeJobFactory: QueuedPngExecutorFactory = function executeJobFa jobLogger, hashUrl, job.browserTimezone, + job.browserLocales, conditionalHeaders, job.layout ); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts index 600762c451a79..23e51a5739f19 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts @@ -22,6 +22,7 @@ export function generatePngObservableFactory( logger: LevelLogger, url: string, browserTimezone: string, + browserLocales: string[], conditionalHeaders: ConditionalHeaders, layoutParams: LayoutParams ): Rx.Observable { @@ -36,6 +37,7 @@ export function generatePngObservableFactory( conditionalHeaders, layout, browserTimezone, + browserLocales, }).pipe( map(([{ screenshots }]) => { if (screenshots.length !== 1) { diff --git a/x-pack/legacy/plugins/reporting/export_types/png/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/png/types.d.ts index 4f5f295f33f4a..9c24559242454 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/types.d.ts @@ -13,6 +13,7 @@ export interface JobParamsPNG { title: string; relativeUrl: string; browserTimezone: string; + browserLocales: string[]; layout: LayoutInstance; } @@ -20,6 +21,7 @@ export interface JobParamsPNG { export interface JobDocPayloadPNG extends JobDocPayload { basePath?: string; browserTimezone: string; + browserLocales: string[]; forceNow?: string; layout: LayoutParams; relativeUrl: string; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts index a8cc71175cffe..0b68a0f14cf8f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts @@ -21,7 +21,7 @@ export const createJobFactory: CreateJobFactory { return await crypto.encrypt(headers); }; -test(`passes browserTimezone to generatePdf`, async () => { +test(`passes browserTimezone and browserLocales to generatePdf`, async () => { const encryptedHeaders = await encryptHeaders({}); const generatePdfObservable = generatePdfObservableFactory(); @@ -70,9 +70,10 @@ test(`passes browserTimezone to generatePdf`, async () => { const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const browserTimezone = 'UTC'; + const browserLocales = ['es-419', 'es', 'en']; await executeJob( 'pdfJobId', - { relativeUrls: [], browserTimezone, headers: encryptedHeaders }, + { relativeUrls: [], browserTimezone, browserLocales, headers: encryptedHeaders }, cancellationToken ); @@ -82,6 +83,7 @@ test(`passes browserTimezone to generatePdf`, async () => { undefined, [], browserTimezone, + browserLocales, expect.anything(), undefined, undefined diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index d85207e671212..3cc6681bda074 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -44,12 +44,13 @@ export const executeJobFactory: QueuedPdfExecutorFactory = function executeJobFa mergeMap(({ logo, conditionalHeaders }) => { const urls = getFullUrls({ server, job }); - const { browserTimezone, layout, title } = job; + const { browserTimezone, browserLocales, layout, title } = job; return generatePdfObservable( jobLogger, title, urls, browserTimezone, + browserLocales, conditionalHeaders, layout, logo diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts index 9a8db308bea79..620ca2036e8c1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts @@ -37,6 +37,7 @@ export function generatePdfObservableFactory( title: string, urls: string[], browserTimezone: string, + browserLocales: string[], conditionalHeaders: ConditionalHeaders, layoutParams: LayoutParams, logo?: string @@ -48,6 +49,7 @@ export function generatePdfObservableFactory( conditionalHeaders, layout, browserTimezone, + browserLocales, }).pipe( mergeMap(async urlScreenshots => { const pdfOutput = pdf.create(layout, logo); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts index 0a9dcfe986ca6..c2eb610f17e41 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts @@ -13,6 +13,7 @@ export interface JobParamsPDF { title: string; relativeUrls: string[]; browserTimezone: string; + browserLocales: string[]; layout: LayoutInstance; } @@ -20,6 +21,7 @@ export interface JobParamsPDF { export interface JobDocPayloadPDF extends JobDocPayload { basePath?: string; browserTimezone: string; + browserLocales: string[]; forceNow?: string; layout: LayoutParams; relativeUrls: string[]; diff --git a/x-pack/legacy/plugins/reporting/public/components/report_info_button.tsx b/x-pack/legacy/plugins/reporting/public/components/report_info_button.tsx index 77869c40d3577..d0aca7d997c49 100644 --- a/x-pack/legacy/plugins/reporting/public/components/report_info_button.tsx +++ b/x-pack/legacy/plugins/reporting/public/components/report_info_button.tsx @@ -73,13 +73,8 @@ export class ReportInfoButton extends Component { const jobType = info.jobtype || NA; - interface JobInfo { - title: string; - description: string; - } - interface JobInfoMap { - [thing: string]: JobInfo[]; + [thing: string]: Array<{ title: string; description: string }>; } const attempts = info.attempts ? info.attempts.toString() : NA; @@ -116,6 +111,11 @@ export class ReportInfoButton extends Component { title: 'Browser Timezone', description: get(info, 'payload.browserTimezone') || NA, }, + { + title: 'Browser Locales', + description: + get(info, 'payload.browserLocales')?.join(', ') || NA, + }, ], payload: [ { diff --git a/x-pack/legacy/plugins/reporting/public/lib/job_queue_client.ts b/x-pack/legacy/plugins/reporting/public/lib/job_queue_client.ts index 173a4e31cfef6..5c8099b444fb9 100644 --- a/x-pack/legacy/plugins/reporting/public/lib/job_queue_client.ts +++ b/x-pack/legacy/plugins/reporting/public/lib/job_queue_client.ts @@ -42,6 +42,7 @@ export interface JobInfo { title: string; forceNow: string; browserTimezone: string; + browserLocales: string[]; }; meta: { layout: string; diff --git a/x-pack/legacy/plugins/reporting/public/share_context_menu/register_reporting.tsx b/x-pack/legacy/plugins/reporting/public/share_context_menu/register_reporting.tsx index fb5e74664e6c6..66a577e3cc802 100644 --- a/x-pack/legacy/plugins/reporting/public/share_context_menu/register_reporting.tsx +++ b/x-pack/legacy/plugins/reporting/public/share_context_menu/register_reporting.tsx @@ -42,10 +42,16 @@ async function reportingProvider() { ? moment.tz.guess() : chrome.getUiSettingsClient().get('dateFormat:tz'); + const browserLocales = + chrome.getUiSettingsClient().get('format:defaultLocale') === 'detect' + ? navigator.languages?.concat(['en']) || [navigator.language, 'en'] + : [chrome.getUiSettingsClient().get('format:defaultLocale')]; + return { ...sharingData, objectType, browserTimezone, + browserLocales, relativeUrls: [relativeUrl], }; }; @@ -59,10 +65,16 @@ async function reportingProvider() { ? moment.tz.guess() : chrome.getUiSettingsClient().get('dateFormat:tz'); + const browserLocales = + chrome.getUiSettingsClient().get('format:defaultLocale') === 'detect' + ? navigator.languages?.concat(['en']) || [navigator.language, 'en'] + : [chrome.getUiSettingsClient().get('format:defaultLocale')]; + return { ...sharingData, objectType, browserTimezone, + browserLocales, relativeUrl, }; }; diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 6fa46b893de8c..92071a50ee01d 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -88,7 +88,11 @@ export class HeadlessChromiumDriverFactory { * Return an observable to objects which will drive screenshot capture for a page */ createPage( - { viewport, browserTimezone }: { viewport: BrowserConfig['viewport']; browserTimezone: string }, + { + viewport, + browserTimezone, + browserLocales, + }: { viewport: BrowserConfig['viewport']; browserTimezone: string; browserLocales: string[] }, pLogger: Logger ): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable }> { return Rx.Observable.create(async (observer: InnerSubscriber) => { @@ -100,12 +104,13 @@ export class HeadlessChromiumDriverFactory { let browser: Browser; let page: Page; try { + logger.debug(`Browser locales ${browserLocales}`); browser = await puppeteerLaunch({ pipe: !this.browserConfig.inspect, userDataDir: this.userDataDir, executablePath: this.binaryPath, ignoreHTTPSErrors: true, - args: chromiumArgs, + args: [...chromiumArgs, `--lang=${browserLocales.join(',')}`], env: { TZ: browserTimezone, }, @@ -121,6 +126,7 @@ export class HeadlessChromiumDriverFactory { logger.debug(`Browser page driver created`); } catch (err) { + logger.warn(err); observer.error(new Error(`Error spawning Chromium browser: [${err}]`)); throw err; } diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 03824dd4e3960..a8b290633a1c9 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -6,7 +6,6 @@ export const APP_ID = 'siem'; export const APP_NAME = 'SIEM'; -export const DEFAULT_BYTES_FORMAT = 'format:bytes:defaultPattern'; export const DEFAULT_DATE_FORMAT = 'dateFormat'; export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz'; export const DEFAULT_DARK_MODE = 'theme:darkMode'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx index 914d233bccc1f..f9c19b6404ada 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx @@ -7,49 +7,22 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { useUiSetting$ } from '../../lib/kibana'; - import { PreferenceFormattedBytesComponent } from '.'; jest.mock('../../lib/kibana'); -const mockUseUiSetting$ = useUiSetting$ as jest.Mock; -const DEFAULT_BYTES_FORMAT_VALUE = '0,0.[0]b'; // kibana's default for this setting const bytes = '2806422'; describe('PreferenceFormattedBytes', () => { test('renders correctly against snapshot', () => { - mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); - test('it renders bytes to Numeral formatting when no format setting exists', () => { - mockUseUiSetting$.mockImplementation(() => [null]); - const wrapper = mount(); - - expect(wrapper.text()).toEqual('2,806,422'); - }); - - test('it renders bytes according to the default format', () => { - mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); - const wrapper = mount(); - - expect(wrapper.text()).toEqual('2.7MB'); - }); - test('it renders bytes supplied as a number according to the default format', () => { - mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); const wrapper = mount(); expect(wrapper.text()).toEqual('2.7MB'); }); - - test('it renders bytes according to new format', () => { - mockUseUiSetting$.mockImplementation(() => ['0b']); - const wrapper = mount(); - - expect(wrapper.text()).toEqual('3MB'); - }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx index 98a1acf471629..bc260b0ce8e00 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx @@ -7,9 +7,6 @@ import React from 'react'; import numeral from '@elastic/numeral'; -import { DEFAULT_BYTES_FORMAT } from '../../../common/constants'; -import { useUiSetting$ } from '../../lib/kibana'; - type Bytes = string | number; export const formatBytes = (value: Bytes, format: string) => { @@ -17,9 +14,7 @@ export const formatBytes = (value: Bytes, format: string) => { }; export const useFormatBytes = () => { - const [bytesFormat] = useUiSetting$(DEFAULT_BYTES_FORMAT); - - return (value: Bytes) => formatBytes(value, bytesFormat); + return (value: Bytes) => formatBytes(value, '0,0.[0]b'); }; export const PreferenceFormattedBytesComponent = ({ value }: { value: Bytes }) => ( diff --git a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts index 968ab6543f4fc..1fa43ab5e54f1 100644 --- a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts +++ b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts @@ -22,7 +22,6 @@ import { DEFAULT_TO, DEFAULT_INTERVAL_PAUSE, DEFAULT_INTERVAL_VALUE, - DEFAULT_BYTES_FORMAT, } from '../../common/constants'; import { defaultIndexPattern } from '../../default_index_pattern'; import { createKibanaCoreStartMock, createKibanaPluginsStartMock } from './kibana_core'; @@ -40,7 +39,6 @@ export const mockUiSettings: Record = { value: DEFAULT_INTERVAL_VALUE, }, [DEFAULT_INDEX_KEY]: defaultIndexPattern, - [DEFAULT_BYTES_FORMAT]: '0,0.[0]b', [DEFAULT_DATE_FORMAT_TZ]: 'UTC', [DEFAULT_DATE_FORMAT]: 'MMM D, YYYY @ HH:mm:ss.SSS', [DEFAULT_DARK_MODE]: false, diff --git a/x-pack/package.json b/x-pack/package.json index ad0be351483f6..0ab1444dad6fc 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -180,7 +180,7 @@ "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.1.0", "@elastic/node-crypto": "^1.0.0", - "@elastic/numeral": "2.3.3", + "@elastic/numeral": "2.3.5", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", "@kbn/i18n": "1.0.0", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f61dfa8d886c2..444566194bdd2 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1000,23 +1000,11 @@ "kbn.advancedSettings.docTableHighlightTitle": "結果をハイライト", "kbn.advancedSettings.fieldsPopularLimitText": "最も頻繁に使用されるフィールドのトップ N を表示します", "kbn.advancedSettings.fieldsPopularLimitTitle": "頻繁に使用されるフィールドの制限", - "kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText": "数字フォーマット", - "kbn.advancedSettings.format.bytesFormatText": "「バイト」フォーマットのデフォルト {numeralFormatLink} です", - "kbn.advancedSettings.format.bytesFormatTitle": "バイトフォーマット", - "kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText": "数字フォーマット", - "kbn.advancedSettings.format.currencyFormatText": "「通貨」フォーマットのデフォルト {numeralFormatLink} です", - "kbn.advancedSettings.format.currencyFormatTitle": "通貨フォーマット", "kbn.advancedSettings.format.defaultTypeMapText": "各フィールドタイプにデフォルトで使用するフォーマット名のマップです。フィールドタイプが特に指定されていない場合は {defaultFormat} が使用されます", "kbn.advancedSettings.format.defaultTypeMapTitle": "フィールドタイプフォーマット名", - "kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText": "数字言語", - "kbn.advancedSettings.format.formattingLocaleText": "{numeralLanguageLink} ロケール", - "kbn.advancedSettings.format.formattingLocaleTitle": "フォーマットロケール", "kbn.advancedSettings.format.numberFormat.numeralFormatLinkText": "数字フォーマット", "kbn.advancedSettings.format.numberFormatText": "「数字」フォーマットのデフォルト {numeralFormatLink} です", "kbn.advancedSettings.format.numberFormatTitle": "数字フォーマット", - "kbn.advancedSettings.format.percentFormat.numeralFormatLinkText": "数字フォーマット", - "kbn.advancedSettings.format.percentFormatText": "「パーセント」フォーマットのデフォルト {numeralFormatLink} です", - "kbn.advancedSettings.format.percentFormatTitle": "パーセントフォーマット", "kbn.advancedSettings.histogram.barTargetText": "日付ヒストグラムで「自動」間隔を使用する際、この数に近いバーの作成を試みます", "kbn.advancedSettings.histogram.barTargetTitle": "目標バー数", "kbn.advancedSettings.histogram.maxBarsText": "日付ヒストグラムに表示されるバーの数の上限です。必要に応じて値をスケーリングしてください", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2c2e532596983..a13912bc6c53c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1000,23 +1000,11 @@ "kbn.advancedSettings.docTableHighlightTitle": "突出显示结果", "kbn.advancedSettings.fieldsPopularLimitText": "要显示的排名前 N 最常见字段", "kbn.advancedSettings.fieldsPopularLimitTitle": "常见字段限制", - "kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText": "数值格式", - "kbn.advancedSettings.format.bytesFormatText": "“字节”格式的默认{numeralFormatLink}", - "kbn.advancedSettings.format.bytesFormatTitle": "字节格式", - "kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText": "数值格式", - "kbn.advancedSettings.format.currencyFormatText": "“货币”格式的默认{numeralFormatLink}", - "kbn.advancedSettings.format.currencyFormatTitle": "货币格式", "kbn.advancedSettings.format.defaultTypeMapText": "要默认用于每个字段类型的格式名称的映射。如果未显式提及字段类型,则将使用{defaultFormat}", "kbn.advancedSettings.format.defaultTypeMapTitle": "字段类型格式名称", - "kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText": "数值语言", - "kbn.advancedSettings.format.formattingLocaleText": "{numeralLanguageLink}区域设置", - "kbn.advancedSettings.format.formattingLocaleTitle": "格式区域设置", "kbn.advancedSettings.format.numberFormat.numeralFormatLinkText": "数值格式", "kbn.advancedSettings.format.numberFormatText": "“数字”格式的默认{numeralFormatLink}", "kbn.advancedSettings.format.numberFormatTitle": "数字格式", - "kbn.advancedSettings.format.percentFormat.numeralFormatLinkText": "数值格式", - "kbn.advancedSettings.format.percentFormatText": "“百分比”格式的默认{numeralFormatLink}", - "kbn.advancedSettings.format.percentFormatTitle": "百分比格式", "kbn.advancedSettings.histogram.barTargetText": "在日期直方图中使用“auto”时尝试生成大约此数目的条形", "kbn.advancedSettings.histogram.barTargetTitle": "目标条形数", "kbn.advancedSettings.histogram.maxBarsText": "在日期直方图中不要显示超过该数目的条形", diff --git a/x-pack/test/reporting/api/generation_urls.js b/x-pack/test/reporting/api/generation_urls.js index b98b1a26651a1..600489100486b 100644 --- a/x-pack/test/reporting/api/generation_urls.js +++ b/x-pack/test/reporting/api/generation_urls.js @@ -5,12 +5,12 @@ */ export const PDF_PRINT_DASHBOARD_6_3 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F2ae34a60-3dd4-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!(),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%27145ced90-3dcb-11e8-8660-4d65aa086b3c!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:e2023110-3dcb-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:visualization,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!f,title:!%27couple%2Bpanels!%27,viewMode:view)%27),title:%27couple%20panels%27)'; + '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,browserLocales:!(de),layout:(id:print),objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F2ae34a60-3dd4-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!(),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%27145ced90-3dcb-11e8-8660-4d65aa086b3c!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:e2023110-3dcb-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:visualization,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!f,title:!%27couple%2Bpanels!%27,viewMode:view)%27),title:%27couple%20panels%27)'; export const PDF_PRESERVE_DASHBOARD_FILTER_6_3 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(dimensions:(height:439,width:1362),id:preserve_layout),objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F61c58ad0-3dd3-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:animal,negate:!!f,params:(query:dog,type:phrase),type:phrase,value:dog),query:(match:(animal:(query:dog,type:phrase))))),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%2750643b60-3dd3-11e8-b2b9-5d5dc1715159!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:a16d1990-3dca-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:search,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!t,title:!%27dashboard%2Bwith%2Bfilter!%27,viewMode:view)%27),title:%27dashboard%20with%20filter%27)'; + '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,browserLocales:!(de),layout:(dimensions:(height:439,width:1362),id:preserve_layout),objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F61c58ad0-3dd3-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:animal,negate:!!f,params:(query:dog,type:phrase),type:phrase,value:dog),query:(match:(animal:(query:dog,type:phrase))))),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%2750643b60-3dd3-11e8-b2b9-5d5dc1715159!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:a16d1990-3dca-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:search,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!t,title:!%27dashboard%2Bwith%2Bfilter!%27,viewMode:view)%27),title:%27dashboard%20with%20filter%27)'; export const PDF_PRESERVE_PIE_VISUALIZATION_6_3 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(dimensions:(height:441,width:1002),id:preserve_layout),objectType:visualization,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2F3fe22200-3dcb-11e8-8660-4d65aa086b3c%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!(),linked:!!f,query:(language:lucene,query:!%27!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:bytes,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Rendering%2BTest:%2Bpie!%27,type:pie))%27),title:%27Rendering%20Test:%20pie%27)'; + '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,browserLocales:!(de),layout:(dimensions:(height:441,width:1002),id:preserve_layout),objectType:visualization,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2F3fe22200-3dcb-11e8-8660-4d65aa086b3c%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!(),linked:!!f,query:(language:lucene,query:!%27!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:bytes,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Rendering%2BTest:%2Bpie!%27,type:pie))%27),title:%27Rendering%20Test:%20pie%27)'; export const PDF_PRINT_PIE_VISUALIZATION_FILTER_AND_SAVED_SEARCH_6_3 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:visualization,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2Fbefdb6b0-3e59-11e8-9fc3-39e49624228e%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:animal.keyword,negate:!!f,params:(query:dog,type:phrase),type:phrase,value:dog),query:(match:(animal.keyword:(query:dog,type:phrase))))),linked:!!t,query:(language:lucene,query:!%27!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:name.keyword,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Filter%2BTest:%2Banimals:%2Blinked%2Bto%2Bsearch%2Bwith%2Bfilter!%27,type:pie))%27),title:%27Filter%20Test:%20animals:%20linked%20to%20search%20with%20filter%27)'; + '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,browserLocales:!(de),layout:(id:print),objectType:visualization,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2Fbefdb6b0-3e59-11e8-9fc3-39e49624228e%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:animal.keyword,negate:!!f,params:(query:dog,type:phrase),type:phrase,value:dog),query:(match:(animal.keyword:(query:dog,type:phrase))))),linked:!!t,query:(language:lucene,query:!%27!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:name.keyword,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Filter%2BTest:%2Banimals:%2Blinked%2Bto%2Bsearch%2Bwith%2Bfilter!%27,type:pie))%27),title:%27Filter%20Test:%20animals:%20linked%20to%20search%20with%20filter%27)'; export const CSV_DISCOVER_KUERY_AND_FILTER_6_3 = '/api/reporting/generate/csv?jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,agent,bytes,clientip),indexPatternId:%270bf35f60-3dc9-11e8-8660-4d65aa086b3c%27,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,agent,bytes,clientip)),docvalue_fields:!(%27@timestamp%27),query:(bool:(filter:!((bool:(minimum_should_match:1,should:!((match:(clientip:%2773.14.212.83%27)))))),must:!((range:(bytes:(gte:100,lt:1000))),(range:(%27@timestamp%27:(format:epoch_millis,gte:1369165215770,lte:1526931615770)))),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,agent,bytes,clientip),version:!t),index:%27logstash-*%27),title:%27Bytes%20and%20kuery%20in%20saved%20search%20with%20filter%27,type:search)'; diff --git a/yarn.lock b/yarn.lock index 3563ee3fc2733..4d4741df5b59b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2036,10 +2036,10 @@ resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-1.0.0.tgz#4d325df333fe1319556bb4d54214098ada1171d4" integrity sha512-bbjbEyILPRTRt0xnda18OttLtlkJBPuXx3CjISUSn9jhWqHoFMzfOaZ73D5jxZE2SaFZUrJYfPpqXP6qqPufAQ== -"@elastic/numeral@2.3.3": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.3.tgz#94d38a35bd315efa7a6918b22695128fc40a885e" - integrity sha512-0OyB9oztlYIq8F1LHjcNf+T089PKfYw78tgUY+q2dtox/jmb4xzFKtI9kv1hwAt5tcgBUTtUMK9kszpSh1UZaQ== +"@elastic/numeral@2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.5.tgz#fcaeac57ddc55cd4b7f0b9c7e070c242dd5d0600" + integrity sha512-lJVZHPuI2csrfwDIEdKedFqNIF+5YsyqvX2soAqhu49iKOd9n7tifLRn30vP6D7oKd+6HsiGfPzT0nzdJWsexQ== "@elastic/request-crypto@^1.0.2": version "1.0.2"