From 7baea1d737f31c8adac86121313013b597d6600a Mon Sep 17 00:00:00 2001 From: pavel06081991 Date: Tue, 20 Nov 2018 10:19:20 +0300 Subject: [PATCH] Feature/dashboard translations (#24328) add dashboard translations --- .../exit_full_screen_button.test.js.snap | 2 +- .../components/exit_full_screen_button.js | 25 ++- .../exit_full_screen_button.test.js | 20 +-- .../public/dashboard/dashboard_app.html | 63 ++++++- .../kibana/public/dashboard/dashboard_app.js | 39 +++- .../dashboard/dashboard_state_manager.js | 5 +- .../public/dashboard/dashboard_strings.js | 22 ++- .../__snapshots__/dashboard_grid.test.js.snap | 4 +- .../public/dashboard/grid/dashboard_grid.js | 12 +- .../dashboard/grid/dashboard_grid.test.js | 12 +- .../grid/dashboard_grid_container.test.js | 6 +- .../kibana/public/dashboard/index.js | 32 +++- .../dashboard_listing.test.js.snap | 167 ++++++++++++----- .../dashboard/listing/dashboard_listing.js | 169 +++++++++++++++--- .../listing/dashboard_listing.test.js | 14 +- .../public/dashboard/panel/dashboard_panel.js | 17 +- .../dashboard/panel/dashboard_panel.test.js | 6 +- .../panel/dashboard_panel_container.js | 6 +- .../panel/dashboard_panel_container.test.js | 4 +- .../get_customize_panel_action.tsx | 9 +- .../panel_actions/get_edit_panel_action.tsx | 5 +- .../get_inspector_panel_action.tsx | 5 +- .../panel_actions/get_remove_panel_action.tsx | 5 +- .../get_toggle_expand_panel_action.tsx | 9 +- .../panel/panel_header/panel_header.tsx | 22 ++- .../panel_header_container.test.tsx | 5 +- .../panel/panel_header/panel_options_menu.tsx | 17 +- .../panel_options_menu_container.ts | 5 +- .../panel_header/panel_options_menu_form.tsx | 29 ++- .../public/dashboard/panel/panel_utils.js | 19 +- .../saved_dashboard/saved_dashboard.js | 4 +- .../saved_dashboard/saved_dashboards.js | 5 +- .../__snapshots__/add_panel.test.js.snap | 12 +- .../__snapshots__/clone_modal.test.js.snap | 24 ++- .../__snapshots__/save_modal.test.js.snap | 24 ++- .../public/dashboard/top_nav/add_panel.js | 46 ++++- .../dashboard/top_nav/add_panel.test.js | 4 +- .../public/dashboard/top_nav/clone_modal.js | 49 ++++- .../dashboard/top_nav/clone_modal.test.js | 8 +- .../dashboard/top_nav/get_top_nav_config.js | 65 +++++-- .../public/dashboard/top_nav/options.js | 22 ++- .../public/dashboard/top_nav/save_modal.js | 22 ++- .../dashboard/top_nav/save_modal.test.js | 4 +- .../dashboard/top_nav/show_add_panel.js | 15 +- .../dashboard/top_nav/show_clone_modal.js | 13 +- .../dashboard/top_nav/show_options_popover.js | 35 ++-- .../viewport/dashboard_viewport_provider.js | 5 +- .../show_saved_object_save_modal.js | 4 +- tsconfig.json | 5 +- 49 files changed, 860 insertions(+), 261 deletions(-) diff --git a/src/core_plugins/kibana/public/dashboard/components/__snapshots__/exit_full_screen_button.test.js.snap b/src/core_plugins/kibana/public/dashboard/components/__snapshots__/exit_full_screen_button.test.js.snap index e41e0bf21420..2fe29dd29a59 100644 --- a/src/core_plugins/kibana/public/dashboard/components/__snapshots__/exit_full_screen_button.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/components/__snapshots__/exit_full_screen_button.test.js.snap @@ -28,7 +28,7 @@ exports[`is rendered 1`] = ` class="dshExitFullScreenButton__text" data-test-subj="exitFullScreenModeText" > - Exit full screen + Exit full screen diff --git a/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.js b/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.js index 1d95ff0d09aa..d24d3a81017a 100644 --- a/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.js +++ b/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.js @@ -20,6 +20,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import chrome from 'ui/chrome'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { KuiButton, @@ -30,7 +31,7 @@ import { EuiScreenReaderOnly, } from '@elastic/eui'; -export class ExitFullScreenButton extends PureComponent { +class ExitFullScreenButtonUi extends PureComponent { onKeyDown = (e) => { if (e.keyCode === keyCodes.ESCAPE) { @@ -49,11 +50,16 @@ export class ExitFullScreenButton extends PureComponent { } render() { + const { intl } = this.props; + return (

- In full screen mode, press ESC to exit. +

- Exit full screen + +
@@ -76,6 +89,8 @@ export class ExitFullScreenButton extends PureComponent { } } -ExitFullScreenButton.propTypes = { +ExitFullScreenButtonUi.propTypes = { onExitFullScreenMode: PropTypes.func.isRequired, }; + +export const ExitFullScreenButton = injectI18n(ExitFullScreenButtonUi); diff --git a/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.test.js b/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.test.js index 0fb74f1a29ff..80a52584cf9b 100644 --- a/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.test.js +++ b/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.test.js @@ -24,7 +24,7 @@ jest.mock('ui/chrome', }), { virtual: true }); import React from 'react'; -import { render, mount } from 'enzyme'; +import { mountWithIntl, renderWithIntl } from 'test_utils/enzyme_helpers'; import sinon from 'sinon'; import chrome from 'ui/chrome'; @@ -36,8 +36,8 @@ import { keyCodes } from '@elastic/eui'; test('is rendered', () => { - const component = render( - {}}/> + const component = renderWithIntl( + {}}/> ); expect(component) @@ -48,8 +48,8 @@ describe('onExitFullScreenMode', () => { test('is called when the button is pressed', () => { const onExitHandler = sinon.stub(); - const component = mount( - + const component = mountWithIntl( + ); component.find('button').simulate('click'); @@ -60,7 +60,7 @@ describe('onExitFullScreenMode', () => { test('is called when the ESC key is pressed', () => { const onExitHandler = sinon.stub(); - mount(); + mountWithIntl(); const escapeKeyEvent = new KeyboardEvent('keydown', { keyCode: keyCodes.ESCAPE }); document.dispatchEvent(escapeKeyEvent); @@ -73,8 +73,8 @@ describe('chrome.setVisible', () => { test('is called with false when the component is rendered', () => { chrome.setVisible = sinon.stub(); - const component = mount( - {}} /> + const component = mountWithIntl( + {}} /> ); component.find('button').simulate('click'); @@ -84,8 +84,8 @@ describe('chrome.setVisible', () => { }); test('is called with true the component is unmounted', () => { - const component = mount( - {}} /> + const component = mountWithIntl( + {}} /> ); chrome.setVisible = sinon.stub(); diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/core_plugins/kibana/public/dashboard/dashboard_app.html index cc020f33e3db..18c40bda026f 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -15,7 +15,12 @@ aria-level="1" ng-if="showPluginBreadcrumbs">
- Dashboard +
{{ getDashTitle() }} @@ -46,22 +51,64 @@ ng-show="getShouldShowEditHelp()" class="dshStartScreen" > -

- This dashboard is empty. Let’s fill it up! +

-

- Click the Add button in the menu bar above to add a visualization to the dashboard.
If you haven't set up any visualizations yet, visit the Visualize app to create your first visualization. +

+ + +

-

- This dashboard is empty. Let’s fill it up! +

- Click the Edit button in the menu bar above to start working on your new dashboard. + + +

diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_app.js b/src/core_plugins/kibana/public/dashboard/dashboard_app.js index 13f6c1006c95..3a1fd54a24de 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_app.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_app.js @@ -56,7 +56,6 @@ import { timefilter } from 'ui/timefilter'; import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; import { DashboardViewportProvider } from './viewport/dashboard_viewport_provider'; -import { i18n } from '@kbn/i18n'; const app = uiModules.get('app/dashboard', [ 'elasticsearch', @@ -90,7 +89,8 @@ app.directive('dashboardApp', function ($injector) { getAppState, dashboardConfig, localStorage, - breadcrumbState + breadcrumbState, + i18n, ) { const filterManager = Private(FilterManagerProvider); const filterBar = Private(FilterBarQueryFilterProvider); @@ -184,7 +184,7 @@ app.directive('dashboardApp', function ($injector) { const updateBreadcrumbs = () => { breadcrumbState.set([ { - text: i18n.translate('kbn.dashboard.dashboardAppBreadcrumbsTitle', { + text: i18n('kbn.dashboard.dashboardAppBreadcrumbsTitle', { defaultMessage: 'Dashboard', }), href: $scope.landingPageUrl() @@ -273,14 +273,22 @@ app.directive('dashboardApp', function ($injector) { } confirmModal( - `Once you discard your changes, there's no getting them back.`, + i18n('kbn.dashboard.changeViewModeConfirmModal.discardChangesDescription', + { defaultMessage: `Once you discard your changes, there's no getting them back.` } + ), { onConfirm: revertChangesAndExitEditMode, onCancel: _.noop, - confirmButtonText: 'Discard changes', - cancelButtonText: 'Continue editing', + confirmButtonText: i18n('kbn.dashboard.changeViewModeConfirmModal.confirmButtonLabel', + { defaultMessage: 'Discard changes' } + ), + cancelButtonText: i18n('kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel', + { defaultMessage: 'Continue editing' } + ), defaultFocusedButton: ConfirmationButtonTypes.CANCEL, - title: 'Discard changes to dashboard?' + title: i18n('kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle', + { defaultMessage: 'Discard changes to dashboard?' } + ) } ); }; @@ -302,7 +310,12 @@ app.directive('dashboardApp', function ($injector) { .then(function (id) { if (id) { toastNotifications.addSuccess({ - title: `Dashboard '${dash.title}' was saved`, + title: i18n('kbn.dashboard.dashboardWasSavedSuccessMessage', + { + defaultMessage: `Dashboard '{dashTitle}' was saved`, + values: { dashTitle: dash.title }, + }, + ), 'data-test-subj': 'saveDashboardSuccess', }); @@ -316,7 +329,15 @@ app.directive('dashboardApp', function ($injector) { return { id }; }).catch((error) => { toastNotifications.addDanger({ - title: `Dashboard '${dash.title}' was not saved. Error: ${error.message}`, + title: i18n('kbn.dashboard.dashboardWasNotSavedDangerMessage', + { + defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`, + values: { + dashTitle: dash.title, + errorMessage: error.message, + }, + }, + ), 'data-test-subj': 'saveDashboardFailure', }); return { error }; diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js b/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js index a9a937231859..aea6ba3b9642 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import moment from 'moment'; @@ -550,7 +551,9 @@ export class DashboardStateManager { */ syncTimefilterWithDashboard(timeFilter, quickTimeRanges) { if (!this.getIsTimeSavedWithDashboard()) { - throw new Error('The time is not saved with this dashboard so should not be synced.'); + throw new Error(i18n.translate('kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', { + defaultMessage: 'The time is not saved with this dashboard so should not be synced.', + })); } let mode; diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_strings.js b/src/core_plugins/kibana/public/dashboard/dashboard_strings.js index 7dffc94c9e97..a0d2af3e9c00 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_strings.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_strings.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { DashboardViewMode } from './dashboard_view_mode'; /** @@ -28,10 +29,21 @@ import { DashboardViewMode } from './dashboard_view_mode'; */ export function getDashboardTitle(title, viewMode, isDirty) { const isEditMode = viewMode === DashboardViewMode.EDIT; - const unsavedSuffix = isEditMode && isDirty - ? ' (unsaved)' - : ''; + let displayTitle; - const displayTitle = `${title}${unsavedSuffix}`; - return isEditMode ? 'Editing ' + displayTitle : displayTitle; + if (isEditMode && isDirty) { + displayTitle = i18n.translate('kbn.dashboard.strings.dashboardUnsavedEditTitle', { + defaultMessage: 'Editing {title} (unsaved)', + values: { title }, + }); + } else if (isEditMode) { + displayTitle = i18n.translate('kbn.dashboard.strings.dashboardEditTitle', { + defaultMessage: 'Editing {title}', + values: { title }, + }); + } else { + displayTitle = title; + } + + return displayTitle; } diff --git a/src/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap b/src/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap index a098c22af236..806e11c557a0 100644 --- a/src/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap @@ -33,7 +33,7 @@ exports[`renders DashboardGrid 1`] = ` } } > - - { }); test('renders DashboardGrid', () => { - const component = shallow(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); - const panelElements = component.find('Connect(DashboardPanel)'); + const panelElements = component.find('Connect(InjectIntl(DashboardPanelUi))'); expect(panelElements.length).toBe(2); }); test('renders DashboardGrid with no visualizations', () => { - const component = shallow(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); test('adjusts z-index of focused panel to be higher than siblings', () => { - const component = shallow(); - const panelElements = component.find('Connect(DashboardPanel)'); + const component = shallowWithIntl(); + const panelElements = component.find('Connect(InjectIntl(DashboardPanelUi))'); panelElements.first().prop('onPanelFocused')('1'); const [gridItem1, gridItem2] = component.update().findWhere(el => el.key() === '1' || el.key() === '2'); expect(gridItem1.props.style.zIndex).toEqual('2'); diff --git a/src/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js b/src/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js index 1a7e3db04868..12d5d42c811f 100644 --- a/src/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js +++ b/src/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js @@ -18,7 +18,7 @@ */ import React from 'react'; -import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { Provider } from 'react-redux'; import _ from 'lodash'; import sizeMe from 'react-sizeme'; @@ -94,7 +94,7 @@ test('loads old panel data in the right order', () => { store.dispatch(updatePanels(panelData)); store.dispatch(updateUseMargins(false)); - const grid = mount(); + const grid = mountWithIntl(); const panels = store.getState().dashboard.panels; expect(Object.keys(panels).length).toBe(16); @@ -130,7 +130,7 @@ test('loads old panel data in the right order with margins', () => { store.dispatch(updatePanels(panelData)); store.dispatch(updateUseMargins(true)); - const grid = mount(); + const grid = mountWithIntl(); const panels = store.getState().dashboard.panels; expect(Object.keys(panels).length).toBe(16); diff --git a/src/core_plugins/kibana/public/dashboard/index.js b/src/core_plugins/kibana/public/dashboard/index.js index 3c302ec41bb6..a2965219d68b 100644 --- a/src/core_plugins/kibana/public/dashboard/index.js +++ b/src/core_plugins/kibana/public/dashboard/index.js @@ -17,6 +17,7 @@ * under the License. */ +import { injectI18nProvider } from '@kbn/i18n/react'; import './dashboard_app'; import './saved_dashboard/saved_dashboards'; import './dashboard_config'; @@ -34,7 +35,6 @@ import { recentlyAccessed } from 'ui/persisted_log'; import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { uiModules } from 'ui/modules'; -import { i18n } from '@kbn/i18n'; const app = uiModules.get('app/dashboard', [ 'ngRoute', @@ -42,16 +42,22 @@ const app = uiModules.get('app/dashboard', [ ]); app.directive('dashboardListing', function (reactDirective) { - return reactDirective(DashboardListing); + return reactDirective(injectI18nProvider(DashboardListing)); }); +function createNewDashboardCtrl($scope, i18n) { + $scope.visitVisualizeAppLinkText = i18n('kbn.dashboard.visitVisualizeAppLinkText', { + defaultMessage: 'visit the Visualize app', + }); +} + uiRoutes .defaults(/dashboard/, { requireDefaultIndex: true }) .when(DashboardConstants.LANDING_PAGE_PATH, { template: dashboardListingTemplate, - controller($injector, $location, $scope, Private, config, breadcrumbState) { + controller($injector, $location, $scope, Private, config, breadcrumbState, i18n) { const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; const dashboardConfig = $injector.get('dashboardConfig'); @@ -65,7 +71,7 @@ uiRoutes $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); $scope.initialFilter = ($location.search()).filter || EMPTY_FILTER; breadcrumbState.set([{ - text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { + text: i18n('kbn.dashboard.dashboardBreadcrumbsTitle', { defaultMessage: 'Dashboards', }), }]); @@ -98,6 +104,7 @@ uiRoutes }) .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { template: dashboardTemplate, + controller: createNewDashboardCtrl, resolve: { dash: function (savedDashboards, redirectWhenMissing) { return savedDashboards.get() @@ -109,8 +116,9 @@ uiRoutes }) .when(createDashboardEditUrl(':id'), { template: dashboardTemplate, + controller: createNewDashboardCtrl, resolve: { - dash: function (savedDashboards, Notifier, $route, $location, redirectWhenMissing, kbnUrl, AppState) { + dash: function (savedDashboards, Notifier, $route, $location, redirectWhenMissing, kbnUrl, AppState, i18n) { const id = $route.current.params.id; return savedDashboards.get(id) @@ -131,7 +139,9 @@ uiRoutes if (error instanceof SavedObjectNotFound && id === 'create') { // Note "new AppState" is necessary so the state in the url is preserved through the redirect. kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); - toastNotifications.addWarning('The url "dashboard/create" was removed in 6.0. Please update your bookmarks.'); + toastNotifications.addWarning(i18n('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', + { defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.' } + )); } else { throw error; } @@ -143,11 +153,15 @@ uiRoutes } }); -FeatureCatalogueRegistryProvider.register(() => { +FeatureCatalogueRegistryProvider.register((i18n) => { return { id: 'dashboard', - title: 'Dashboard', - description: 'Display and share a collection of visualizations and saved searches.', + title: i18n('kbn.dashboard.featureCatalogue.dashboardTitle', { + defaultMessage: 'Dashboard', + }), + description: i18n('kbn.dashboard.featureCatalogue.dashboardDescription', { + defaultMessage: 'Display and share a collection of visualizations and saved searches.', + }), icon: 'dashboardApp', path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, showOnHomePage: true, diff --git a/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap index cd45b2ae64b1..e350b43c4028 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap @@ -22,7 +22,11 @@ exports[`after fetch hideWriteControls 1`] = ` color="subdued" component="span" > - Looks like you don't have any dashboards. + @@ -64,7 +68,11 @@ exports[`after fetch initialFilter 1`] = ` textTransform="none" >

- Dashboards +

@@ -80,7 +88,11 @@ exports[`after fetch initialFilter 1`] = ` iconSide="left" type="button" > - Create new dashboard + @@ -108,7 +120,7 @@ exports[`after fetch initialFilter 1`] = ` incremental={false} isLoading={false} onChange={[Function]} - placeholder="Search..." + placeholder="Search…" value="my dashboard" /> @@ -157,7 +169,13 @@ exports[`after fetch initialFilter 1`] = ` ] } loading={false} - noItemsMessage="No dashboards matched your search." + noItemsMessage={ + + } onChange={[Function]} pagination={ Object { @@ -210,24 +228,42 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` iconType="plusInCircle" type="button" > - Create new dashboard + } body={

- You can combine data views from any Kibana app into one dashboard and see everything in one place. +

- New to Kibana? - - Install some sample data - - to take a test drive. + + + , + } + } + />

} @@ -235,7 +271,11 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` iconType="dashboardApp" title={

- Create your first dashboard +

} /> @@ -278,7 +318,11 @@ exports[`after fetch renders table rows 1`] = ` textTransform="none" >

- Dashboards +

@@ -294,7 +338,11 @@ exports[`after fetch renders table rows 1`] = ` iconSide="left" type="button" > - Create new dashboard + @@ -322,7 +370,7 @@ exports[`after fetch renders table rows 1`] = ` incremental={false} isLoading={false} onChange={[Function]} - placeholder="Search..." + placeholder="Search…" value="" /> @@ -371,7 +419,13 @@ exports[`after fetch renders table rows 1`] = ` ] } loading={false} - noItemsMessage="No dashboards matched your search." + noItemsMessage={ + + } onChange={[Function]} pagination={ Object { @@ -432,7 +486,11 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` textTransform="none" >

- Dashboards +

@@ -448,7 +506,11 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` iconSide="left" type="button" > - Create new dashboard + @@ -460,26 +522,39 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` color="warning" iconType="help" size="m" - title="Listing limit exceeded" + title={ + + } >

- You have - 2 - dashboards, but your - - listingLimit - - setting prevents the table below from displaying more than - 1 - . You can change this setting under - - Advanced Settings - - . + + + , + "listingLimitText": + listingLimit + , + "listingLimitValue": 1, + "totalDashboards": 2, + } + } + />

@@ -556,7 +631,13 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` ] } loading={false} - noItemsMessage="No dashboards matched your search." + noItemsMessage={ + + } onChange={[Function]} pagination={ Object { diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index 2a2aef320880..b7af6a616ba2 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -19,6 +19,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; import { toastNotifications } from 'ui/notify'; import { @@ -49,7 +50,7 @@ export const EMPTY_FILTER = ''; // and not supporting server-side paging. // This component does not try to tackle these problems (yet) and is just feature matching the legacy component // TODO support server side sorting/paging once title and description are sortable on the server. -export class DashboardListing extends React.Component { +class DashboardListingUi extends React.Component { constructor(props) { super(props); @@ -111,7 +112,12 @@ export class DashboardListing extends React.Component { await this.props.delete(this.state.selectedIds); } catch (error) { toastNotifications.addDanger({ - title: `Unable to delete dashboard(s)`, + title: ( + + ), text: `${error}`, }); } @@ -194,14 +200,34 @@ export class DashboardListing extends React.Component { return ( + } onCancel={this.closeDeleteModal} onConfirm={this.deleteSelectedItems} - cancelButtonText="Cancel" - confirmButtonText="Delete" + cancelButtonText={ + + } + confirmButtonText={ + + } defaultFocusedButton="cancel" > -

{`You can't recover deleted dashboards.`}

+

+ +

); @@ -212,14 +238,38 @@ export class DashboardListing extends React.Component { return ( + } color="warning" iconType="help" >

- You have {this.state.totalDashboards} dashboards, - but your listingLimit setting prevents the table below from displaying more than {this.props.listingLimit}. - You can change this setting under Advanced Settings. + + listingLimit + + ), + advancedSettingsLink: ( + + + + ) + }} + />

@@ -233,7 +283,12 @@ export class DashboardListing extends React.Component { return ''; } - return 'No dashboards matched your search.'; + return ( + + ); } renderNoItemsMessage() { @@ -243,7 +298,10 @@ export class DashboardListing extends React.Component {

- {`Looks like you don't have any dashboards.`} +

@@ -254,14 +312,37 @@ export class DashboardListing extends React.Component {
Create your first dashboard} + title={ +

+ +

+ } body={

- You can combine data views from any Kibana app into one dashboard and see everything in one place. +

- New to Kibana? Install some sample data to take a test drive. + + + + ), + }} + />

} @@ -272,7 +353,10 @@ export class DashboardListing extends React.Component { iconType="plusInCircle" data-test-subj="createDashboardPromptButton" > - Create new dashboard + } /> @@ -282,6 +366,7 @@ export class DashboardListing extends React.Component { } renderSearchBar() { + const { intl } = this.props; let deleteBtn; if (this.state.selectedIds.length > 0) { deleteBtn = ( @@ -292,7 +377,10 @@ export class DashboardListing extends React.Component { data-test-subj="deleteSelectedDashboards" key="delete" > - Delete selected + ); @@ -303,8 +391,14 @@ export class DashboardListing extends React.Component { {deleteBtn} { @@ -320,10 +414,14 @@ export class DashboardListing extends React.Component { } renderTable() { + const { intl } = this.props; const tableColumns = [ { field: 'title', - name: 'Title', + name: intl.formatMessage({ + id: 'kbn.dashboard.listing.table.titleColumnName', + defaultMessage: 'Title', + }), sortable: true, render: (field, record) => ( { @@ -351,7 +455,10 @@ export class DashboardListing extends React.Component { - Edit + ); } @@ -413,7 +520,10 @@ export class DashboardListing extends React.Component { href={`#${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`} data-test-subj="newDashboardLink" > - Create new dashboard + ); @@ -426,7 +536,10 @@ export class DashboardListing extends React.Component {

- Dashboards +

@@ -471,7 +584,7 @@ export class DashboardListing extends React.Component { } } -DashboardListing.propTypes = { +DashboardListingUi.propTypes = { find: PropTypes.func.isRequired, delete: PropTypes.func.isRequired, listingLimit: PropTypes.number.isRequired, @@ -479,6 +592,8 @@ DashboardListing.propTypes = { initialFilter: PropTypes.string, }; -DashboardListing.defaultProps = { +DashboardListingUi.defaultProps = { initialFilter: EMPTY_FILTER, }; + +export const DashboardListing = injectI18n(DashboardListingUi); diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js index e1a95b65982a..9fcb0c6bab90 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js @@ -36,7 +36,7 @@ jest.mock('lodash', }), { virtual: true }); import React from 'react'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardListing, @@ -58,7 +58,7 @@ const find = (num) => { }; test('renders empty page in before initial fetch to avoid flickering', () => { - const component = shallow( {}} listingLimit={1000} @@ -69,7 +69,7 @@ test('renders empty page in before initial fetch to avoid flickering', () => { describe('after fetch', () => { test('initialFilter', async () => { - const component = shallow( {}} listingLimit={1000} @@ -86,7 +86,7 @@ describe('after fetch', () => { }); test('renders table rows', async () => { - const component = shallow( {}} listingLimit={1000} @@ -102,7 +102,7 @@ describe('after fetch', () => { }); test('renders call to action when no dashboards exist', async () => { - const component = shallow( {}} listingLimit={1} @@ -118,7 +118,7 @@ describe('after fetch', () => { }); test('hideWriteControls', async () => { - const component = shallow( {}} listingLimit={1} @@ -134,7 +134,7 @@ describe('after fetch', () => { }); test('renders warning when listingLimit is exceeded', async () => { - const component = shallow( {}} listingLimit={1} diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js index 684fe6c54a22..1ec58d197154 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js @@ -19,6 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import classNames from 'classnames'; import _ from 'lodash'; @@ -29,11 +30,14 @@ import { EuiPanel, } from '@elastic/eui'; -export class DashboardPanel extends React.Component { +class DashboardPanelUi extends React.Component { constructor(props) { super(props); this.state = { - error: props.embeddableFactory ? null : `No factory found for embeddable`, + error: props.embeddableFactory ? null : props.intl.formatMessage({ + id: 'kbn.dashboard.panel.noEmbeddableFactoryErrorMessage', + defaultMessage: 'No factory found for embeddable', + }), }; this.mounted = false; @@ -100,7 +104,10 @@ export class DashboardPanel extends React.Component { className="panel-content" ref={panelElement => this.panelElement = panelElement} > - {!this.props.initialized && 'loading...'} + {!this.props.initialized && }
); } @@ -151,7 +158,7 @@ export class DashboardPanel extends React.Component { } } -DashboardPanel.propTypes = { +DashboardPanelUi.propTypes = { viewOnlyMode: PropTypes.bool.isRequired, onPanelFocused: PropTypes.func, onPanelBlurred: PropTypes.func, @@ -179,3 +186,5 @@ DashboardPanel.propTypes = { panelIndex: PropTypes.string, }).isRequired, }; + +export const DashboardPanel = injectI18n(DashboardPanelUi); diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js index 5f142a8b1e33..eb8258f43833 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js @@ -19,7 +19,7 @@ import React from 'react'; import _ from 'lodash'; -import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardPanel } from './dashboard_panel'; import { DashboardViewMode } from '../dashboard_view_mode'; import { PanelError } from '../panel/panel_error'; @@ -62,7 +62,7 @@ beforeAll(() => { }); test('DashboardPanel matches snapshot', () => { - const component = mount(); + const component = mountWithIntl(); expect(takeMountedSnapshot(component)).toMatchSnapshot(); }); @@ -71,7 +71,7 @@ test('renders an error when error prop is passed', () => { error: 'Simulated error' }); - const component = mount(); + const component = mountWithIntl(); const panelError = component.find(PanelError); expect(panelError.length).toBe(1); }); diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.js index 1b62a93f2cfb..556c81d32f8d 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.js @@ -19,6 +19,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; +import { i18n } from '@kbn/i18n'; import { DashboardPanel } from './dashboard_panel'; import { DashboardViewMode } from '../dashboard_view_mode'; @@ -40,7 +41,10 @@ const mapStateToProps = ({ dashboard }, { embeddableFactory, panelId }) => { let error = null; if (!embeddableFactory) { const panelType = getPanelType(dashboard, panelId); - error = `No embeddable factory found for panel type ${panelType}`; + error = i18n.translate('kbn.dashboard.panel.noFoundEmbeddableFactoryErrorMessage', { + defaultMessage: 'No embeddable factory found for panel type {panelType}', + values: { panelType }, + }); } else { error = (embeddable && getEmbeddableError(dashboard, panelId)) || ''; } diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.js index 2af88510b715..cb532f1a48e3 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.js @@ -19,7 +19,7 @@ import React from 'react'; import _ from 'lodash'; -import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardPanelContainer } from './dashboard_panel_container'; import { DashboardViewMode } from '../dashboard_view_mode'; import { PanelError } from '../panel/panel_error'; @@ -52,7 +52,7 @@ test('renders an error when embeddableFactory.create throws an error', (done) => throw new Error('simulated error'); }); }; - const component = mount(); + const component = mountWithIntl(); setTimeout(() => { component.update(); const panelError = component.find(PanelError); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx index f7cd97666b5c..dd059c47475c 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx @@ -18,6 +18,7 @@ */ import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { ContextMenuAction, ContextMenuPanel } from 'ui/embeddable'; import { DashboardViewMode } from '../../../dashboard_view_mode'; @@ -36,7 +37,9 @@ export function getCustomizePanelAction({ }): ContextMenuAction { return new ContextMenuAction( { - displayName: 'Customize panel', + displayName: i18n.translate('kbn.dashboard.panel.customizePanel.displayName', { + defaultMessage: 'Customize panel', + }), id: 'customizePanel', parentPanelId: 'mainMenu', }, @@ -44,7 +47,9 @@ export function getCustomizePanelAction({ childContextMenuPanel: new ContextMenuPanel( { id: 'panelSubOptionsMenu', - title: 'Customize panel', + title: i18n.translate('kbn.dashboard.panel.customizePanelTitle', { + defaultMessage: 'Customize panel', + }), }, { getContent: () => ( diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx index 88bc0001a9ac..f4413fc5539e 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx @@ -20,6 +20,7 @@ import React from 'react'; import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ContextMenuAction } from 'ui/embeddable'; import { DashboardViewMode } from '../../../dashboard_view_mode'; @@ -31,7 +32,9 @@ import { DashboardViewMode } from '../../../dashboard_view_mode'; export function getEditPanelAction() { return new ContextMenuAction( { - displayName: 'Edit visualization', + displayName: i18n.translate('kbn.dashboard.panel.editPanel.displayName', { + defaultMessage: 'Edit visualization', + }), id: 'editPanel', parentPanelId: 'mainMenu', }, diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx index 2739e2859a42..01f6da642110 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx @@ -20,6 +20,7 @@ import React from 'react'; import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ContextMenuAction } from 'ui/embeddable'; import { Inspector } from 'ui/inspector'; @@ -41,7 +42,9 @@ export function getInspectorPanelAction({ return new ContextMenuAction( { id: 'openInspector', - displayName: 'Inspect', + displayName: i18n.translate('kbn.dashboard.panel.inspectorPanel.displayName', { + defaultMessage: 'Inspect', + }), parentPanelId: 'mainMenu', }, { diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx index fce94f24b16c..47718a5f21b3 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx @@ -18,6 +18,7 @@ */ import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { ContextMenuAction } from 'ui/embeddable'; @@ -31,7 +32,9 @@ import { DashboardViewMode } from '../../../dashboard_view_mode'; export function getRemovePanelAction(onDeletePanel: () => void) { return new ContextMenuAction( { - displayName: 'Delete from dashboard', + displayName: i18n.translate('kbn.dashboard.panel.removePanel.displayName', { + defaultMessage: 'Delete from dashboard', + }), id: 'deletePanel', parentPanelId: 'mainMenu', }, diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx index 27dca29c01ba..80d43347b308 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx @@ -18,6 +18,7 @@ */ import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { ContextMenuAction } from 'ui/embeddable'; @@ -37,7 +38,13 @@ export function getToggleExpandPanelAction({ }) { return new ContextMenuAction( { - displayName: isExpanded ? 'Minimize' : 'Full screen', + displayName: isExpanded + ? i18n.translate('kbn.dashboard.panel.toggleExpandPanel.expandedDisplayName', { + defaultMessage: 'Minimize', + }) + : i18n.translate('kbn.dashboard.panel.toggleExpandPanel.notExpandedDisplayName', { + defaultMessage: 'Full screen', + }), id: 'togglePanel', parentPanelId: 'mainMenu', }, diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx index c0acd5a672a6..9e9e59d79aa9 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx @@ -17,6 +17,7 @@ * under the License. */ +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; import { Embeddable } from 'ui/embeddable'; import { PanelId } from '../../selectors'; @@ -30,13 +31,18 @@ export interface PanelHeaderProps { hidePanelTitles: boolean; } -export function PanelHeader({ +interface PanelHeaderUiProps extends PanelHeaderProps { + intl: InjectedIntl; +} + +function PanelHeaderUi({ title, panelId, embeddable, isViewOnlyMode, hidePanelTitles, -}: PanelHeaderProps) { + intl, +}: PanelHeaderUiProps) { if (isViewOnlyMode && (!title || hidePanelTitles)) { return (
@@ -56,7 +62,15 @@ export function PanelHeader({ data-test-subj="dashboardPanelTitle" className="dshPanel__title" title={title} - aria-label={`Dashboard panel: ${title}`} + aria-label={intl.formatMessage( + { + id: 'kbn.dashboard.panel.dashboardPanelAriaLabel', + defaultMessage: 'Dashboard panel: {title}', + }, + { + title, + } + )} > {hidePanelTitles ? '' : title} @@ -67,3 +81,5 @@ export function PanelHeader({
); } + +export const PanelHeader = injectI18n(PanelHeaderUi); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx index 998c1f6dc3ff..f395b17207be 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx @@ -17,10 +17,11 @@ * under the License. */ -import { mount, ReactWrapper } from 'enzyme'; +import { ReactWrapper } from 'enzyme'; import _ from 'lodash'; import React from 'react'; import { Provider } from 'react-redux'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; // TODO: remove this when EUI supports types for this. // @ts-ignore: implicit any for JS file @@ -77,7 +78,7 @@ afterAll(() => { }); test('Panel header shows embeddable title when nothing is set on the panel', () => { - component = mount( + component = mountWithIntl( diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx index 983a235a719f..25efd58d059e 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx @@ -17,6 +17,7 @@ * under the License. */ +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; import { @@ -34,19 +35,27 @@ export interface PanelOptionsMenuProps { isViewMode: boolean; } -export function PanelOptionsMenu({ +interface PanelOptionsMenuUiProps extends PanelOptionsMenuProps { + intl: InjectedIntl; +} + +function PanelOptionsMenuUi({ toggleContextMenu, isPopoverOpen, closeContextMenu, panels, isViewMode, -}: PanelOptionsMenuProps) { + intl, +}: PanelOptionsMenuUiProps) { const button = ( @@ -70,3 +79,5 @@ export function PanelOptionsMenu({ ); } + +export const PanelOptionsMenu = injectI18n(PanelOptionsMenuUi); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts index 294a7dd2661c..478c59847fe3 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts @@ -18,6 +18,7 @@ */ import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { connect } from 'react-redux'; import { buildEuiContextMenuPanels, @@ -167,7 +168,9 @@ const mergeProps = ( // every panel, every time any state changes. if (isPopoverOpen) { const contextMenuPanel = new ContextMenuPanel({ - title: 'Options', + title: i18n.translate('kbn.dashboard.panel.optionsMenu.optionsContextMenuTitle', { + defaultMessage: 'Options', + }), id: 'mainMenu', }); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx index 4b7b4a3a1341..c80ab00a46b7 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx @@ -20,6 +20,7 @@ import React, { ChangeEvent, KeyboardEvent } from 'react'; import { EuiButtonEmpty, EuiFieldText, EuiFormRow, keyCodes } from '@elastic/eui'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; export interface PanelOptionsMenuFormProps { title?: string; @@ -28,12 +29,17 @@ export interface PanelOptionsMenuFormProps { onClose: () => void; } -export function PanelOptionsMenuForm({ +interface PanelOptionsMenuFormUiProps extends PanelOptionsMenuFormProps { + intl: InjectedIntl; +} + +function PanelOptionsMenuFormUi({ title, onReset, onUpdatePanelTitle, onClose, -}: PanelOptionsMenuFormProps) { + intl, +}: PanelOptionsMenuFormUiProps) { function onInputChange(event: ChangeEvent) { onUpdatePanelTitle(event.target.value); } @@ -46,7 +52,12 @@ export function PanelOptionsMenuForm({ return (
- + - Reset title +
); } + +export const PanelOptionsMenuForm = injectI18n(PanelOptionsMenuFormUi); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_utils.js b/src/core_plugins/kibana/public/dashboard/panel/panel_utils.js index c5b60f1018bb..aec24998f17b 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_utils.js +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_utils.js @@ -18,6 +18,7 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from '../dashboard_constants'; import chrome from 'ui/chrome'; @@ -31,7 +32,10 @@ export class PanelUtils { static convertPanelDataPre_6_1(panel) { // eslint-disable-line camelcase ['col', 'row'].forEach(key => { if (!_.has(panel, key)) { - throw new Error(`Unable to migrate panel data for "6.1.0" backwards compatibility, panel does not contain expected field: ${key}`); + throw new Error(i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage', { + defaultMessage: 'Unable to migrate panel data for "6.1.0" backwards compatibility, panel does not contain expected field: {key}', + values: { key }, + })); } }); @@ -59,7 +63,10 @@ export class PanelUtils { static convertPanelDataPre_6_3(panel, useMargins) { // eslint-disable-line camelcase ['w', 'x', 'h', 'y'].forEach(key => { if (!_.has(panel.gridData, key)) { - throw new Error(`Unable to migrate panel data for "6.3.0" backwards compatibility, panel does not contain expected field: ${key}`); + throw new Error(i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', { + defaultMessage: 'Unable to migrate panel data for "6.3.0" backwards compatibility, panel does not contain expected field: {key}', + values: { key }, + })); } }); @@ -78,7 +85,13 @@ export class PanelUtils { static parseVersion(version = '6.0.0') { const versionSplit = version.split('.'); if (versionSplit.length < 3) { - throw new Error(`Invalid version, ${version}, expected ..`); + throw new Error(i18n.translate('kbn.dashboard.panel.invalidVersionErrorMessage', { + defaultMessage: 'Invalid version, {version}, expected {semver}', + values: { + version, + semver: '..', + }, + })); } return { major: parseInt(versionSplit[0], 10), diff --git a/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js b/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js index cf82b8274b63..04773be6e428 100644 --- a/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js +++ b/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js @@ -26,7 +26,7 @@ import { SavedObjectProvider } from 'ui/courier'; const module = uiModules.get('app/dashboard'); // Used only by the savedDashboards service, usually no reason to change this -module.factory('SavedDashboard', function (Private, config) { +module.factory('SavedDashboard', function (Private, config, i18n) { // SavedDashboard constructor. Usually you'd interact with an instance of this. // ID is option, without it one will be generated on save. const SavedObject = Private(SavedObjectProvider); @@ -43,7 +43,7 @@ module.factory('SavedDashboard', function (Private, config) { // default values that will get assigned if the doc is new defaults: { - title: 'New Dashboard', + title: i18n('kbn.dashboard.savedDashboard.newDashboardTitle', { defaultMessage: 'New Dashboard' }), hits: 0, description: '', panelsJSON: '[]', diff --git a/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.js b/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.js index a4563b71a26f..c4ab8b9a96e3 100644 --- a/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.js +++ b/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import './saved_dashboard'; import { uiModules } from 'ui/modules'; import { SavedObjectLoader } from 'ui/courier/saved_object/saved_object_loader'; @@ -29,7 +30,9 @@ const module = uiModules.get('app/dashboard'); // edited by the object editor. savedObjectManagementRegistry.register({ service: 'savedDashboards', - title: 'dashboards' + title: i18n.translate('kbn.dashboard.savedDashboardsTitle', { + defaultMessage: 'dashboards', + }), }); // This is the only thing that gets injected into controllers diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap index d976372dd2bf..4f96414b427c 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap @@ -16,7 +16,11 @@ exports[`render 1`] = ` textTransform="none" >

- Add Panels +

- Add new Visualization + } key="visSavedObjectFinder" diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/clone_modal.test.js.snap b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/clone_modal.test.js.snap index 80fb7a7ed275..92e8f07ea0da 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/clone_modal.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/clone_modal.test.js.snap @@ -10,7 +10,11 @@ exports[`renders DashboardCloneModal 1`] = ` > - Clone Dashboard + @@ -19,7 +23,11 @@ exports[`renders DashboardCloneModal 1`] = ` size="m" >

- Please enter a new name for your dashboard. +

- Cancel + - Confirm Clone + diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/save_modal.test.js.snap b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/save_modal.test.js.snap index 2112aee0763c..aae42c0b98ce 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/save_modal.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/save_modal.test.js.snap @@ -11,7 +11,13 @@ exports[`renders DashboardSaveModal 1`] = ` describedByIds={Array []} fullWidth={false} hasEmptyLabelSpace={false} - label="Description" + label={ + + } > + } + label={ + + } > - Add new Visualization + ); const tabs = [{ id: VIS_TAB_ID, - name: 'Visualization', + name: props.intl.formatMessage({ + id: 'kbn.dashboard.topNav.addPanel.visualizationTabName', + defaultMessage: 'Visualization', + }), dataTestSubj: 'addVisualizationTab', toastDataTestSubj: 'addVisualizationToDashboardSuccess', savedObjectFinder: ( @@ -59,20 +66,29 @@ export class DashboardAddPanel extends React.Component { callToActionButton={addNewVisBtn} onChoose={this.onAddPanel} visTypes={this.props.visTypes} - noItemsMessage="No matching visualizations found." + noItemsMessage={props.intl.formatMessage({ + id: 'kbn.dashboard.topNav.addPanel.visSavedObjectFinder.noMatchingVisualizationsMessage', + defaultMessage: 'No matching visualizations found.', + })} savedObjectType="visualization" /> ) }, { id: SAVED_SEARCH_TAB_ID, - name: 'Saved Search', + name: props.intl.formatMessage({ + id: 'kbn.dashboard.topNav.addPanel.savedSearchTabName', + defaultMessage: 'Saved Search', + }), dataTestSubj: 'addSavedSearchTab', toastDataTestSubj: 'addSavedSearchToDashboardSuccess', savedObjectFinder: ( ) @@ -115,7 +131,12 @@ export class DashboardAddPanel extends React.Component { } this.lastToast = toastNotifications.addSuccess({ - title: `${this.state.selectedTab.name} was added to your dashboard`, + title: this.props.intl.formatMessage({ + id: 'kbn.dashboard.topNav.addPanel.selectedTabAddedToDashboardSuccessMessageTitle', + defaultMessage: '{selectedTabName} was added to your dashboard', + }, { + selectedTabName: this.state.selectedTab.name, + }), 'data-test-subj': this.state.selectedTab.toastDataTestSubj, }); } @@ -131,7 +152,12 @@ export class DashboardAddPanel extends React.Component { -

Add Panels

+

+ +

@@ -148,9 +174,11 @@ export class DashboardAddPanel extends React.Component { } } -DashboardAddPanel.propTypes = { +DashboardAddPanelUi.propTypes = { onClose: PropTypes.func.isRequired, visTypes: PropTypes.object.isRequired, addNewPanel: PropTypes.func.isRequired, addNewVis: PropTypes.func.isRequired, }; + +export const DashboardAddPanel = injectI18n(DashboardAddPanelUi); diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js b/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js index 9c17980f7b9c..3f233eed6b10 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js @@ -19,7 +19,7 @@ import React from 'react'; import sinon from 'sinon'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardAddPanel, @@ -38,7 +38,7 @@ beforeEach(() => { }); test('render', () => { - const component = shallow( {}} diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.js b/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.js index 5bab97387a14..507eb2b6db34 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.js @@ -19,6 +19,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, @@ -34,7 +35,7 @@ import { EuiCallOut, } from '@elastic/eui'; -export class DashboardCloneModal extends React.Component { +class DashboardCloneModalUi extends React.Component { constructor(props) { super(props); @@ -90,12 +91,30 @@ export class DashboardCloneModal extends React.Component { return (

- Click Confirm Clone to clone the dashboard with the duplicate title. + + + + ), + }} + />

@@ -113,14 +132,20 @@ export class DashboardCloneModal extends React.Component { > - Clone Dashboard +

- Please enter a new name for your dashboard. +

@@ -143,7 +168,10 @@ export class DashboardCloneModal extends React.Component { data-test-subj="cloneCancelButton" onClick={this.props.onClose} > - Cancel + - Confirm Clone + @@ -161,8 +192,10 @@ export class DashboardCloneModal extends React.Component { } } -DashboardCloneModal.propTypes = { +DashboardCloneModalUi.propTypes = { onClone: PropTypes.func, onClose: PropTypes.func, title: PropTypes.string }; + +export const DashboardCloneModal = injectI18n(DashboardCloneModalUi); diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js b/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js index c8154fe3bdd6..3dfaa26b7982 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js @@ -19,7 +19,7 @@ import React from 'react'; import sinon from 'sinon'; -import { mount, shallow } from 'enzyme'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { findTestSubject, } from '@elastic/eui/lib/test'; @@ -37,9 +37,9 @@ beforeEach(() => { onClose = sinon.spy(); }); -function createComponent(creationMethod = mount) { +function createComponent(creationMethod = mountWithIntl) { component = creationMethod( - { - createComponent(shallow); + createComponent(shallowWithIntl); expect(component).toMatchSnapshot(); // eslint-disable-line }); diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.js b/src/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.js index c0ac1cb2702b..969ff3f631de 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { DashboardViewMode } from '../dashboard_view_mode'; import { TopNavIds } from './top_nav_ids'; @@ -57,8 +58,12 @@ export function getTopNavConfig(dashboardMode, actions, hideWriteControls) { function getFullScreenConfig(action) { return { - key: 'full screen', - description: 'Full Screen Mode', + key: i18n.translate('kbn.dashboard.topNave.fullScreenButtonAriaLabel', { + defaultMessage: 'full screen', + }), + description: i18n.translate('kbn.dashboard.topNave.fullScreenConfigDescription', { + defaultMessage: 'Full Screen Mode', + }), testId: 'dashboardFullScreenMode', run: action }; @@ -69,8 +74,12 @@ function getFullScreenConfig(action) { */ function getEditConfig(action) { return { - key: 'edit', - description: 'Switch to edit mode', + key: i18n.translate('kbn.dashboard.topNave.editButtonAriaLabel', { + defaultMessage: 'edit', + }), + description: i18n.translate('kbn.dashboard.topNave.editConfigDescription', { + defaultMessage: 'Switch to edit mode', + }), testId: 'dashboardEditMode', run: action }; @@ -81,8 +90,12 @@ function getEditConfig(action) { */ function getSaveConfig(action) { return { - key: TopNavIds.SAVE, - description: 'Save your dashboard', + key: i18n.translate('kbn.dashboard.topNave.saveButtonAriaLabel', { + defaultMessage: 'save', + }), + description: i18n.translate('kbn.dashboard.topNave.saveConfigDescription', { + defaultMessage: 'Save your dashboard', + }), testId: 'dashboardSaveMenuItem', run: action }; @@ -93,8 +106,12 @@ function getSaveConfig(action) { */ function getViewConfig(action) { return { - key: 'cancel', - description: 'Cancel editing and switch to view-only mode', + key: i18n.translate('kbn.dashboard.topNave.cancelButtonAriaLabel', { + defaultMessage: 'cancel', + }), + description: i18n.translate('kbn.dashboard.topNave.viewConfigDescription', { + defaultMessage: 'Cancel editing and switch to view-only mode', + }), testId: 'dashboardViewOnlyMode', run: action }; @@ -105,8 +122,12 @@ function getViewConfig(action) { */ function getCloneConfig(action) { return { - key: TopNavIds.CLONE, - description: 'Create a copy of your dashboard', + key: i18n.translate('kbn.dashboard.topNave.cloneButtonAriaLabel', { + defaultMessage: 'clone', + }), + description: i18n.translate('kbn.dashboard.topNave.cloneConfigDescription', { + defaultMessage: 'Create a copy of your dashboard', + }), testId: 'dashboardClone', run: action }; @@ -117,8 +138,12 @@ function getCloneConfig(action) { */ function getAddConfig(action) { return { - key: TopNavIds.ADD, - description: 'Add a panel to the dashboard', + key: i18n.translate('kbn.dashboard.topNave.addButtonAriaLabel', { + defaultMessage: 'add', + }), + description: i18n.translate('kbn.dashboard.topNave.addConfigDescription', { + defaultMessage: 'Add a panel to the dashboard', + }), testId: 'dashboardAddPanelButton', run: action }; @@ -129,8 +154,12 @@ function getAddConfig(action) { */ function getShareConfig(action) { return { - key: TopNavIds.SHARE, - description: 'Share Dashboard', + key: i18n.translate('kbn.dashboard.topNave.shareButtonAriaLabel', { + defaultMessage: 'share', + }), + description: i18n.translate('kbn.dashboard.topNave.shareConfigDescription', { + defaultMessage: 'Share Dashboard', + }), testId: 'shareTopNavButton', run: action, }; @@ -141,8 +170,12 @@ function getShareConfig(action) { */ function getOptionsConfig(action) { return { - key: TopNavIds.OPTIONS, - description: 'Options', + key: i18n.translate('kbn.dashboard.topNave.optionsButtonAriaLabel', { + defaultMessage: 'options', + }), + description: i18n.translate('kbn.dashboard.topNave.optionsConfigDescription', { + defaultMessage: 'Options', + }), testId: 'dashboardOptionsButton', run: action, }; diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/options.js b/src/core_plugins/kibana/public/dashboard/top_nav/options.js index 6c42c2d26a25..62c3b65374db 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/options.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/options.js @@ -19,6 +19,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { injectI18n } from '@kbn/i18n/react'; import { EuiForm, @@ -26,7 +27,7 @@ import { EuiSwitch, } from '@elastic/eui'; -export class OptionsMenu extends Component { +class OptionsMenuUi extends Component { state = { darkTheme: this.props.darkTheme, @@ -60,7 +61,10 @@ export class OptionsMenu extends Component { } > } + helpText={} > { - const component = shallow( {}} onClose={() => {}} title="dash title" diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js b/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js index 00719a850aca..d4d8b1a0e404 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js @@ -17,6 +17,7 @@ * under the License. */ +import { I18nProvider } from '@kbn/i18n/react'; import { DashboardAddPanel } from './add_panel'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -43,12 +44,14 @@ export function showAddPanel(addNewPanel, addNewVis, visTypes) { document.body.appendChild(container); const element = ( - + + + ); ReactDOM.render(element, container); } diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.js b/src/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.js index d75c3da660e9..08163a9a3595 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.js @@ -17,9 +17,11 @@ * under the License. */ +import { I18nProvider } from '@kbn/i18n/react'; import { DashboardCloneModal } from './clone_modal'; import React from 'react'; import ReactDOM from 'react-dom'; +import { i18n } from '@kbn/i18n'; export function showCloneModal(onClone, title) { const container = document.createElement('div'); @@ -37,7 +39,16 @@ export function showCloneModal(onClone, title) { }; document.body.appendChild(container); const element = ( - + + + ); ReactDOM.render(element, container); } diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.js b/src/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.js index 2d2dd83b4c34..cf6fba6316e7 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.js @@ -19,6 +19,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; import { OptionsMenu } from './options'; @@ -53,22 +54,24 @@ export function showOptionsPopover({ document.body.appendChild(container); const element = ( - - - + + + + + ); ReactDOM.render(element, container); } diff --git a/src/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js b/src/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js index a219f763dac2..95b66cc1c4c5 100644 --- a/src/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js +++ b/src/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js @@ -19,6 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { I18nProvider } from '@kbn/i18n/react'; import { store } from '../../store'; import { Provider } from 'react-redux'; import { DashboardViewportContainer } from './dashboard_viewport_container'; @@ -26,7 +27,9 @@ import { DashboardViewportContainer } from './dashboard_viewport_container'; export function DashboardViewportProvider(props) { return ( - + + + ); } diff --git a/src/ui/public/saved_objects/show_saved_object_save_modal.js b/src/ui/public/saved_objects/show_saved_object_save_modal.js index f53f819c697f..5cb1681c0828 100644 --- a/src/ui/public/saved_objects/show_saved_object_save_modal.js +++ b/src/ui/public/saved_objects/show_saved_object_save_modal.js @@ -19,6 +19,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; export function showSaveModal(saveModal) { const container = document.createElement('div'); @@ -44,5 +45,6 @@ export function showSaveModal(saveModal) { onClose: closeModal } ); - ReactDOM.render(element, container); + + ReactDOM.render({element}, container); } diff --git a/tsconfig.json b/tsconfig.json index 0ca1bef98f43..6eedfa53b261 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,9 @@ "paths": { "ui/*": [ "src/ui/public/*" + ], + "test_utils/*": [ + "src/test_utils/public/*" ] }, // Support .tsx files and transform JSX into calls to React.createElement @@ -54,4 +57,4 @@ // the tsconfig.json file for public files correctly. // "src/**/public/**/*" ] -} \ No newline at end of file +}