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

[browser] Application service #18843

Closed
9 of 10 tasks
epixa opened this issue May 5, 2018 · 7 comments
Closed
9 of 10 tasks

[browser] Application service #18843

epixa opened this issue May 5, 2018 · 7 comments
Assignees
Labels
enhancement New value added to drive a business result Feature:New Platform Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc

Comments

@epixa
Copy link
Contributor

epixa commented May 5, 2018

The client-side application service handles registering new applications. It should expose a client for mounting and unmounting applications, which will ultimately be used as a sort of base router in the UI. It might also want to manage application state, like whether the application is disabled or not, or at least provide a mechanism to plugins to indicate state.

Routing RFC: #36477


Execution plan

  • Fix Angular's broken URL encoding/decoding (Handle encoding and decoding of angular route url components #34300). This is necessary because we want to avoid any problems with malformed URLs being created by any apps that choose to use Angular in the new platform. Malformed URLs would break any root-level routing done by the application service in some scenarios. We are not planning to do unless we know for a fact that support for Angular plugins is critical in the New Platform.
  • Move ui/chrome angular configuration out (Extract angular configuration from ui/chrome #34314). This involves moving Angular configuration out of ui/chrome so apps can consume this configuration logic directly, rather than relying on global behavior to configure their app for them.
  • Create a shell ApplicationService (Create baseline structure for application service #34893). This shell would allow app registration with all the details needed for rendering navlinks in the Chrome UI. This is necessary to move the Nav APIs into Core without exposing more than we need to plugins.
  • Move Capabilities loading to frontend (Add application registration + move capability loading to frontend #35194). The capabilities data structure used by Feature Controls current depends on the backend have knowledge of the applications provided by plugins. We will need to move this to be fetched by the frontend after apps are registered.
  • Hide the "lastSubUrl" feature from new platform plugins (Move Nav APIs to new platform #34490). This currently lives as a "core" service in ui/plugins. We will need to expose some functionality in Core to enable legacy apps to modify the links in the header UI. However, this functionality will not be necessary in new platform plugins, so this will not be exposed to the new platform plugins at all.
  • Move Nav APIs to Core (Move Nav APIs to new platform #34490). Most of this will actually be removing these APIs completely. Plugins that directly modify navlinks will have to accomplish this in other ways. We've already tracked down where this is being done and do not expect this to be a problem.
  • Move Chrome UI to Core ([new-platform] migrate chrome UI to the new platform #27086). This involves removing the directive for the header from ui/chrome and rendering it directly from Core. This puts the new platform in control of the base element and would change which DOM element gets passed to the kbn-chrome directive for rendering legacy apps.
  • Add ContextService (Add ContextService #41251). This is required to use the handler context pattern in the AppService.
  • Separate legacy navlinks from NP navlinks ([new-platform] Add legacy property to NavLinks registered by legacy apps #41301). This is a small cleanup PR to make implementing AppService easier.
  • Introduce ApplicationService routing and bootstrapping (Add ApplicationService Mounting #41007). This involves:
    • Adding a new Core-only bundle + entry point route
    • Allowing NP plugins to register apps
    • Support SPA-style routing for NP apps
    • Perform full-page refresh when navigating between Legacy <-> NP apps
@epixa epixa added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Feature:New Platform enhancement New value added to drive a business result labels May 5, 2018
@epixa epixa added this to Services in New platform miscellaneous May 6, 2018
@epixa epixa changed the title Application service [browser] Application service May 7, 2018
@epixa epixa moved this from To do to In progress in kibana-core [DEPRECATED] Mar 20, 2019
@joshdover
Copy link
Contributor

joshdover commented Mar 27, 2019

The exploration below resulted in us deciding against using iframes.


I've done some exploration on syncing the window.history in an iframe with the parent and have put together a small demo here. Main conclusions:

  • Syncing window.history changes works and could be extended easily to also support changes to window.location.hash by watching the hashchanged event on the iframe's window.
  • We cannot support things like window.location = '/url' without polling, 👎. The browser does not let us redefine this property descriptor in order to capture the setter of this property.
  • We could expose services directly to the iframe by setting values on it's window. Because we're on the same domain, I don't believe we have to use the postMessage API.

One of the hairy parts of rendering inside iframes would be getting the actual application to load inside the iframe and still be able to access core services or plugin-specific code.

One way I think of solving this would be to require that applications provide a separate entry file that the ApplicationService would load for them. This entry bundle could expose a mount function that ApplicationService would call with specified arguments. The main drawback of this approach is that the mount function is not a closure inside the plugin's setup callback like we've been envisioning.

// plugin.js
class Plugin {
  setup(core) {
    core.applicationService.registerApplication({
      name: 'Dashboard',
      route: '/dashboard',
      icon: <EuiIcon type="dashboardApp" />,
      entryPoint: './my_entry_point.js',
      // Application service would call window.mount with this array
      // as it's args.
      mountArgs: [{
        kfetch: core.kfetch,
        myService: {
          getObj() { ... },
        }
      }]
    });
  }
}

// my_entry_point.js
window.__kbnMount__ = (targetDomElement, { kfetch, myService }) => {
  ReactDOM.mount(<MyApp />, targetDomElement);
  return () => {
    ReactDOM.unmountComponentAtNode(targetDomElement);
  };
};

This would introduce an implicit contract where the app service executes window.__kbnMount__ that was created by the my_entry_point.js file when loaded on an HTML page. Type-safety for this bundle would not be guaranteed, but could be enforced by exporting a type to represent the parameters of your mount function.

This method would provide us more "sandboxing" which could be particularly useful for 3rd-party plugins. They may not know about our custom angular-route hacks to make Angular.js v1 work on new Kibana. Any other frameworks they use that do any global monkey-patching would be safe to use.

The downside is that this solution is brittle, could break in some browsers (so far, I've only tested in Firefox), and may introduce security concerns that need to be handled.

@joshdover
Copy link
Contributor

joshdover commented Apr 5, 2019

@eliperelman and I synced today and here is our tentative plan:

Moved to description above

@joshdover
Copy link
Contributor

After some investigations on (3) above, I've decided that completely moving the "lastSubUrl" feature into plugins is not feasible until plugins are on the new platform.

The reason for this boils down to the fact that a legacy Angular app does not know when it is being "mounted" by Chrome vs. the user trying to get back to the root of the app. Without this, an individual plugin cannot make the decision of whether or not they should redirect to the last known location or render the root of the app.

Instead, I will modify the existing lastSubUrl logic to manually modify navLinks in the new platform ChromeService. However, I will not expose this functionality to new platform plugins. New platform plugins will have to handle this feature themselves, by tracking route changes in their app and redirecting to the last location in their ApplicationService mount callback.

@mshustov
Copy link
Contributor

mshustov commented Apr 9, 2019

on syncing the window.history in an iframe

Where I can find a discussion about introducing that approach and why we need to use it?

@joshdover
Copy link
Contributor

@restrry It was being explored as an option when figuring out how best to support angular apps in the new platform. We have since decided against it and are moving forward with fixing Angular's URL encoding bugs instead.

I apologize this wasn't recorded anywhere, happened in a flurry of video calls.

@joshdover
Copy link
Contributor

joshdover commented Apr 10, 2019

Part of our plain entails building out in parallel a few of the pieces (shell AppService, moving Nav APIs to new platform, adding AppService routing). We don't want to merge an incomplete AppService that may confuse other developers.

We've decided to open a branch for us to collaborate on together so we can make sure these pieces fit together well before merging to master. @eliperelman has opened the application-service branch on the root repo.

We may still merge a feature-complete AppService to master before turning it on for routing and bootstrapping of legacy apps.

@joshdover
Copy link
Contributor

One snag I've encountered when moving NavLinks to Core is Feature Controls. Feature controls currently rely on server.getUiNavLinks() to populate the initial list of navLinks that should be presented to the user based on their permissions.

This is all done with server-side code in the plugin, which is incompatible with new platform plugins which can be client-only (a hard requirement). Additionally, we may want to allow plugins to conditionally register applications, or register them based on some data fetched from elsewhere, so a static spec solution here will not work (eg. adding a list of applications to kibana.json).

I've talked this over with @kobelb and have come up with a PoC. In order to support Feature Controls and the design goals of the ApplicationService, I propose that:

  • uiCapabilities (which controls which navLinks are displayed) are not resolved on the server.
  • Instead, the Capabilities service will make a request to the backend to fetch authorization permissions for the registered applications. The ApplicationService will then consume a filtered list of available Applications based on the user's permissions.

Implementation Details

In the initial implementation of this, I believe we will have to use an Observable of registered applications since this list is not guaranteed to be "finalized" in any point during the "setup" lifecycle event. Once we have a "start" lifecycle event, we will be able to change this to a single fetch, without the complexities of an Observable.

With the new dependencies between services (ApplicationService, Capabilities, ChromeService) I also plan to move Capabilities to be a sub-component of the ApplicationService. Reason being is that Capabilities depends on ApplicationService's Observable<App[]> and the ApplicationService also needs the list of "available apps" from Capabilities so that it can block routing to an app that the user does not have access to.

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

No branches or pull requests

5 participants