-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
expression service #42337
Conversation
Pinging @elastic/kibana-app-arch |
💔 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< |
There was a problem hiding this comment.
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 = () => ({ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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.
e5056d3
to
23af8fa
Compare
💔 Build Failed |
There was a problem hiding this 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'; |
There was a problem hiding this comment.
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$; |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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 => ({ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the any
s 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'; |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
23af8fa
to
3f4cb0d
Compare
💔 Build Failed |
💚 Build Succeeded |
expression, | ||
onRenderFailure, | ||
...options | ||
}: ExpressionRendererProps) => { | ||
const mountpoint: React.MutableRefObject<null | HTMLDivElement> = useRef(null); | ||
|
||
const handlerRef = useRef((null as any) as ExpressionLoader); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); | ||
} |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes LGTM
💚 Build Succeeded |
> & { | ||
expression: string | Ast; | ||
export interface ExpressionRendererProps extends IExpressionLoaderParams { | ||
className: 'string'; |
There was a problem hiding this comment.
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
💔 Build Failed |
Summary
Implements basic expression service
Checklist
Use
strikethroughsto remove checklist items you don't feel are applicable to this PR.For maintainers