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

expression service #42337

Merged
merged 6 commits into from
Sep 4, 2019
Merged

expression service #42337

merged 6 commits into from
Sep 4, 2019

Conversation

ppisljar
Copy link
Member

@ppisljar ppisljar commented Jul 31, 2019

Summary

Implements basic expression service

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

For maintainers

@ppisljar ppisljar added WIP Work in progress Team:AppArch labels Jul 31, 2019
@ppisljar ppisljar requested review from flash1293 and lukeelmers July 31, 2019 11:37
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-app-arch

@elasticmachine
Copy link
Contributor

💔 Build Failed


// Accept all options of the runner as props except for the
// dom element which is provided by the component itself
export type ExpressionRendererProps = Pick<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing that makes it impossible to pass in things like the context over the react interface. IExpressionLoaderParams should be part of the renderer props. They are already passed correctly to the loader: https://github.com/elastic/kibana/pull/42337/files#diff-e2b0614a60b75e9f05d4bec84efb896aR50

this.abortController = new AbortController();
this.inspectorAdapters = this.getActiveInspectorAdapters();

const getInitialContext = () => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only breaking change for the expression renderer react component is that it won't be possible to pass in a custom getInitialContext function anymore. Maybe I'm missing something, but is that limitation necessary?

The alternative would be adding getInitialContext to IExpressionLoaderParams instead of searchContext. Then there could also be initial contexts other than kibana_context. Not important for the usage in Lens though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can pass in the SearchContext (which will be returned by getInitialContext).
for now lets keep it like this, if we find a usecase for sending in something else we can extend this in the future.

@@ -50,7 +47,8 @@ export const createRenderer = (run: ExpressionRunner): ExpressionRenderer => ({

useEffect(() => {
if (mountpoint.current) {
run(expression, { ...options, element: mountpoint.current }).catch(result => {
const handler = loader(mountpoint.current, expression, options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The re-creates the loader every time a new expression is passed into this component, right? Shouldn't we hold the loader in a ref? Otherwise cancel isn't called correctly in this branch https://github.com/elastic/kibana/pull/42337/files#diff-1793959dde8c2d221081302bc14dd984R99 for the old loader.

*/

import { fromExpression } from '@kbn/interpreter/target/common';
import { DataAdapter, RequestAdapter } from 'ui/inspector/adapters';
Copy link
Contributor

@lizozom lizozom Aug 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change the imports to the new plugin.
You will have to export DataAdapter, RequestAdapter from the top level of the inspector plugin.

I started here #42615, but closing this PR now.

@ppisljar ppisljar force-pushed the expressionService/1 branch from e5056d3 to 23af8fa Compare August 22, 2019 11:43
@ppisljar ppisljar added review and removed WIP Work in progress labels Aug 22, 2019
@ppisljar ppisljar requested a review from streamich August 22, 2019 11:43
@ppisljar ppisljar added v7.4.0 v8.0.0 release_note:skip Skip the PR/issue when compiling release notes labels Aug 22, 2019
@elasticmachine
Copy link
Contributor

💔 Build Failed

Copy link
Contributor

@streamich streamich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, subject to Joe's questions and green CI.

import { Observable } from 'rxjs';
import * as Rx from 'rxjs';
import { share, first } from 'rxjs/operators';
import { renderersRegistry } from '../../../../interpreter/public/registries';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I'm adding getRenderer method to Expressions service in a parallel PR.

this.renderHandler = new ExpressionRenderHandler(element);
this.render$ = this.renderHandler.render$;
this.update$ = this.renderHandler.update$;
this.events$ = this.renderHandler.events$;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why set all these observables on this object, we can already access them through this.renderHandler.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderHandler is private and not accessible to the outside


export type ExpressionRenderer = React.FC<ExpressionRendererProps>;

export const createRenderer = (run: ExpressionRunner): ExpressionRenderer => ({
export const createRenderer = (loader: IExpressionLoader): ExpressionRenderer => ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IExpressionLoader is a type, not interface, but it has I prefix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a convention for naming types ? TExpressionLoader ?


import { fromExpression } from '@kbn/interpreter/target/common';
import { DataAdapter, RequestAdapter } from '../../../../../ui/public/inspector/adapters';
import { Adapters } from '../../../../../ui/public/inspector';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like Liza mentioned, the above two could be like this:

import { DataAdapter, RequestAdapter, Adapters } from '../../../../../../plugins/inspector/public';

* under the License.
*/

import { Ast } from '@kbn/interpreter/common';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now this is just typed as unknown in the @kbn/interpreter package. We should instead use the ExpressionAST interface from src/plugins/data/common/expressions/types

update: (params: any) => {
updateSubject.next(params);
},
event: (data: any) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the anys needed here, or are these types already provided by IInterpreterRenderHandlers?

@@ -28,7 +28,7 @@ export function plugin() {

/** @public types */
export type DataSetup = DataSetup;
export { ExpressionRenderer, ExpressionRendererProps, ExpressionRunner } from './expressions';
export { ExpressionRenderer, ExpressionRendererProps } from './expressions';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should export DataStart here too

return data;
};

private async render(data: Data): Promise<RenderId> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something, but this is ultimately calling the renderer from the registry right (via the render handler?)

Are any renderers actually returning a Promise<RenderId> today? Otherwise we should perhaps be adding an ExpressionRenderer generic, similar to ExpressionFunction, so that this can actually be enforced.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm ... here we call the expression renderer handler, which will return a promise which will always resolve with the renderId. There internally we call renderer from the registry and pass it the handlers. Once rendering is done the done() handler should be called (and is nowadays i think for all renderers) which then resolves the promise.

@ppisljar ppisljar force-pushed the expressionService/1 branch from 23af8fa to 3f4cb0d Compare September 3, 2019 10:48
@ppisljar ppisljar added v7.5.0 and removed v7.4.0 labels Sep 3, 2019
@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

expression,
onRenderFailure,
...options
}: ExpressionRendererProps) => {
const mountpoint: React.MutableRefObject<null | HTMLDivElement> = useRef(null);

const handlerRef = useRef((null as any) as ExpressionLoader);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can be written as useRef<null | ExpressionLoader>(null); for better type safety.

/**
* If an element is specified, but the response of the expression run can't be rendered
* because it isn't a valid response or the specified renderer isn't available,
* this callback is called with the given result.
*/
onRenderFailure?: (result: Result) => void;
};
onRenderFailure?: (result: IInterpreterResult) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a className prop here that's passed to the mount div element?

} else {
handlerRef.current.update(expression, options);
}
handlerRef.current.data$.toPromise().catch(result => {
if (onRenderFailure) {
onRenderFailure(result);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effect should also re-render if options.context changes

}

public start({ inspector }: ExpressionsServiceStartDependencies) {
const ExpressionRenderer = createRenderer(loader);
Copy link
Contributor

@flash1293 flash1293 Sep 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we expose the renderer in the setup phase? Lens currently requires the expression renderer in the embeddable factory which should register its stuff in the setup phase.

EDIT: OK, I see why it is solved this way - the inspector is not ready in the setup phase. I guess we could work around it in Lens and pass the expression renderer to the embeddable factory instance in the start phase - slightly dirty but probably the best way. It's not super clean because it hides the fact that Lens embeddables can only be created after the start phase but it shouldn't cause problems in this particular instance and I can throw an explanatory error if it happens.

Still wondering whether there is a better way though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, also different plugins (will) register renderers, so in your setup lifecycle there is no guarantee that the functions or renderers exist in the registry yet.

adding options to deps
Copy link
Contributor

@flash1293 flash1293 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes LGTM

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@ppisljar ppisljar merged commit 2f5306e into elastic:master Sep 4, 2019
> & {
expression: string | Ast;
export interface ExpressionRendererProps extends IExpressionLoaderParams {
className: 'string';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ppisljar The only allowed className here is the literal string "string"- the quotes should be removed

ppisljar added a commit to ppisljar/kibana that referenced this pull request Sep 9, 2019
ppisljar added a commit that referenced this pull request Sep 9, 2019
@elasticmachine
Copy link
Contributor

💔 Build Failed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release_note:skip Skip the PR/issue when compiling release notes review v7.5.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants