Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] Typescript dashboard stuff (#27167) #32483

Merged
merged 1 commit into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)];

Expand All @@ -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),
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 React from 'react';
import { Provider } from 'react-redux';
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';
// @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 {
updateViewMode,
setPanels,
updateTimeRange,
embeddableIsInitialized,
} from '../actions';
import { Provider } from 'react-redux';

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);
Expand All @@ -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);
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 React from 'react';
import {
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';

import {
EuiPanel,
EuiLoadingChart,
} from '@elastic/eui';
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;
}

class DashboardPanelUi extends React.Component {
constructor(props) {
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,
Expand All @@ -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);
Expand All @@ -68,37 +103,37 @@ class DashboardPanelUi extends React.Component {
embeddable.destroy();
}
})
.catch((error) => {
.catch((error: { message: EmbeddableErrorAction }) => {
if (this.mounted) {
embeddableError(error.message);
}
});
}
}

componentWillUnmount() {
public componentWillUnmount() {
this.props.destroy();
this.mounted = false;
if (this.embeddable) {
this.embeddable.destroy();
}
}

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,
});
Expand All @@ -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);
}
Expand All @@ -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
Expand All @@ -155,46 +187,12 @@ 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>
);
}
}

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);
Loading