Feature/dashboard translations (#24328) (#25917)

add dashboard translations
This commit is contained in:
pavel06081991 2018-11-20 14:17:16 +03:00 committed by GitHub
parent aa44d56bf4
commit e96f58dcc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 860 additions and 261 deletions

View file

@ -28,7 +28,7 @@ exports[`is rendered 1`] = `
class="dshExitFullScreenButton__text"
data-test-subj="exitFullScreenModeText"
>
Exit full screen
Exit full screen
<span
class="kuiIcon fa fa-angle-left"
/>

View file

@ -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 (
<div>
<EuiScreenReaderOnly>
<p aria-live="polite">
In full screen mode, press ESC to exit.
<FormattedMessage
id="kbn.dashboard.exitFullScreenButton.fullScreenModeDescription"
defaultMessage="In full screen mode, press ESC to exit."
/>
</p>
</EuiScreenReaderOnly>
<div
@ -61,13 +67,20 @@ export class ExitFullScreenButton extends PureComponent {
>
<KuiButton
type="hollow"
aria-label="Exit full screen mode"
aria-label={intl.formatMessage({
id: 'kbn.dashboard.exitFullScreenButton.exitFullScreenModeButtonAreaLabel',
defaultMessage: 'Exit full screen mode',
})}
className="dshExitFullScreenButton__mode"
onClick={this.props.onExitFullScreenMode}
>
<span className="dshExitFullScreenButton__logo" data-test-subj="exitFullScreenModeLogo"/>
<span className="dshExitFullScreenButton__text" data-test-subj="exitFullScreenModeText">
Exit full screen <span className="kuiIcon fa fa-angle-left"/>
<FormattedMessage
id="kbn.dashboard.exitFullScreenButton.exitFullScreenModeButtonLabel"
defaultMessage="Exit full screen"
/>
<span className="kuiIcon fa fa-angle-left"/>
</span>
</KuiButton>
</div>
@ -76,6 +89,8 @@ export class ExitFullScreenButton extends PureComponent {
}
}
ExitFullScreenButton.propTypes = {
ExitFullScreenButtonUi.propTypes = {
onExitFullScreenMode: PropTypes.func.isRequired,
};
export const ExitFullScreenButton = injectI18n(ExitFullScreenButtonUi);

View file

@ -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(
<ExitFullScreenButton onExitFullScreenMode={() => {}}/>
const component = renderWithIntl(
<ExitFullScreenButton.WrappedComponent onExitFullScreenMode={() => {}}/>
);
expect(component)
@ -48,8 +48,8 @@ describe('onExitFullScreenMode', () => {
test('is called when the button is pressed', () => {
const onExitHandler = sinon.stub();
const component = mount(
<ExitFullScreenButton onExitFullScreenMode={onExitHandler} />
const component = mountWithIntl(
<ExitFullScreenButton.WrappedComponent onExitFullScreenMode={onExitHandler} />
);
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(<ExitFullScreenButton onExitFullScreenMode={onExitHandler} />);
mountWithIntl(<ExitFullScreenButton.WrappedComponent onExitFullScreenMode={onExitHandler} />);
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(
<ExitFullScreenButton onExitFullScreenMode={() => {}} />
const component = mountWithIntl(
<ExitFullScreenButton.WrappedComponent onExitFullScreenMode={() => {}} />
);
component.find('button').simulate('click');
@ -84,8 +84,8 @@ describe('chrome.setVisible', () => {
});
test('is called with true the component is unmounted', () => {
const component = mount(
<ExitFullScreenButton onExitFullScreenMode={() => {}} />
const component = mountWithIntl(
<ExitFullScreenButton.WrappedComponent onExitFullScreenMode={() => {}} />
);
chrome.setVisible = sinon.stub();

View file

@ -15,7 +15,12 @@
aria-level="1"
ng-if="showPluginBreadcrumbs">
<div class="kuiLocalBreadcrumb">
<a class="kuiLocalBreadcrumb__link" href="{{landingPageUrl()}}">Dashboard</a>
<a
class="kuiLocalBreadcrumb__link"
href="{{landingPageUrl()}}"
i18n-id="kbn.dashboard.dashboardLinkLabel"
i18n-default-message="Dashboard"
></a>
</div>
<div class="kuiLocalBreadcrumb">
{{ getDashTitle() }}
@ -46,22 +51,64 @@
ng-show="getShouldShowEditHelp()"
class="dshStartScreen"
>
<h2 class="kuiTitle kuiVerticalRhythm">
This dashboard is empty. Let&rsquo;s fill it up!
<h2
class="kuiTitle kuiVerticalRhythm"
i18n-id="kbn.dashboard.fillDashboardTitle"
i18n-default-message="This dashboard is empty. Let&rsquo;s fill it up!"
>
</h2>
<p class="kuiText kuiVerticalRhythm">
Click the <a kbn-accessible-click class="kuiButton kuiButton--primary kuiButton--small" ng-click="showAddPanel()" aria-label="Add visualization" data-test-subj="emptyDashboardAddPanelButton">Add</a> button in the menu bar above to add a visualization to the dashboard. <br/>If you haven't set up any visualizations yet, <a class="kuiLink" href="#/visualize">visit the Visualize app</a> to create your first visualization.
<p>
<span
i18n-id="kbn.dashboard.addVisualizationDescription1"
i18n-default-message="Click the "
i18n-context="Part of composite label kbn.dashboard.addVisualizationDescription1 + kbn.dashboard.addVisualizationLinkText + kbn.dashboard.addVisualizationDescription2"
></span>
<a
kbn-accessible-click
class="kuiButton kuiButton--primary kuiButton--small"
ng-click="showAddPanel()"
aria-label="{{::'kbn.dashboard.addVisualizationLinkAriaLabel' | i18n: { defaultMessage: 'Add visualization' } }}"
data-test-subj="emptyDashboardAddPanelButton"
i18n-id="kbn.dashboard.addVisualizationLinkText"
i18n-default-message="Add"
></a>
<span
i18n-id="kbn.dashboard.addVisualizationDescription2"
i18n-default-message=" button in the menu bar above to add a visualization to the dashboard. {br}If you haven't set up any visualizations yet, {visitVisualizeAppLink} to create your first visualization."
i18n-values="{
br: '<br/>',
visitVisualizeAppLink: '<a class=\'kuiLink\' href=\'#/visualize\'>' + visitVisualizeAppLinkText + '</a>'
}"
></span>
</p>
</div>
<div ng-show="getShouldShowViewHelp()" class="dshStartScreen">
<h2 class="kuiTitle kuiVerticalRhythm">
This dashboard is empty. Let&rsquo;s fill it up!
<h2
class="kuiTitle kuiVerticalRhythm"
i18n-id="kbn.dashboard.fillDashboardTitle"
i18n-default-message="This dashboard is empty. Let&rsquo;s fill it up!"
>
</h2>
<p class="kuiText kuiVerticalRhythm">
Click the <a kbn-accessible-click class="kuiButton kuiButton--primary kuiButton--small" ng-click="enterEditMode()">Edit</a> button in the menu bar above to start working on your new dashboard.
<span
i18n-id="kbn.dashboard.howToStartWorkingOnNewDashboardDescription1"
i18n-default-message="Click the "
i18n-context="Part of composite label kbn.dashboard.howToStartWorkingOnNewDashboardDescription1 + kbn.dashboard.howToStartWorkingOnNewDashboardEditLinkText + kbn.dashboard.howToStartWorkingOnNewDashboardDescription2"
></span>
<a
kbn-accessible-click
class="kuiButton kuiButton--primary kuiButton--small"
ng-click="enterEditMode()"
i18n-id="kbn.dashboard.howToStartWorkingOnNewDashboardEditLinkText"
i18n-default-message="Edit"
></a>
<span
i18n-id="kbn.dashboard.howToStartWorkingOnNewDashboardDescription2"
i18n-default-message=" button in the menu bar above to start working on your new dashboard."
></span>
</p>
</div>

View file

@ -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 };

View file

@ -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;

View file

@ -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;
}

View file

@ -33,7 +33,7 @@ exports[`renders DashboardGrid 1`] = `
}
}
>
<Connect(DashboardPanel)
<Connect(InjectIntl(DashboardPanelUi))
embeddableFactory={
Object {
"create": [MockFunction],
@ -53,7 +53,7 @@ exports[`renders DashboardGrid 1`] = `
}
}
>
<Connect(DashboardPanel)
<Connect(InjectIntl(DashboardPanelUi))
embeddableFactory={
Object {
"create": [MockFunction],

View file

@ -19,6 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectI18n } from '@kbn/i18n/react';
import _ from 'lodash';
import ReactGridLayout from 'react-grid-layout';
import classNames from 'classnames';
@ -106,7 +107,7 @@ function ResponsiveGrid({
const ResponsiveSizedGrid = sizeMe(config)(ResponsiveGrid);
export class DashboardGrid extends React.Component {
class DashboardGridUi extends React.Component {
constructor(props) {
super(props);
// A mapping of panelIndexes to grid items so we can set the zIndex appropriately on the last focused
@ -120,7 +121,10 @@ export class DashboardGrid extends React.Component {
} catch (error) {
isLayoutInvalid = true;
toastNotifications.addDanger({
title: 'Unable to load dashboard.',
title: props.intl.formatMessage({
id: 'kbn.dashboard.dashboardGrid.unableToLoadDashboardDangerMessage',
defaultMessage: 'Unable to load dashboard.',
}),
text: error.message,
});
window.location = `#${DashboardConstants.LANDING_PAGE_PATH}`;
@ -259,7 +263,7 @@ export class DashboardGrid extends React.Component {
}
}
DashboardGrid.propTypes = {
DashboardGridUi.propTypes = {
panels: PropTypes.object.isRequired,
getEmbeddableFactory: PropTypes.func.isRequired,
dashboardViewMode: PropTypes.oneOf([DashboardViewMode.EDIT, DashboardViewMode.VIEW]).isRequired,
@ -267,3 +271,5 @@ DashboardGrid.propTypes = {
maximizedPanelId: PropTypes.string,
useMargins: PropTypes.bool.isRequired,
};
export const DashboardGrid = injectI18n(DashboardGridUi);

View file

@ -18,7 +18,7 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import sizeMe from 'react-sizeme';
import { DashboardViewMode } from '../dashboard_view_mode';
@ -72,20 +72,20 @@ afterAll(() => {
});
test('renders DashboardGrid', () => {
const component = shallow(<DashboardGrid {...getProps()} />);
const component = shallowWithIntl(<DashboardGrid.WrappedComponent {...getProps()} />);
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(<DashboardGrid {...getProps({ panels: {} })} />);
const component = shallowWithIntl(<DashboardGrid.WrappedComponent {...getProps({ panels: {} })} />);
expect(component).toMatchSnapshot();
});
test('adjusts z-index of focused panel to be higher than siblings', () => {
const component = shallow(<DashboardGrid {...getProps()} />);
const panelElements = component.find('Connect(DashboardPanel)');
const component = shallowWithIntl(<DashboardGrid.WrappedComponent {...getProps()} />);
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');

View file

@ -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(<Provider store={store}><DashboardGridContainer {...getProps()} /></Provider>);
const grid = mountWithIntl(<Provider store={store}><DashboardGridContainer {...getProps()} /></Provider>);
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(<Provider store={store}><DashboardGridContainer {...getProps()} /></Provider>);
const grid = mountWithIntl(<Provider store={store}><DashboardGridContainer {...getProps()} /></Provider>);
const panels = store.getState().dashboard.panels;
expect(Object.keys(panels).length).toBe(16);

View file

@ -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,

View file

@ -22,7 +22,11 @@ exports[`after fetch hideWriteControls 1`] = `
color="subdued"
component="span"
>
Looks like you don't have any dashboards.
<FormattedMessage
defaultMessage="Looks like you don't have any dashboards."
id="kbn.dashboard.listing.noDashboardsItemsMessage"
values={Object {}}
/>
</EuiTextColor>
</h2>
</EuiText>
@ -64,7 +68,11 @@ exports[`after fetch initialFilter 1`] = `
textTransform="none"
>
<h1>
Dashboards
<FormattedMessage
defaultMessage="Dashboards"
id="kbn.dashboard.listing.dashboardsTitle"
values={Object {}}
/>
</h1>
</EuiTitle>
</EuiFlexItem>
@ -80,7 +88,11 @@ exports[`after fetch initialFilter 1`] = `
iconSide="left"
type="button"
>
Create new dashboard
<FormattedMessage
defaultMessage="Create new dashboard"
id="kbn.dashboard.listing.createNewDashboardButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
@ -108,7 +120,7 @@ exports[`after fetch initialFilter 1`] = `
incremental={false}
isLoading={false}
onChange={[Function]}
placeholder="Search..."
placeholder="Search"
value="my dashboard"
/>
</EuiFlexItem>
@ -157,7 +169,13 @@ exports[`after fetch initialFilter 1`] = `
]
}
loading={false}
noItemsMessage="No dashboards matched your search."
noItemsMessage={
<FormattedMessage
defaultMessage="No dashboards matched your search."
id="kbn.dashboard.listing.noMatchedDashboardsMessage"
values={Object {}}
/>
}
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
<FormattedMessage
defaultMessage="Create new dashboard"
id="kbn.dashboard.listing.createNewDashboard.createButtonLabel"
values={Object {}}
/>
</EuiButton>
}
body={
<React.Fragment>
<p>
You can combine data views from any Kibana app into one dashboard and see everything in one place.
<FormattedMessage
defaultMessage="You can combine data views from any Kibana app into one dashboard and see everything in one place."
id="kbn.dashboard.listing.createNewDashboard.combineDataViewFromKibanaAppDescription"
values={Object {}}
/>
</p>
<p>
New to Kibana?
<EuiLink
color="primary"
href="#/home/tutorial_directory/sampleData"
type="button"
>
Install some sample data
</EuiLink>
to take a test drive.
<FormattedMessage
defaultMessage="New to Kibana? {sampleDataInstallLink} to take a test drive."
id="kbn.dashboard.listing.createNewDashboard.newToKibanaDescription"
values={
Object {
"sampleDataInstallLink": <EuiLink
color="primary"
href="#/home/tutorial_directory/sampleData"
type="button"
>
<FormattedMessage
defaultMessage="Install some sample data"
id="kbn.dashboard.listing.createNewDashboard.sampleDataInstallLinkText"
values={Object {}}
/>
</EuiLink>,
}
}
/>
</p>
</React.Fragment>
}
@ -235,7 +271,11 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = `
iconType="dashboardApp"
title={
<h2>
Create your first dashboard
<FormattedMessage
defaultMessage="Create your first dashboard"
id="kbn.dashboard.listing.createNewDashboard.title"
values={Object {}}
/>
</h2>
}
/>
@ -278,7 +318,11 @@ exports[`after fetch renders table rows 1`] = `
textTransform="none"
>
<h1>
Dashboards
<FormattedMessage
defaultMessage="Dashboards"
id="kbn.dashboard.listing.dashboardsTitle"
values={Object {}}
/>
</h1>
</EuiTitle>
</EuiFlexItem>
@ -294,7 +338,11 @@ exports[`after fetch renders table rows 1`] = `
iconSide="left"
type="button"
>
Create new dashboard
<FormattedMessage
defaultMessage="Create new dashboard"
id="kbn.dashboard.listing.createNewDashboardButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
@ -322,7 +370,7 @@ exports[`after fetch renders table rows 1`] = `
incremental={false}
isLoading={false}
onChange={[Function]}
placeholder="Search..."
placeholder="Search"
value=""
/>
</EuiFlexItem>
@ -371,7 +419,13 @@ exports[`after fetch renders table rows 1`] = `
]
}
loading={false}
noItemsMessage="No dashboards matched your search."
noItemsMessage={
<FormattedMessage
defaultMessage="No dashboards matched your search."
id="kbn.dashboard.listing.noMatchedDashboardsMessage"
values={Object {}}
/>
}
onChange={[Function]}
pagination={
Object {
@ -432,7 +486,11 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
textTransform="none"
>
<h1>
Dashboards
<FormattedMessage
defaultMessage="Dashboards"
id="kbn.dashboard.listing.dashboardsTitle"
values={Object {}}
/>
</h1>
</EuiTitle>
</EuiFlexItem>
@ -448,7 +506,11 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
iconSide="left"
type="button"
>
Create new dashboard
<FormattedMessage
defaultMessage="Create new dashboard"
id="kbn.dashboard.listing.createNewDashboardButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
@ -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={
<FormattedMessage
defaultMessage="Listing limit exceeded"
id="kbn.dashboard.listing.listingLimitExceededTitle"
values={Object {}}
/>
}
>
<p>
You have
2
dashboards, but your
<strong>
listingLimit
</strong>
setting prevents the table below from displaying more than
1
. You can change this setting under
<EuiLink
color="primary"
href="#/management/kibana/settings"
type="button"
>
Advanced Settings
</EuiLink>
.
<FormattedMessage
defaultMessage="You have {totalDashboards} dashboards, but your {listingLimitText} setting prevents the table below from displaying more than {listingLimitValue}. You can change this setting under {advancedSettingsLink}."
id="kbn.dashboard.listing.listingLimitExceededDescription"
values={
Object {
"advancedSettingsLink": <EuiLink
color="primary"
href="#/management/kibana/settings"
type="button"
>
<FormattedMessage
defaultMessage="Advanced Settings"
id="kbn.dashboard.listing.listingLimitExceeded.advancedSettingsLinkText"
values={Object {}}
/>
</EuiLink>,
"listingLimitText": <strong>
listingLimit
</strong>,
"listingLimitValue": 1,
"totalDashboards": 2,
}
}
/>
</p>
</EuiCallOut>
<EuiSpacer
@ -507,7 +582,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
incremental={false}
isLoading={false}
onChange={[Function]}
placeholder="Search..."
placeholder="Search"
value=""
/>
</EuiFlexItem>
@ -556,7 +631,13 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
]
}
loading={false}
noItemsMessage="No dashboards matched your search."
noItemsMessage={
<FormattedMessage
defaultMessage="No dashboards matched your search."
id="kbn.dashboard.listing.noMatchedDashboardsMessage"
values={Object {}}
/>
}
onChange={[Function]}
pagination={
Object {

View file

@ -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: (
<FormattedMessage
id="kbn.dashboard.listing.unableToDeleteDashboardsDangerMessage"
defaultMessage="Unable to delete dashboard(s)"
/>
),
text: `${error}`,
});
}
@ -194,14 +200,34 @@ export class DashboardListing extends React.Component {
return (
<EuiOverlayMask>
<EuiConfirmModal
title="Delete selected dashboards?"
title={
<FormattedMessage
id="kbn.dashboard.listing.deleteSelectedDashboardsConfirmModal.title"
defaultMessage="Delete selected dashboards?"
/>
}
onCancel={this.closeDeleteModal}
onConfirm={this.deleteSelectedItems}
cancelButtonText="Cancel"
confirmButtonText="Delete"
cancelButtonText={
<FormattedMessage
id="kbn.dashboard.listing.deleteSelectedDashboardsConfirmModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
}
confirmButtonText={
<FormattedMessage
id="kbn.dashboard.listing.deleteSelectedDashboardsConfirmModal.confirmButtonLabel"
defaultMessage="Delete"
/>
}
defaultFocusedButton="cancel"
>
<p>{`You can't recover deleted dashboards.`}</p>
<p>
<FormattedMessage
id="kbn.dashboard.listing.deleteDashboardsConfirmModalDescription"
defaultMessage="You can't recover deleted dashboards."
/>
</p>
</EuiConfirmModal>
</EuiOverlayMask>
);
@ -212,14 +238,38 @@ export class DashboardListing extends React.Component {
return (
<React.Fragment>
<EuiCallOut
title="Listing limit exceeded"
title={
<FormattedMessage
id="kbn.dashboard.listing.listingLimitExceededTitle"
defaultMessage="Listing limit exceeded"
/>
}
color="warning"
iconType="help"
>
<p>
You have {this.state.totalDashboards} dashboards,
but your <strong>listingLimit</strong> setting prevents the table below from displaying more than {this.props.listingLimit}.
You can change this setting under <EuiLink href="#/management/kibana/settings">Advanced Settings</EuiLink>.
<FormattedMessage
id="kbn.dashboard.listing.listingLimitExceededDescription"
defaultMessage="You have {totalDashboards} dashboards, but your {listingLimitText} setting prevents
the table below from displaying more than {listingLimitValue}. You can change this setting under {advancedSettingsLink}."
values={{
totalDashboards: this.state.totalDashboards,
listingLimitValue: this.props.listingLimit,
listingLimitText: (
<strong>
listingLimit
</strong>
),
advancedSettingsLink: (
<EuiLink href="#/management/kibana/settings">
<FormattedMessage
id="kbn.dashboard.listing.listingLimitExceeded.advancedSettingsLinkText"
defaultMessage="Advanced Settings"
/>
</EuiLink>
)
}}
/>
</p>
</EuiCallOut>
<EuiSpacer size="m" />
@ -233,7 +283,12 @@ export class DashboardListing extends React.Component {
return '';
}
return 'No dashboards matched your search.';
return (
<FormattedMessage
id="kbn.dashboard.listing.noMatchedDashboardsMessage"
defaultMessage="No dashboards matched your search."
/>
);
}
renderNoItemsMessage() {
@ -243,7 +298,10 @@ export class DashboardListing extends React.Component {
<EuiText>
<h2>
<EuiTextColor color="subdued">
{`Looks like you don't have any dashboards.`}
<FormattedMessage
id="kbn.dashboard.listing.noDashboardsItemsMessage"
defaultMessage="Looks like you don't have any dashboards."
/>
</EuiTextColor>
</h2>
</EuiText>
@ -254,14 +312,37 @@ export class DashboardListing extends React.Component {
<div>
<EuiEmptyPrompt
iconType="dashboardApp"
title={<h2>Create your first dashboard</h2>}
title={
<h2>
<FormattedMessage
id="kbn.dashboard.listing.createNewDashboard.title"
defaultMessage="Create your first dashboard"
/>
</h2>
}
body={
<Fragment>
<p>
You can combine data views from any Kibana app into one dashboard and see everything in one place.
<FormattedMessage
id="kbn.dashboard.listing.createNewDashboard.combineDataViewFromKibanaAppDescription"
defaultMessage="You can combine data views from any Kibana app into one dashboard and see everything in one place."
/>
</p>
<p>
New to Kibana? <EuiLink href="#/home/tutorial_directory/sampleData">Install some sample data</EuiLink> to take a test drive.
<FormattedMessage
id="kbn.dashboard.listing.createNewDashboard.newToKibanaDescription"
defaultMessage="New to Kibana? {sampleDataInstallLink} to take a test drive."
values={{
sampleDataInstallLink: (
<EuiLink href="#/home/tutorial_directory/sampleData">
<FormattedMessage
id="kbn.dashboard.listing.createNewDashboard.sampleDataInstallLinkText"
defaultMessage="Install some sample data"
/>
</EuiLink>
),
}}
/>
</p>
</Fragment>
}
@ -272,7 +353,10 @@ export class DashboardListing extends React.Component {
iconType="plusInCircle"
data-test-subj="createDashboardPromptButton"
>
Create new dashboard
<FormattedMessage
id="kbn.dashboard.listing.createNewDashboard.createButtonLabel"
defaultMessage="Create new dashboard"
/>
</EuiButton>
}
/>
@ -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
<FormattedMessage
id="kbn.dashboard.listing.searchBar.deleteSelectedButtonLabel"
defaultMessage="Delete selected"
/>
</EuiButton>
</EuiFlexItem>
);
@ -303,8 +391,14 @@ export class DashboardListing extends React.Component {
{deleteBtn}
<EuiFlexItem grow={true}>
<EuiFieldSearch
aria-label="Filter dashboards"
placeholder="Search..."
aria-label={intl.formatMessage({
id: 'kbn.dashboard.listing.searchBar.searchFieldAriaLabel',
defaultMessage: 'Filter dashboards',
})}
placeholder={intl.formatMessage({
id: 'kbn.dashboard.listing.searchBar.searchFieldPlaceholder',
defaultMessage: 'Search…',
})}
fullWidth
value={this.state.filter}
onChange={(e) => {
@ -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) => (
<EuiLink
@ -336,14 +434,20 @@ export class DashboardListing extends React.Component {
},
{
field: 'description',
name: 'Description',
name: intl.formatMessage({
id: 'kbn.dashboard.listing.table.descriptionColumnName',
defaultMessage: 'Description',
}),
dataType: 'string',
sortable: true,
}
];
if (!this.props.hideWriteControls) {
tableColumns.push({
name: 'Actions',
name: intl.formatMessage({
id: 'kbn.dashboard.listing.table.actionsColumnName',
defaultMessage: 'Actions',
}),
actions: [
{
render: (record) => {
@ -351,7 +455,10 @@ export class DashboardListing extends React.Component {
<EuiLink
href={`#${createDashboardEditUrl(record.id)}?_a=(viewMode:edit)`}
>
Edit
<FormattedMessage
id="kbn.dashboard.listing.table.actionsColumn.editLinkText"
defaultMessage="Edit"
/>
</EuiLink>
);
}
@ -413,7 +520,10 @@ export class DashboardListing extends React.Component {
href={`#${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`}
data-test-subj="newDashboardLink"
>
Create new dashboard
<FormattedMessage
id="kbn.dashboard.listing.createNewDashboardButtonLabel"
defaultMessage="Create new dashboard"
/>
</EuiButton>
</EuiFlexItem>
);
@ -426,7 +536,10 @@ export class DashboardListing extends React.Component {
<EuiFlexItem grow={false}>
<EuiTitle size="l">
<h1>
Dashboards
<FormattedMessage
id="kbn.dashboard.listing.dashboardsTitle"
defaultMessage="Dashboards"
/>
</h1>
</EuiTitle>
</EuiFlexItem>
@ -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);

View file

@ -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(<DashboardListing
const component = shallowWithIntl(<DashboardListing.WrappedComponent
find={find.bind(null, 2)}
delete={() => {}}
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(<DashboardListing
const component = shallowWithIntl(<DashboardListing.WrappedComponent
find={find.bind(null, 2)}
delete={() => {}}
listingLimit={1000}
@ -86,7 +86,7 @@ describe('after fetch', () => {
});
test('renders table rows', async () => {
const component = shallow(<DashboardListing
const component = shallowWithIntl(<DashboardListing.WrappedComponent
find={find.bind(null, 2)}
delete={() => {}}
listingLimit={1000}
@ -102,7 +102,7 @@ describe('after fetch', () => {
});
test('renders call to action when no dashboards exist', async () => {
const component = shallow(<DashboardListing
const component = shallowWithIntl(<DashboardListing.WrappedComponent
find={find.bind(null, 0)}
delete={() => {}}
listingLimit={1}
@ -118,7 +118,7 @@ describe('after fetch', () => {
});
test('hideWriteControls', async () => {
const component = shallow(<DashboardListing
const component = shallowWithIntl(<DashboardListing.WrappedComponent
find={find.bind(null, 0)}
delete={() => {}}
listingLimit={1}
@ -134,7 +134,7 @@ describe('after fetch', () => {
});
test('renders warning when listingLimit is exceeded', async () => {
const component = shallow(<DashboardListing
const component = shallowWithIntl(<DashboardListing.WrappedComponent
find={find.bind(null, 2)}
delete={() => {}}
listingLimit={1}

View file

@ -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 && <FormattedMessage
id="kbn.dashboard.panel.embeddableViewport.loadingLabel"
defaultMessage="loading…"
/>}
</div>
);
}
@ -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);

View file

@ -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(<Provider store={store}><DashboardPanel {...getProps()} /></Provider>);
const component = mountWithIntl(<Provider store={store}><DashboardPanel.WrappedComponent {...getProps()} /></Provider>);
expect(takeMountedSnapshot(component)).toMatchSnapshot();
});
@ -71,7 +71,7 @@ test('renders an error when error prop is passed', () => {
error: 'Simulated error'
});
const component = mount(<Provider store={store}><DashboardPanel {...props} /></Provider>);
const component = mountWithIntl(<Provider store={store}><DashboardPanel.WrappedComponent {...props} /></Provider>);
const panelError = component.find(PanelError);
expect(panelError.length).toBe(1);
});

View file

@ -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)) || '';
}

View file

@ -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(<Provider store={store}><DashboardPanelContainer {...props} /></Provider>);
const component = mountWithIntl(<Provider store={store}><DashboardPanelContainer {...props} /></Provider>);
setTimeout(() => {
component.update();
const panelError = component.find(PanelError);

View file

@ -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: () => (

View file

@ -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',
},

View file

@ -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',
},
{

View file

@ -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',
},

View file

@ -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',
},

View file

@ -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 (
<div className="dshPanel__header--floater">
@ -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}
</span>
@ -67,3 +81,5 @@ export function PanelHeader({
</div>
);
}
export const PanelHeader = injectI18n(PanelHeaderUi);

View file

@ -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(
<Provider store={store}>
<PanelHeaderContainer {...getProps()} />
</Provider>

View file

@ -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 = (
<EuiButtonIcon
iconType={isViewMode ? 'boxesHorizontal' : 'gear'}
color="text"
className={isViewMode && !isPopoverOpen ? 'dshPanel_optionsMenuButton' : ''}
aria-label="Panel options"
aria-label={intl.formatMessage({
id: 'kbn.dashboard.panel.optionsMenu.panelOptionsButtonAriaLabel',
defaultMessage: 'Panel options',
})}
data-test-subj="dashboardPanelToggleMenuIcon"
onClick={toggleContextMenu}
/>
@ -70,3 +79,5 @@ export function PanelOptionsMenu({
</EuiPopover>
);
}
export const PanelOptionsMenu = injectI18n(PanelOptionsMenuUi);

View file

@ -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',
});

View file

@ -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<HTMLInputElement>) {
onUpdatePanelTitle(event.target.value);
}
@ -46,7 +52,12 @@ export function PanelOptionsMenuForm({
return (
<div className="dshPanel__optionsMenuForm" data-test-subj="dashboardPanelTitleInputMenuItem">
<EuiFormRow label="Panel title">
<EuiFormRow
label={intl.formatMessage({
id: 'kbn.dashboard.panel.optionsMenuForm.panelTitleFormRowLabel',
defaultMessage: 'Panel title',
})}
>
<EuiFieldText
id="panelTitleInput"
data-test-subj="customDashboardPanelTitleInput"
@ -55,13 +66,21 @@ export function PanelOptionsMenuForm({
value={title}
onChange={onInputChange}
onKeyDown={onKeyDown}
aria-label="Changes to this input are applied immediately. Press enter to exit."
aria-label={intl.formatMessage({
id: 'kbn.dashboard.panel.optionsMenuForm.panelTitleInputAriaLabel',
defaultMessage: 'Changes to this input are applied immediately. Press enter to exit.',
})}
/>
</EuiFormRow>
<EuiButtonEmpty data-test-subj="resetCustomDashboardPanelTitle" onClick={onReset}>
Reset title
<FormattedMessage
id="kbn.dashboard.panel.optionsMenuForm.resetCustomDashboardButtonLabel"
defaultMessage="Reset title"
/>
</EuiButtonEmpty>
</div>
);
}
export const PanelOptionsMenuForm = injectI18n(PanelOptionsMenuFormUi);

View file

@ -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 <major>.<minor>.<patch>`);
throw new Error(i18n.translate('kbn.dashboard.panel.invalidVersionErrorMessage', {
defaultMessage: 'Invalid version, {version}, expected {semver}',
values: {
version,
semver: '<major>.<minor>.<patch>',
},
}));
}
return {
major: parseInt(versionSplit[0], 10),

View file

@ -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: '[]',

View file

@ -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

View file

@ -16,7 +16,11 @@ exports[`render 1`] = `
textTransform="none"
>
<h1>
Add Panels
<FormattedMessage
defaultMessage="Add Panels"
id="kbn.dashboard.topNav.addPanelsTitle"
values={Object {}}
/>
</h1>
</EuiTitle>
<EuiTabs
@ -55,7 +59,11 @@ exports[`render 1`] = `
onClick={[Function]}
type="button"
>
Add new Visualization
<FormattedMessage
defaultMessage="Add new Visualization"
id="kbn.dashboard.topNav.addPanel.addNewVisualizationButtonLabel"
values={Object {}}
/>
</EuiButton>
}
key="visSavedObjectFinder"

View file

@ -10,7 +10,11 @@ exports[`renders DashboardCloneModal 1`] = `
>
<EuiModalHeader>
<EuiModalHeaderTitle>
Clone Dashboard
<FormattedMessage
defaultMessage="Clone Dashboard"
id="kbn.dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle"
values={Object {}}
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
@ -19,7 +23,11 @@ exports[`renders DashboardCloneModal 1`] = `
size="m"
>
<p>
Please enter a new name for your dashboard.
<FormattedMessage
defaultMessage="Please enter a new name for your dashboard."
id="kbn.dashboard.topNav.cloneModal.enterNewNameForDashboardDescription"
values={Object {}}
/>
</p>
</EuiText>
<EuiSpacer
@ -45,7 +53,11 @@ exports[`renders DashboardCloneModal 1`] = `
onClick={[Function]}
type="button"
>
Cancel
<FormattedMessage
defaultMessage="Cancel"
id="kbn.dashboard.topNav.cloneModal.cancelButtonLabel"
values={Object {}}
/>
</EuiButton>
<EuiButton
color="primary"
@ -56,7 +68,11 @@ exports[`renders DashboardCloneModal 1`] = `
onClick={[Function]}
type="button"
>
Confirm Clone
<FormattedMessage
defaultMessage="Confirm Clone"
id="kbn.dashboard.topNav.cloneModal.confirmButtonLabel"
values={Object {}}
/>
</EuiButton>
</EuiModalFooter>
</EuiModal>

View file

@ -11,7 +11,13 @@ exports[`renders DashboardSaveModal 1`] = `
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
label="Description"
label={
<FormattedMessage
defaultMessage="Description"
id="kbn.dashboard.topNav.saveModal.descriptionFormRowLabel"
values={Object {}}
/>
}
>
<EuiTextArea
compressed={true}
@ -26,8 +32,20 @@ exports[`renders DashboardSaveModal 1`] = `
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
helpText="This changes the time filter to the currently selected time each time this dashboard is loaded."
label="Store time with dashboard"
helpText={
<FormattedMessage
defaultMessage="This changes the time filter to the currently selected time each time this dashboard is loaded."
id="kbn.dashboard.topNav.saveModal.storeTimeWithDashboardFormRowHelpText"
values={Object {}}
/>
}
label={
<FormattedMessage
defaultMessage="Store time with dashboard"
id="kbn.dashboard.topNav.saveModal.storeTimeWithDashboardFormRowLabel"
values={Object {}}
/>
}
>
<EuiSwitch
checked={true}

View file

@ -19,6 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { toastNotifications } from 'ui/notify';
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
@ -35,7 +36,7 @@ import {
const VIS_TAB_ID = 'vis';
const SAVED_SEARCH_TAB_ID = 'search';
export class DashboardAddPanel extends React.Component {
class DashboardAddPanelUi extends React.Component {
constructor(props) {
super(props);
@ -44,13 +45,19 @@ export class DashboardAddPanel extends React.Component {
onClick={this.props.addNewVis}
data-test-subj="addNewSavedObjectLink"
>
Add new Visualization
<FormattedMessage
id="kbn.dashboard.topNav.addPanel.addNewVisualizationButtonLabel"
defaultMessage="Add new Visualization"
/>
</EuiButton>
);
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: (
<SavedObjectFinder
key="searchSavedObjectFinder"
onChoose={this.onAddPanel}
noItemsMessage="No matching saved searches found."
noItemsMessage={props.intl.formatMessage({
id: 'kbn.dashboard.topNav.addPanel.searchSavedObjectFinder.noMatchingVisualizationsMessage',
defaultMessage: 'No matching saved searches found.',
})}
savedObjectType="search"
/>
)
@ -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 {
<EuiFlyoutBody>
<EuiTitle size="s">
<h1>Add Panels</h1>
<h1>
<FormattedMessage
id="kbn.dashboard.topNav.addPanelsTitle"
defaultMessage="Add Panels"
/>
</h1>
</EuiTitle>
<EuiTabs>
@ -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);

View file

@ -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(<DashboardAddPanel
const component = shallowWithIntl(<DashboardAddPanel.WrappedComponent
onClose={onClose}
visTypes={{}}
addNewPanel={() => {}}

View file

@ -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 (
<Fragment>
<EuiCallOut
title={`A Dashboard with the title '${this.state.newDashboardName}' already exists.`}
title={this.props.intl.formatMessage({
id: 'kbn.dashboard.topNav.cloneModal.dashboardExistsTitle',
defaultMessage: 'A Dashboard with the title {newDashboardName} already exists.',
}, {
newDashboardName: `'${this.state.newDashboardName}'`,
})}
color="warning"
data-test-subj="titleDupicateWarnMsg"
>
<p>
Click <strong>Confirm Clone</strong> to clone the dashboard with the duplicate title.
<FormattedMessage
id="kbn.dashboard.topNav.cloneModal.dashboardExistsDescription"
defaultMessage="Click {confirmClone} to clone the dashboard with the duplicate title."
values={{
confirmClone: (
<strong>
<FormattedMessage
id="kbn.dashboard.topNav.cloneModal.confirmCloneDescription"
defaultMessage="Confirm Clone"
/>
</strong>
),
}}
/>
</p>
</EuiCallOut>
<EuiSpacer />
@ -113,14 +132,20 @@ export class DashboardCloneModal extends React.Component {
>
<EuiModalHeader>
<EuiModalHeaderTitle>
Clone Dashboard
<FormattedMessage
id="kbn.dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle"
defaultMessage="Clone Dashboard"
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiText>
<p>
Please enter a new name for your dashboard.
<FormattedMessage
id="kbn.dashboard.topNav.cloneModal.enterNewNameForDashboardDescription"
defaultMessage="Please enter a new name for your dashboard."
/>
</p>
</EuiText>
@ -143,7 +168,10 @@ export class DashboardCloneModal extends React.Component {
data-test-subj="cloneCancelButton"
onClick={this.props.onClose}
>
Cancel
<FormattedMessage
id="kbn.dashboard.topNav.cloneModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButton>
<EuiButton
@ -152,7 +180,10 @@ export class DashboardCloneModal extends React.Component {
onClick={this.cloneDashboard}
isLoading={this.state.isLoading}
>
Confirm Clone
<FormattedMessage
id="kbn.dashboard.topNav.cloneModal.confirmButtonLabel"
defaultMessage="Confirm Clone"
/>
</EuiButton>
</EuiModalFooter>
</EuiModal>
@ -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);

View file

@ -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(
<DashboardCloneModal
<DashboardCloneModal.WrappedComponent
title="dash title"
onClose={onClose}
onClone={onClone}
@ -48,7 +48,7 @@ function createComponent(creationMethod = mount) {
}
test('renders DashboardCloneModal', () => {
createComponent(shallow);
createComponent(shallowWithIntl);
expect(component).toMatchSnapshot(); // eslint-disable-line
});

View file

@ -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,
};

View file

@ -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 {
<EuiFormRow>
<EuiSwitch
label="Use dark theme"
label={this.props.intl.formatMessage({
id: 'kbn.dashboard.topNav.options.useDarkThemeSwitchLabel',
defaultMessage: 'Use dark theme',
})}
checked={this.state.darkTheme}
onChange={this.handleDarkThemeChange}
data-test-subj="dashboardDarkThemeCheckbox"
@ -69,7 +73,10 @@ export class OptionsMenu extends Component {
<EuiFormRow>
<EuiSwitch
label="Use margins between panels"
label={this.props.intl.formatMessage({
id: 'kbn.dashboard.topNav.options.useMarginsBetweenPanelsSwitchLabel',
defaultMessage: 'Use margins between panels',
})}
checked={this.state.useMargins}
onChange={this.handleUseMarginsChange}
data-test-subj="dashboardMarginsCheckbox"
@ -78,7 +85,10 @@ export class OptionsMenu extends Component {
<EuiFormRow>
<EuiSwitch
label="Hide all panel titles"
label={this.props.intl.formatMessage({
id: 'kbn.dashboard.topNav.options.hideAllPanelTitlesSwitchLabel',
defaultMessage: 'Hide all panel titles',
})}
checked={this.state.hidePanelTitles}
onChange={this.handleHidePanelTitlesChange}
data-test-subj="dashboardPanelTitlesCheckbox"
@ -90,7 +100,7 @@ export class OptionsMenu extends Component {
}
}
OptionsMenu.propTypes = {
OptionsMenuUi.propTypes = {
darkTheme: PropTypes.bool.isRequired,
onDarkThemeChange: PropTypes.func.isRequired,
useMargins: PropTypes.bool.isRequired,
@ -98,3 +108,5 @@ OptionsMenu.propTypes = {
hidePanelTitles: PropTypes.bool.isRequired,
onHidePanelTitlesChange: PropTypes.func.isRequired,
};
export const OptionsMenu = injectI18n(OptionsMenuUi);

View file

@ -19,6 +19,7 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
import {
@ -27,7 +28,7 @@ import {
EuiSwitch,
} from '@elastic/eui';
export class DashboardSaveModal extends React.Component {
class DashboardSaveModalUi extends React.Component {
constructor(props) {
super(props);
@ -64,7 +65,10 @@ export class DashboardSaveModal extends React.Component {
return (
<Fragment>
<EuiFormRow
label="Description"
label={<FormattedMessage
id="kbn.dashboard.topNav.saveModal.descriptionFormRowLabel"
defaultMessage="Description"
/>}
>
<EuiTextArea
data-test-subj="dashboardDescription"
@ -75,8 +79,14 @@ export class DashboardSaveModal extends React.Component {
</EuiFormRow>
<EuiFormRow
label="Store time with dashboard"
helpText="This changes the time filter to the currently selected time each time this dashboard is loaded."
label={<FormattedMessage
id="kbn.dashboard.topNav.saveModal.storeTimeWithDashboardFormRowLabel"
defaultMessage="Store time with dashboard"
/>}
helpText={<FormattedMessage
id="kbn.dashboard.topNav.saveModal.storeTimeWithDashboardFormRowHelpText"
defaultMessage="This changes the time filter to the currently selected time each time this dashboard is loaded."
/>}
>
<EuiSwitch
data-test-subj="storeTimeWithDashboard"
@ -102,7 +112,7 @@ export class DashboardSaveModal extends React.Component {
}
}
DashboardSaveModal.propTypes = {
DashboardSaveModalUi.propTypes = {
onSave: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
@ -110,3 +120,5 @@ DashboardSaveModal.propTypes = {
timeRestore: PropTypes.bool.isRequired,
showCopyOnSave: PropTypes.bool.isRequired,
};
export const DashboardSaveModal = injectI18n(DashboardSaveModalUi);

View file

@ -18,14 +18,14 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import {
DashboardSaveModal,
} from './save_modal';
test('renders DashboardSaveModal', () => {
const component = shallow(<DashboardSaveModal
const component = shallowWithIntl(<DashboardSaveModal.WrappedComponent
onSave={() => {}}
onClose={() => {}}
title="dash title"

View file

@ -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 = (
<DashboardAddPanel
onClose={onClose}
visTypes={visTypes}
addNewPanel={addNewPanel}
addNewVis={addNewVisWithCleanup}
/>
<I18nProvider>
<DashboardAddPanel
onClose={onClose}
visTypes={visTypes}
addNewPanel={addNewPanel}
addNewVis={addNewVisWithCleanup}
/>
</I18nProvider>
);
ReactDOM.render(element, container);
}

View file

@ -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 = (
<DashboardCloneModal onClone={onCloneConfirmed} onClose={closeModal} title={title + ' Copy'} />
<I18nProvider>
<DashboardCloneModal
onClone={onCloneConfirmed}
onClose={closeModal}
title={i18n.translate('kbn.dashboard.topNav.showCloneModal.dashboardCopyTitle', {
defaultMessage: '{title} Copy',
values: { title },
})}
/>
</I18nProvider>
);
ReactDOM.render(element, container);
}

View file

@ -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 = (
<EuiWrappingPopover
className="navbar__popover"
id="popover"
button={anchorElement}
isOpen={true}
closePopover={onClose}
>
<OptionsMenu
darkTheme={darkTheme}
onDarkThemeChange={onDarkThemeChange}
useMargins={useMargins}
onUseMarginsChange={onUseMarginsChange}
hidePanelTitles={hidePanelTitles}
onHidePanelTitlesChange={onHidePanelTitlesChange}
/>
</EuiWrappingPopover>
<I18nProvider>
<EuiWrappingPopover
className="navbar__popover"
id="popover"
button={anchorElement}
isOpen={true}
closePopover={onClose}
>
<OptionsMenu
darkTheme={darkTheme}
onDarkThemeChange={onDarkThemeChange}
useMargins={useMargins}
onUseMarginsChange={onUseMarginsChange}
hidePanelTitles={hidePanelTitles}
onHidePanelTitlesChange={onHidePanelTitlesChange}
/>
</EuiWrappingPopover>
</I18nProvider>
);
ReactDOM.render(element, container);
}

View file

@ -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 (
<Provider store={store}>
<DashboardViewportContainer {...props} />
<I18nProvider>
<DashboardViewportContainer {...props} />
</I18nProvider>
</Provider>
);
}

View file

@ -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(<I18nProvider>{element}</I18nProvider>, container);
}

View file

@ -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/**/*"
]
}
}