[Canvas] Remove recompose and convert to Typescript expression component (#86969)

* Remove recompose from expression component

* Fix type check

* Fix expression not updating

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Corey Robertson 2021-01-07 13:27:31 -05:00 committed by GitHub
parent 1b6f737546
commit 19687765b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 147 additions and 120 deletions

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { FC, MutableRefObject } from 'react';
import PropTypes from 'prop-types';
import {
EuiPanel,
@ -16,19 +16,26 @@ import {
EuiLink,
EuiPortal,
} from '@elastic/eui';
// @ts-expect-error
import { Shortcuts } from 'react-shortcuts';
import { ComponentStrings } from '../../../i18n';
import { ExpressionInput } from '../expression_input';
import { ToolTipShortcut } from '../tool_tip_shortcut';
import { ExpressionFunction } from '../../../types';
import { FormState } from './';
const { Expression: strings } = ComponentStrings;
const { useRef } = React;
const shortcut = (ref, cmd, callback) => (
const shortcut = (
ref: MutableRefObject<ExpressionInput | null>,
cmd: string,
callback: () => void
) => (
<Shortcuts
name="EXPRESSION"
handler={(command) => {
handler={(command: string) => {
const isInputActive = ref.current && ref.current.editor && ref.current.editor.hasTextFocus();
if (isInputActive && command === cmd) {
callback();
@ -40,18 +47,28 @@ const shortcut = (ref, cmd, callback) => (
/>
);
export const Expression = ({
interface Props {
functionDefinitions: ExpressionFunction[];
formState: FormState;
updateValue: (expression?: string) => void;
setExpression: (expression: string) => void;
done: () => void;
error?: string;
isCompact: boolean;
toggleCompactView: () => void;
}
export const Expression: FC<Props> = ({
functionDefinitions,
formState,
updateValue,
setExpression,
done,
error,
fontSize,
isCompact,
toggleCompactView,
}) => {
const refExpressionInput = useRef(null);
const refExpressionInput = useRef<null | ExpressionInput>(null);
const handleRun = () => {
setExpression(formState.expression);
@ -78,7 +95,6 @@ export const Expression = ({
<ExpressionInput
ref={refExpressionInput}
fontSize={fontSize}
isCompact={isCompact}
functionDefinitions={functionDefinitions}
error={error ? error : `\u00A0`}

View file

@ -1,112 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { connect } from 'react-redux';
import {
compose,
withState,
withHandlers,
lifecycle,
withPropsOnChange,
branch,
renderComponent,
} from 'recompose';
import { fromExpression } from '@kbn/interpreter/common';
import { withServices } from '../../services';
import { getSelectedPage, getSelectedElement } from '../../state/selectors/workpad';
import { setExpression, flushContext } from '../../state/actions/elements';
import { ElementNotSelected } from './element_not_selected';
import { Expression as Component } from './expression';
const mapStateToProps = (state) => ({
pageId: getSelectedPage(state),
element: getSelectedElement(state),
});
const mapDispatchToProps = (dispatch) => ({
setExpression: (elementId, pageId) => (expression) => {
// destroy the context cache
dispatch(flushContext(elementId));
// update the element's expression
dispatch(setExpression(expression, elementId, pageId));
},
});
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { element, pageId } = stateProps;
const allProps = { ...ownProps, ...stateProps, ...dispatchProps };
if (!element) {
return allProps;
}
const { expression } = element;
const functions = Object.values(allProps.services.expressions.getFunctions());
return {
...allProps,
expression,
functionDefinitions: functions,
setExpression: dispatchProps.setExpression(element.id, pageId),
};
};
const expressionLifecycle = lifecycle({
componentDidUpdate({ expression }) {
if (
this.props.expression !== expression &&
this.props.expression !== this.props.formState.expression
) {
this.props.setFormState({
expression: this.props.expression,
dirty: false,
});
}
},
});
export const Expression = compose(
withServices,
connect(mapStateToProps, mapDispatchToProps, mergeProps),
withState('formState', 'setFormState', ({ expression }) => ({
expression,
dirty: false,
})),
withState('isCompact', 'setCompact', true),
withHandlers({
toggleCompactView: ({ isCompact, setCompact }) => () => {
setCompact(!isCompact);
},
updateValue: ({ setFormState }) => (expression) => {
setFormState({
expression,
dirty: true,
});
},
setExpression: ({ setExpression, setFormState }) => (exp) => {
setFormState((prev) => ({
...prev,
dirty: false,
}));
setExpression(exp);
},
}),
expressionLifecycle,
withPropsOnChange(['formState'], ({ formState }) => ({
error: (function () {
try {
// TODO: We should merge the advanced UI input and this into a single validated expression input.
fromExpression(formState.expression);
return null;
} catch (e) {
return e.message;
}
})(),
})),
branch((props) => !props.element, renderComponent(ElementNotSelected))
)(Component);

View file

@ -0,0 +1,124 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FC, useState, useCallback, useMemo, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fromExpression } from '@kbn/interpreter/common';
import { useServices } from '../../services';
import { getSelectedPage, getSelectedElement } from '../../state/selectors/workpad';
// @ts-expect-error
import { setExpression, flushContext } from '../../state/actions/elements';
// @ts-expect-error
import { ElementNotSelected } from './element_not_selected';
import { Expression as Component } from './expression';
import { State, CanvasElement } from '../../../types';
interface ExpressionProps {
done: () => void;
}
interface ExpressionContainerProps extends ExpressionProps {
element: CanvasElement;
pageId: string;
}
export interface FormState {
dirty: boolean;
expression: string;
}
export const Expression: FC<ExpressionProps> = ({ done }) => {
const { element, pageId } = useSelector((state: State) => ({
pageId: getSelectedPage(state),
element: getSelectedElement(state),
}));
if (!element) {
return <ElementNotSelected done={done} />;
}
return <ExpressionContainer key={element.id} done={done} element={element} pageId={pageId} />;
};
const ExpressionContainer: FC<ExpressionContainerProps> = ({ done, element, pageId }) => {
const services = useServices();
const dispatch = useDispatch();
const [isCompact, setCompact] = useState<boolean>(true);
const toggleCompactView = useCallback(() => {
setCompact(!isCompact);
}, [isCompact, setCompact]);
const dispatchSetExpression = useCallback(
(expression: string) => {
// destroy the context cache
dispatch(flushContext(element.id));
// update the element's expression
dispatch(setExpression(expression, element.id, pageId));
},
[dispatch, element, pageId]
);
const [formState, setFormState] = useState<FormState>({
dirty: false,
expression: element.expression,
});
const updateValue = useCallback(
(expression: string = '') => {
setFormState({
expression,
dirty: true,
});
},
[setFormState]
);
const onSetExpression = useCallback(
(expression: string) => {
setFormState({
...formState,
dirty: false,
});
dispatchSetExpression(expression);
},
[setFormState, dispatchSetExpression, formState]
);
const currentExpression = formState.expression;
const error = useMemo(() => {
try {
// TODO: We should merge the advanced UI input and this into a single validated expression input.
fromExpression(currentExpression);
return null;
} catch (e) {
return e.message;
}
}, [currentExpression]);
useEffect(() => {
if (element.expression !== formState.expression && !formState.dirty) {
setFormState({
dirty: false,
expression: element.expression,
});
}
}, [element, setFormState, formState]);
return (
<Component
done={done}
isCompact={isCompact}
functionDefinitions={Object.values(services.expressions.getFunctions())}
formState={formState}
setExpression={onSetExpression}
toggleCompactView={toggleCompactView}
updateValue={updateValue}
error={error}
/>
);
};

View file

@ -21,7 +21,6 @@ import {
import { WorkpadManager } from '../workpad_manager';
import { RouterContext } from '../router';
import { PageManager } from '../page_manager';
// @ts-expect-error untyped local
import { Expression } from '../expression';
import { Tray } from './tray';