Skip to content

Commit

Permalink
Typescript dashboard stuff (#27167)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rshen91 authored Mar 4, 2019
1 parent 8b0e3a7 commit 3702ac1
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 195 deletions.
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

0 comments on commit 3702ac1

Please sign in to comment.