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

HTML page rendering service #41964

Closed
mshustov opened this issue Jul 25, 2019 · 10 comments
Closed

HTML page rendering service #41964

mshustov opened this issue Jul 25, 2019 · 10 comments
Assignees
Labels
blocker Feature:New Platform Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v8.0.0

Comments

@mshustov
Copy link
Contributor

mshustov commented Jul 25, 2019

Rendering a view populates an HTML page with build artifacts and additional meta-data to bootstrap client-side core services and plugins.
Currently, Kibana supports 2 types of view rendering in ui_mixin

  • Render the Kibana app with default config. No user-specific settings are loaded.
  • Render Kibana app. The page is populated with user settings.

Also, there is an implicit concept of lightweight applications. Those applications do not have client code of the Kibana app but should have the same headers (CSP, CORS, etc.) as normal Kibana HTML page

HttpResource service (#44620) or another rendering service should provide API covering all 3 use-cases.

Known limitations
Concepts of uiExports and optimizer from Legacy platform don't exist in the New platform. Thus a Legacy platform code cannot be accessed, loaded and bootstrapped within the New Platform core.

Blocked by #18843
Related:
#41981

@mshustov mshustov added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Feature:New Platform labels Jul 25, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform

@mshustov mshustov changed the title View rendering in the New platform HTML page rendering service Sep 6, 2019
@mshustov
Copy link
Contributor Author

mshustov commented Sep 10, 2019

Server-side:

In the new platform, we have one js bundle - core, which bootstrap and load all the plugins. It means that we can serve only one page for different URLs and rely on client-side rendering to mount the required app depending on the current URL. It's different from the Legacy platform rendering, where the server needs to know app id to include a correct link to the application bundle.

There are a few blockers on the server-side. We should either migrate them to the New platform or bridge Legacy platform API for a while (available via Hapi server object):

Steps:

  1. To introduce Rendering service in the new platform. Which is responsible to populate a Kibana page template with passed data. I didn't find any use-cases when a plugin defines a custom template, seems that we can skip this functionality for a while. The Service should be coupled to HTTP service (important for integration with Legacy platform)
interface KibanaRenderParams {
 strictCsp: boolean;
 bootstrapScriptUrl: string;
 ...
}
interface RenderingServiceSetup {
  renderKibanaPage(params: KibanaRenderParams): string;
  1. Provide an ability to respond from HttpService with rendered page.
const page = rendering.renderKibanaPage();
return response.ok(page);
  1. Switch legacy ui_render_mixin to the New platform Rendering service.

Client-side

The current implementation of Application Service limits standalone apps URL format. The URL format is defined as /app/:appId, where appId is application Id to mount. Thus, any other URL format (for example, global resource /login) cannot be associated with New platform plugin.
There are several options to discuss:

  • Plugins declare other global(?) URLs as a part of their Application definition when registers in Application service. As before, an application is responsible to implement internal routing to render a page.
    Pros:
    One point for all the mounting logic.
    Easier to integrate with related services - capabilities, chrome, etc.
core.application.register({
  id: 'security',
  globalURL: ['/login', '/logout'],
  async mount(context, params) {
    // mount is called for `/app/security`, `/login` and `/logout`
    return renderApp(context, params);
   },
});
// or register as a separate app
core.application.register({
  id: 'security-login',
  globalURL: '/login',
  async mount(context, params) {
    // mount is called for  `/login` only
    return renderApp(context, params);
  },
});
  • Create a separate StandalonePage Service. Which is similar to the Application Service, but allows a plugin to register any URL.
    Pros:
    More focused Application and StandalonePage services.
core.standalonePage.register({
  url: '/login',
  async mount(context, params) {
    // mount is called for `/login` only
    return renderApp(context, params);
   },
});
  • If an application doesn't want to be listed in ChromeUI it should configure it somehow. In the current implementation, all the apps are listed by default. Should we split registration in the application service from registration in the ChromeUI?
core.chrome.addLink({ title, icon: ... })
core.application.register({ id, mount... });

@mshustov
Copy link
Contributor Author

Until the new API is ready we need to recommend a workaround.
Options:

  • To keep rendering in the legacy platform. Plugins can migrate code to the new platform. When then need to render HTML, they request a legacy route or redirect a request to the legacy route.
  • NP provides temporary API to render HTML. The platform can use Legacy infrastructure under the hood. Plugins don't depend on the implementation.

@joshdover
Copy link
Contributor

I proposed something similar to the second option here: #49102 (comment)

I think that's a practical way forward, but we should be careful about which options we expose in the NP API so we don't have too much churn from breaking it in the future.

@mshustov
Copy link
Contributor Author

consider adding a rendering helper #54470 (comment)

@mshustov
Copy link
Contributor Author

mshustov commented Mar 12, 2020

The current implementation has a couple of drawbacks:

return response.ok({
  body: await context.core.rendering.render({ includeUserSettings: true }),
  headers: { 'content-security-policy': csp.header },
});

We would like to improve the situation and avoid error-prone manual configuration. It seems that this logic is more appropriate for HttpResource service #44620

  • for basic use-cases when not much logic involved and we need to respond with the static "core" HTML page it can provide
interface HttpResourceSetup {
  registerStaticApp: ({ path: string }) => void
}
httpResource.registerStaticApp({ path: '/my-url'})
  • for use-cases when a plugin needs to perform some computation in a route handler we could adopt HttpResource service to provide Kibana-router compatible API:
interface HttpResourceSetup {
  registerApp: (RouteConfig<'get'>) => KibanaResponse
}
httpResource.registerApp({ path: '/my-url', validate: false }, async (context, request, response) => {
  // response already pre-configured with necessary CSP, Content-Type, Cache-control headers
  // plugins can override headers except for CSP header
  return response.ok({
    body: `<!DOCTYPE html> ...`
    headers: 
  });

Are we okay with this proposal?
Can we close the current issue in favor of #44620?
Should I outline RFC for HttpResource service?
@elastic/kibana-platform

@joshdover
Copy link
Contributor

joshdover commented Mar 15, 2020

I wonder if we couldn't improve the ergonomics of a regular app that needs a custom route (not prefixed with /app/):

  • Make the server-side API easier to find by making it part of the application namespace of CoreSetup: core.application.registerAppRoute('/login')
  • If an application is registered in the UI with a custom appRoute and there is no corresponding registration on the backend, we could throw an error to guide developers.
  • for use-cases when a plugin needs to perform some computation in a route handle we could adopt HttpResource service to provide Kibana-router compatible API:
interface HttpResourceSetup {
  registerApp: (RouteConfig<'get'>) => KibanaResponse
}
httpResource.registerApp({ path: '/my-url', validate: false }, async (context, request, response) => {
  // response already pre-configured with necessary CSP, Content-Type, Cache-control headers
  // plugins can override headers except for CSP header
  return response.ok({
    body: `<!DOCTYPE html> ...`
    headers: 
  });

This API seems to do very little. It feels like it should just be part of the ResponseFactory:

res.html({ html: ``, headers: {} });

@mshustov
Copy link
Contributor Author

mshustov commented Mar 16, 2020

This API seems to do very little. It feels like it should just be part of the ResponseFactory:

I thought about it, but I decided it wasn't enough for a few (maybe minor) reasons:

  • html will have an inconsistent interface with ResponseFactory. for example, you might need to send an HTML page with an error status code.
  • response type is not limited by html, but can be any type of dynamic content
    window.location.replace(
    '${serverBasePath}/api/security/oidc/callback?authenticationResponseURI=' + encodeURIComponent(window.location.href)
    );
    `,
    'text/javascript',

I wonder if we couldn't improve the ergonomics of a regular app that needs a custom route (not prefixed with /app/):

Why not for both prefixed and un-prefixed apps? As I understand, a user can land on any page.

@mshustov
Copy link
Contributor Author

Let's move discussion to #50654

@mshustov
Copy link
Contributor Author

will be done in #50654

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocker Feature:New Platform Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v8.0.0
Projects
None yet
Development

No branches or pull requests

4 participants