mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
parent
992daf5fed
commit
7baea1d737
49 changed files with 860 additions and 261 deletions
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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’s fill it up!
|
||||
<h2
|
||||
class="kuiTitle kuiVerticalRhythm"
|
||||
i18n-id="kbn.dashboard.fillDashboardTitle"
|
||||
i18n-default-message="This dashboard is empty. Let’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’s fill it up!
|
||||
<h2
|
||||
class="kuiTitle kuiVerticalRhythm"
|
||||
i18n-id="kbn.dashboard.fillDashboardTitle"
|
||||
i18n-default-message="This dashboard is empty. Let’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>
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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)) || '';
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: () => (
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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: '[]',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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={() => {}}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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/**/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue