Typescript dashboard stuff (#27167)

* gridData id changed to i

* to.equal panel_state.ts

* clarifying return values in panel_utils

*fixed imports in dpc

consitency across id -> i for gridData, improvment made in dashboard_panel.tsx

changed DashboardPanelUiProps onPanelFocused and onPanelBlurred (panel: PanelState) to (panel: string) resolved Problems in TS

* Resolved ts testing type errors with DashboardPanelUiProps in dashboard_panel.tsx

Updated snapshot for dashboard_panel.test.tsx

* Code review from Emma with changes partial PanelState and defining size_x and size_y to numbers

adding comment to clarify panel.panelIndex.toString()

* test commit to take in Emmas feedback and show the panel_utils and types.ts modified to show panelIndex string | number

reverting panel_utils and types.ts changes with panelIndex, keeping other changes from Emmas suggestions

* Fixed panel_utils and panel_utils test to create updatedPanel object instead of mutation

Revert mutation in panel utils

removed ts-ignore statements in __tests__/panel_state.ts
This commit is contained in:
Rachel Shen 2019-03-04 11:47:33 -08:00 committed by GitHub
parent 8b0e3a7de1
commit 3702ac1223
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 326 additions and 199 deletions

View file

@ -18,19 +18,29 @@
*/
import expect from 'expect.js';
import { PanelState } from '../../selectors';
import { createPanelState } from '../panel_state';
function createPanelWithDimensions(x, y, w, h) {
function createPanelWithDimensions(x: number, y: number, w: number, h: number): PanelState {
return {
id: 'foo',
version: '6.3.0',
type: 'bar',
panelIndex: 'test',
title: 'test title',
gridData: {
x, y, w, h
}
x,
y,
w,
h,
i: 'an id',
},
embeddableConfig: {},
};
}
describe('Panel state', function () {
it('finds a spot on the right', function () {
describe('Panel state', () => {
it('finds a spot on the right', () => {
// Default setup after a single panel, of default size, is on the grid
const panels = [createPanelWithDimensions(0, 0, 24, 30)];
@ -39,7 +49,7 @@ describe('Panel state', function () {
expect(panel.gridData.y).to.equal(0);
});
it('finds a spot on the right when the panel is taller than any other panel on the grid', function () {
it('finds a spot on the right when the panel is taller than any other panel on the grid', () => {
// Should be a little empty spot on the right.
const panels = [
createPanelWithDimensions(0, 0, 24, 45),
@ -51,7 +61,7 @@ describe('Panel state', function () {
expect(panel.gridData.y).to.equal(30);
});
it('finds an empty spot in the middle of the grid', function () {
it('finds an empty spot in the middle of the grid', () => {
const panels = [
createPanelWithDimensions(0, 0, 48, 5),
createPanelWithDimensions(0, 5, 4, 30),

View file

@ -17,38 +17,28 @@
* under the License.
*/
import React from 'react';
// TODO: remove this when EUI supports types for this.
// @ts-ignore: implicit any for JS file
import { takeMountedSnapshot } from '@elastic/eui/lib/test';
import _ from 'lodash';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { DashboardPanel } from './dashboard_panel';
import { DashboardViewMode } from '../dashboard_view_mode';
import { PanelError } from '../panel/panel_error';
import { store } from '../../store';
import { getEmbeddableFactoryMock } from '../__tests__/get_embeddable_factories_mock';
import {
updateViewMode,
setPanels,
updateTimeRange,
embeddableIsInitialized,
} from '../actions';
import React from 'react';
import { Provider } from 'react-redux';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { store } from '../../store';
// @ts-ignore: implicit any for JS file
import { getEmbeddableFactoryMock } from '../__tests__/get_embeddable_factories_mock';
import { embeddableIsInitialized, setPanels, updateTimeRange, updateViewMode } from '../actions';
import { DashboardViewMode } from '../dashboard_view_mode';
import { DashboardPanel, DashboardPanelUiProps } from './dashboard_panel';
import {
takeMountedSnapshot,
} from '@elastic/eui/lib/test';
import { PanelError } from './panel_error';
function getProps(props = {}) {
function getProps(props = {}): DashboardPanelUiProps {
const defaultTestProps = {
panel: { panelIndex: 'foo1' },
viewOnlyMode: false,
destroy: () => {},
initialized: true,
lastReloadRequestTime: 0,
embeddableIsInitialized: () => {},
embeddableIsInitializing: () => {},
embeddableStateChanged: () => {},
embeddableError: () => {},
embeddableFactory: getEmbeddableFactoryMock(),
};
return _.defaultsDeep(props, defaultTestProps);
@ -57,22 +47,47 @@ function getProps(props = {}) {
beforeAll(() => {
store.dispatch(updateTimeRange({ to: 'now', from: 'now-15m' }));
store.dispatch(updateViewMode(DashboardViewMode.EDIT));
store.dispatch(setPanels({ 'foo1': { panelIndex: 'foo1' } }));
store.dispatch(
setPanels({
foo1: {
panelIndex: 'foo1',
id: 'hi',
version: '123',
type: 'viz',
embeddableConfig: {},
gridData: {
x: 1,
y: 1,
w: 1,
h: 1,
i: 'hi',
},
},
})
);
const metadata = { title: 'my embeddable title', editUrl: 'editme' };
store.dispatch(embeddableIsInitialized({ metadata, panelId: 'foo1' }));
});
test('DashboardPanel matches snapshot', () => {
const component = mountWithIntl(<Provider store={store}><DashboardPanel.WrappedComponent {...getProps()} /></Provider>);
const component = mountWithIntl(
<Provider store={store}>
<DashboardPanel.WrappedComponent {...getProps()} />
</Provider>
);
expect(takeMountedSnapshot(component)).toMatchSnapshot();
});
test('renders an error when error prop is passed', () => {
const props = getProps({
error: 'Simulated error'
error: 'Simulated error',
});
const component = mountWithIntl(<Provider store={store}><DashboardPanel.WrappedComponent {...props} /></Provider>);
const component = mountWithIntl(
<Provider store={store}>
<DashboardPanel.WrappedComponent {...props} />
</Provider>
);
const panelError = component.find(PanelError);
expect(panelError.length).toBe(1);
});

View file

@ -17,34 +17,68 @@
* under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { injectI18n } from '@kbn/i18n/react';
import { EuiLoadingChart, EuiPanel } from '@elastic/eui';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import classNames from 'classnames';
import _ from 'lodash';
import { PanelHeader } from './panel_header';
import { PanelError } from './panel_error';
import React from 'react';
import {
EuiPanel,
EuiLoadingChart,
} from '@elastic/eui';
ContainerState,
Embeddable,
EmbeddableFactory,
EmbeddableMetadata,
EmbeddableState,
} from 'ui/embeddable';
import { EmbeddableErrorAction } from '../actions';
import { PanelId, PanelState } from '../selectors';
import { PanelError } from './panel_error';
import { PanelHeader } from './panel_header';
class DashboardPanelUi extends React.Component {
constructor(props) {
export interface DashboardPanelProps {
viewOnlyMode: boolean;
onPanelFocused?: (panelIndex: PanelId) => {};
onPanelBlurred?: (panelIndex: PanelId) => {};
error?: string | object;
destroy: () => void;
containerState: ContainerState;
embeddableFactory: EmbeddableFactory;
lastReloadRequestTime?: number;
embeddableStateChanged: (embeddableStateChanges: EmbeddableState) => void;
embeddableIsInitialized: (embeddableIsInitializing: EmbeddableMetadata) => void;
embeddableError: (errorMessage: EmbeddableErrorAction) => void;
embeddableIsInitializing: () => void;
initialized: boolean;
panel: PanelState;
className?: string;
}
export interface DashboardPanelUiProps extends DashboardPanelProps {
intl: InjectedIntl;
}
interface State {
error: string | null;
}
class DashboardPanelUi extends React.Component<DashboardPanelUiProps, State> {
[panel: string]: any;
public mounted: boolean;
public embeddable!: Embeddable;
constructor(props: DashboardPanelUiProps) {
super(props);
this.state = {
error: props.embeddableFactory ? null : props.intl.formatMessage({
id: 'kbn.dashboard.panel.noEmbeddableFactoryErrorMessage',
defaultMessage: '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;
}
async componentDidMount() {
public async componentDidMount() {
this.mounted = true;
const {
initialized,
@ -58,8 +92,9 @@ class DashboardPanelUi extends React.Component {
if (!initialized) {
embeddableIsInitializing();
embeddableFactory.create(panel, embeddableStateChanged)
.then((embeddable) => {
embeddableFactory
.create(panel, embeddableStateChanged)
.then((embeddable: Embeddable) => {
if (this.mounted) {
this.embeddable = embeddable;
embeddableIsInitialized(embeddable.metadata);
@ -68,7 +103,7 @@ class DashboardPanelUi extends React.Component {
embeddable.destroy();
}
})
.catch((error) => {
.catch((error: { message: EmbeddableErrorAction }) => {
if (this.mounted) {
embeddableError(error.message);
}
@ -76,7 +111,7 @@ class DashboardPanelUi extends React.Component {
}
}
componentWillUnmount() {
public componentWillUnmount() {
this.props.destroy();
this.mounted = false;
if (this.embeddable) {
@ -84,21 +119,21 @@ class DashboardPanelUi extends React.Component {
}
}
onFocus = () => {
public onFocus = () => {
const { onPanelFocused, panel } = this.props;
if (onPanelFocused) {
onPanelFocused(panel.panelIndex);
}
};
onBlur = () => {
public onBlur = () => {
const { onPanelBlurred, panel } = this.props;
if (onPanelBlurred) {
onPanelBlurred(panel.panelIndex);
}
};
renderEmbeddableViewport() {
public renderEmbeddableViewport() {
const classes = classNames('panel-content', {
'panel-content-isLoading': !this.props.initialized,
});
@ -107,16 +142,14 @@ class DashboardPanelUi extends React.Component {
<div
id="embeddedPanel"
className={classes}
ref={panelElement => this.panelElement = panelElement}
ref={panelElement => (this.panelElement = panelElement)}
>
{!this.props.initialized && (
<EuiLoadingChart size="l" mono/>
)}
{!this.props.initialized && <EuiLoadingChart size="l" mono />}
</div>
);
}
shouldComponentUpdate(nextProps) {
public shouldComponentUpdate(nextProps: DashboardPanelUiProps) {
if (this.embeddable && !_.isEqual(nextProps.containerState, this.props.containerState)) {
this.embeddable.onContainerStateChanged(nextProps.containerState);
}
@ -125,27 +158,26 @@ class DashboardPanelUi extends React.Component {
this.embeddable.reload();
}
return nextProps.error !== this.props.error ||
nextProps.initialized !== this.props.initialized;
return nextProps.error !== this.props.error || nextProps.initialized !== this.props.initialized;
}
renderEmbeddedError() {
public renderEmbeddedError() {
return <PanelError error={this.props.error} />;
}
renderContent() {
public renderContent() {
const { error } = this.props;
if (error) {
return this.renderEmbeddedError(error);
return this.renderEmbeddedError();
} else {
return this.renderEmbeddableViewport();
}
}
render() {
public render() {
const { viewOnlyMode, panel } = this.props;
const classes = classNames('dshPanel', this.props.className, {
'dshPanel--editing': !viewOnlyMode
'dshPanel--editing': !viewOnlyMode,
});
return (
<EuiPanel
@ -155,10 +187,7 @@ class DashboardPanelUi extends React.Component {
onBlur={this.onBlur}
paddingSize="none"
>
<PanelHeader
panelId={panel.panelIndex}
embeddable={this.embeddable}
/>
<PanelHeader panelId={panel.panelIndex} embeddable={this.embeddable} />
{this.renderContent()}
</EuiPanel>
@ -166,35 +195,4 @@ class DashboardPanelUi extends React.Component {
}
}
DashboardPanelUi.propTypes = {
viewOnlyMode: PropTypes.bool.isRequired,
onPanelFocused: PropTypes.func,
onPanelBlurred: PropTypes.func,
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]),
destroy: PropTypes.func.isRequired,
containerState: PropTypes.shape({
timeRange: PropTypes.object,
refreshConfig: PropTypes.object,
filters: PropTypes.array,
query: PropTypes.object,
embeddableCustomization: PropTypes.object,
hidePanelTitles: PropTypes.bool.isRequired,
}),
embeddableFactory: PropTypes.shape({
create: PropTypes.func,
}).isRequired,
lastReloadRequestTime: PropTypes.number.isRequired,
embeddableStateChanged: PropTypes.func.isRequired,
embeddableIsInitialized: PropTypes.func.isRequired,
embeddableError: PropTypes.func.isRequired,
embeddableIsInitializing: PropTypes.func.isRequired,
initialized: PropTypes.bool.isRequired,
panel: PropTypes.shape({
panelIndex: PropTypes.string,
}).isRequired,
};
export const DashboardPanel = injectI18n(DashboardPanelUi);

View file

@ -17,21 +17,22 @@
* under the License.
*/
import React from 'react';
import _ from 'lodash';
import React from 'react';
import { Provider } from 'react-redux';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { DashboardPanelContainer } from './dashboard_panel_container';
import { store } from '../../store';
// @ts-ignore: implicit for any JS file
import { getEmbeddableFactoryMock } from '../__tests__/get_embeddable_factories_mock';
import { setPanels, updateTimeRange, updateViewMode } from '../actions';
import { DashboardViewMode } from '../dashboard_view_mode';
import { PanelError } from '../panel/panel_error';
import { store } from '../../store';
import {
updateViewMode,
setPanels, updateTimeRange,
} from '../actions';
import { Provider } from 'react-redux';
import { getEmbeddableFactoryMock } from '../__tests__/get_embeddable_factories_mock';
DashboardPanelContainer,
DashboardPanelContainerOwnProps,
} from './dashboard_panel_container';
function getProps(props = {}) {
function getProps(props = {}): DashboardPanelContainerOwnProps {
const defaultTestProps = {
panelId: 'foo1',
embeddableFactory: getEmbeddableFactoryMock(),
@ -42,17 +43,38 @@ function getProps(props = {}) {
beforeAll(() => {
store.dispatch(updateViewMode(DashboardViewMode.EDIT));
store.dispatch(updateTimeRange({ to: 'now', from: 'now-15m' }));
store.dispatch(setPanels({ 'foo1': { panelIndex: 'foo1' } }));
store.dispatch(
setPanels({
foo1: {
panelIndex: 'foo1',
id: 'hi',
version: '123',
type: 'viz',
embeddableConfig: {},
gridData: {
x: 1,
y: 1,
w: 1,
h: 1,
i: 'hi',
},
},
})
);
});
test('renders an error when embeddableFactory.create throws an error', (done) => {
test('renders an error when embeddableFactory.create throws an error', done => {
const props = getProps();
props.embeddableFactory.create = () => {
return new Promise(() => {
throw new Error('simulated error');
});
};
const component = mountWithIntl(<Provider store={store}><DashboardPanelContainer {...props} /></Provider>);
const component = mountWithIntl(
<Provider store={store}>
<DashboardPanelContainer {...props} />
</Provider>
);
setTimeout(() => {
component.update();
const panelError = component.find(PanelError);
@ -60,4 +82,3 @@ test('renders an error when embeddableFactory.create throws an error', (done) =>
done();
}, 0);
});

View file

@ -17,26 +17,66 @@
* under the License.
*/
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';
import { connect } from 'react-redux';
import { Action } from 'redux-actions';
import { ThunkDispatch } from 'redux-thunk';
import {
deletePanel, embeddableError, embeddableIsInitialized, embeddableIsInitializing, embeddableStateChanged,
ContainerState,
EmbeddableFactory,
EmbeddableMetadata,
EmbeddableState,
} from 'ui/embeddable';
import { CoreKibanaState } from '../../selectors';
import {
deletePanel,
embeddableError,
EmbeddableErrorAction,
embeddableIsInitialized,
embeddableIsInitializing,
embeddableStateChanged,
} from '../actions';
import { DashboardViewMode } from '../dashboard_view_mode';
import {
getContainerState,
getEmbeddable,
getFullScreenMode,
getViewMode,
getEmbeddableError,
getPanelType, getContainerState, getPanel, getEmbeddableInitialized,
getEmbeddableInitialized,
getFullScreenMode,
getPanel,
getPanelType,
getViewMode,
PanelId,
PanelState,
} from '../selectors';
import { DashboardPanel } from './dashboard_panel';
const mapStateToProps = ({ dashboard }, { embeddableFactory, panelId }) => {
export interface DashboardPanelContainerOwnProps {
panelId: PanelId;
embeddableFactory: EmbeddableFactory;
}
interface DashboardPanelContainerStateProps {
error?: string | object;
viewOnlyMode: boolean;
containerState: ContainerState;
initialized: boolean;
panel: PanelState;
lastReloadRequestTime?: number;
}
export interface DashboardPanelContainerDispatchProps {
destroy: () => void;
embeddableIsInitializing: () => void;
embeddableIsInitialized: (metadata: EmbeddableMetadata) => void;
embeddableStateChanged: (embeddableState: EmbeddableState) => void;
embeddableError: (errorMessage: EmbeddableErrorAction) => void;
}
const mapStateToProps = (
{ dashboard }: CoreKibanaState,
{ embeddableFactory, panelId }: DashboardPanelContainerOwnProps
) => {
const embeddable = getEmbeddable(dashboard, panelId);
let error = null;
if (!embeddableFactory) {
@ -60,35 +100,26 @@ const mapStateToProps = ({ dashboard }, { embeddableFactory, panelId }) => {
};
};
const mapDispatchToProps = (dispatch, { panelId }) => ({
destroy: () => (
dispatch(deletePanel(panelId))
),
embeddableIsInitializing: () => (
dispatch(embeddableIsInitializing(panelId))
),
embeddableIsInitialized: (metadata) => (
dispatch(embeddableIsInitialized({ panelId, metadata }))
),
embeddableStateChanged: (embeddableState) => (
dispatch(embeddableStateChanged({ panelId, embeddableState }))
),
embeddableError: (errorMessage) => (
dispatch(embeddableError({ panelId, error: errorMessage }))
)
const mapDispatchToProps = (
dispatch: ThunkDispatch<CoreKibanaState, {}, Action<any>>,
{ panelId }: DashboardPanelContainerOwnProps
): DashboardPanelContainerDispatchProps => ({
destroy: () => dispatch(deletePanel(panelId)),
embeddableIsInitializing: () => dispatch(embeddableIsInitializing(panelId)),
embeddableIsInitialized: (metadata: EmbeddableMetadata) =>
dispatch(embeddableIsInitialized({ panelId, metadata })),
embeddableStateChanged: (embeddableState: EmbeddableState) =>
dispatch(embeddableStateChanged({ panelId, embeddableState })),
embeddableError: (errorMessage: EmbeddableErrorAction) =>
dispatch(embeddableError({ panelId, error: errorMessage })),
});
export const DashboardPanelContainer = connect(
export const DashboardPanelContainer = connect<
DashboardPanelContainerStateProps,
DashboardPanelContainerDispatchProps,
DashboardPanelContainerOwnProps,
CoreKibanaState
>(
mapStateToProps,
mapDispatchToProps
)(DashboardPanel);
DashboardPanelContainer.propTypes = {
panelId: PropTypes.string.isRequired,
/**
* @type {EmbeddableFactory}
*/
embeddableFactory: PropTypes.shape({
create: PropTypes.func.isRequired,
}).isRequired,
};

View file

@ -17,13 +17,17 @@
* under the License.
*/
jest.mock('ui/chrome',
jest.mock(
'ui/chrome',
() => ({
getKibanaVersion: () => '6.3.0',
}), { virtual: true });
}),
{ virtual: true }
);
import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants';
import { PanelUtils } from './panel_utils';
import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from '../dashboard_constants';
import { createPanelState } from './panel_state';
test('parseVersion', () => {
const { major, minor } = PanelUtils.parseVersion('6.2.0');
@ -33,8 +37,24 @@ test('parseVersion', () => {
test('convertPanelDataPre_6_1 gives supplies width and height when missing', () => {
const panelData = [
{ col: 3, id: 'foo1', row: 1, type: 'visualization', panelIndex: 1 },
{ col: 3, id: 'foo2', row: 1, size_x: 3, size_y: 2, type: 'visualization', panelIndex: 2 }
{
col: 3,
id: 'foo1',
row: 1,
type: 'visualization',
panelIndex: 1,
gridData: createPanelState,
},
{
col: 3,
id: 'foo2',
row: 1,
size_x: 3,
size_y: 2,
type: 'visualization',
panelIndex: 2,
gridData: createPanelState,
},
];
panelData.forEach(oldPanel => PanelUtils.convertPanelDataPre_6_1(oldPanel));
expect(panelData[0].gridData.w).toBe(DEFAULT_PANEL_WIDTH);
@ -54,9 +74,9 @@ test('convertPanelDataPre_6_3 scales panel dimensions', () => {
x: 2,
y: 5,
},
version: '6.2.0'
version: '6.2.0',
};
const updatedPanel = PanelUtils.convertPanelDataPre_6_3(oldPanel);
const updatedPanel = PanelUtils.convertPanelDataPre_6_3(oldPanel, false);
expect(updatedPanel.gridData.w).toBe(28);
expect(updatedPanel.gridData.h).toBe(15);
expect(updatedPanel.gridData.x).toBe(8);
@ -72,7 +92,7 @@ test('convertPanelDataPre_6_3 with margins scales panel dimensions', () => {
x: 2,
y: 5,
},
version: '6.2.0'
version: '6.2.0',
};
const updatedPanel = PanelUtils.convertPanelDataPre_6_3(oldPanel, true);
expect(updatedPanel.gridData.w).toBe(28);

View file

@ -17,25 +17,41 @@
* under the License.
*/
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from '../dashboard_constants';
import _ from 'lodash';
import chrome from 'ui/chrome';
import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants';
import { GridData, PanelState } from '../selectors';
const PANEL_HEIGHT_SCALE_FACTOR = 5;
const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4;
const PANEL_WIDTH_SCALE_FACTOR = 4;
export class PanelUtils {
export interface SemanticVersion {
major: number;
minor: number;
}
export class PanelUtils {
// 6.1 switched from gridster to react grid. React grid uses different variables for tracking layout
static convertPanelDataPre_6_1(panel) { // eslint-disable-line camelcase
public static convertPanelDataPre_6_1(panel: {
panelIndex: any; // earlier versions allowed panelIndex to be a number or a string
gridData: GridData;
col: number;
row: number;
size_x: number;
size_y: number;
version: string;
}): Partial<PanelState> {
['col', 'row'].forEach(key => {
if (!_.has(panel, 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 },
}));
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 },
})
);
}
});
@ -44,7 +60,7 @@ export class PanelUtils {
y: panel.row - 1,
w: panel.size_x || DEFAULT_PANEL_WIDTH,
h: panel.size_y || DEFAULT_PANEL_HEIGHT,
i: panel.panelIndex.toString()
i: panel.panelIndex.toString(),
};
panel.version = chrome.getKibanaVersion();
panel.panelIndex = panel.panelIndex.toString();
@ -60,18 +76,32 @@ export class PanelUtils {
// 1) decrease column height from 100 to 20.
// 2) increase rows from 12 to 48
// Need to scale pre 6.3 panels so they maintain the same layout
static convertPanelDataPre_6_3(panel, useMargins) { // eslint-disable-line camelcase
public static convertPanelDataPre_6_3(
panel: {
gridData: { w: number; x: number; h: number; y: number };
version: string;
},
useMargins: boolean
) {
['w', 'x', 'h', 'y'].forEach(key => {
if (!_.has(panel.gridData, 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 },
}));
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 },
}
)
);
}
});
// see https://github.com/elastic/kibana/issues/20635 on why the scale factor changes when margins are being used
const heightScaleFactor = useMargins ? PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS : PANEL_HEIGHT_SCALE_FACTOR;
const heightScaleFactor = useMargins
? PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS
: PANEL_HEIGHT_SCALE_FACTOR;
panel.gridData.w = panel.gridData.w * PANEL_WIDTH_SCALE_FACTOR;
panel.gridData.x = panel.gridData.x * PANEL_WIDTH_SCALE_FACTOR;
@ -82,38 +112,40 @@ export class PanelUtils {
return panel;
}
static parseVersion(version = '6.0.0') {
public static parseVersion(version = '6.0.0'): SemanticVersion {
const versionSplit = version.split('.');
if (versionSplit.length < 3) {
throw new Error(i18n.translate('kbn.dashboard.panel.invalidVersionErrorMessage', {
defaultMessage: 'Invalid version, {version}, expected {semver}',
values: {
version,
semver: '<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),
minor: parseInt(versionSplit[1], 10)
minor: parseInt(versionSplit[1], 10),
};
}
static initPanelIndexes(panels) {
public static initPanelIndexes(panels: PanelState[]): void {
// find the largest panelIndex in all the panels
let maxIndex = this.getMaxPanelIndex(panels);
// ensure that all panels have a panelIndex
panels.forEach(function (panel) {
panels.forEach(panel => {
if (!panel.panelIndex) {
panel.panelIndex = maxIndex++;
panel.panelIndex = (maxIndex++).toString();
}
});
}
static getMaxPanelIndex(panels) {
let maxId = panels.reduce(function (id, panel) {
return Math.max(id, panel.panelIndex || id);
public static getMaxPanelIndex(panels: PanelState[]): number {
let maxId = panels.reduce((id, panel) => {
return Math.max(id, Number(panel.panelIndex || id));
}, 0);
return ++maxId;
}

View file

@ -43,11 +43,11 @@ describe('embeddableIsInitializing', () => {
test('clears the error', () => {
store.dispatch(embeddableIsInitializing('foo1'));
const initialized = getEmbeddableInitialized(store.getState(), 'foo1');
expect(initialized).toBe(false);
expect(initialized).toEqual(false);
});
test('and clears the error', () => {
const error = getEmbeddableError(store.getState(), 'foo1');
expect(error).toBe(undefined);
expect(error).toEqual(undefined);
});
});

View file

@ -48,7 +48,7 @@ export interface PanelState {
readonly id: SavedObjectId;
readonly version: string;
readonly type: string;
readonly panelIndex: PanelId;
panelIndex: PanelId;
readonly embeddableConfig: any;
readonly gridData: GridData;
readonly title?: string;