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

Add ability to load custom Experiment Engine UI #101

Merged
merged 8 commits into from
Sep 30, 2021

Conversation

krithika369
Copy link
Collaborator

@krithika369 krithika369 commented Sep 28, 2021

This PR adds the ability to load a remote UI component exposed by an experiment engine, at the /experiments path of the Turing app. This is made possible by the following 2 configurations (added to ui/.env.development):

  • REACT_APP_DEFAULT_EXPERIMENT_ENGINE
  • REMOTE_COMPONENTS_PROXY_PATHS

The first one introduces the concept of a 'default' experiment engine, tied to the Turing app. Although Turing support multiple experiment engines when configuring routers, the /experiments endpoint is meant to configure the experiments themselves and this will only be possible for one experiment engine that is recognised as the default. In the future, this env var will be removed and the value will instead be obtained through the Turing API.

The 2nd env var will be used only in the local environment, to proxy API requests required by the remote component so that CORS issues can be bypassed.

The remote component is created and consumed with the use of Module Federation. Some information on the main changes are described below.

Modifications

  • ui/package.json - Upgrade to CRA5 alpha build, use craco to override the Webpack configs
  • ui/craco.config.js - Add Module Federation settings
  • ui/src/setupProxy.js - Add proxy settings for API calls made by the remote component
  • ui/src/utils/remoteComponent.js, ui/src/hooks/useDynamicScript.js - Make it possible to load the remote module dynamically, as opposed to specifying the experiment engine remote in the Craco configs (ref: module-federation-examples).
  • ui/src/experiment/ExperimentsRouter.js - Dynamically load the experiment engine's landing page or show an appropriate message if it is not configured or if the load fails.
    Screenshot 2021-09-28 at 12 00 54 PM
  • ui/src/App.js - Register the /experiments route.
  • ui/src/bootstrap.js - Moved the contents of ui/src/index.js here to let it be loaded asynchronously.

The other changes in this PR are automatic formatting fixes.

References

"react-scripts": "5.0.0-next.37"
},
"resolutions": {
"react-scripts/**/postcss-normalize": "10.0.1"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Context: facebook/create-react-app#11278 (comment)

Currently, this will lead to a warning on yarn install:

warning Resolution field "postcss-normalize@10.0.1" is incompatible with requested version
"postcss-normalize@10.0.0"

@@ -1,28 +1,51 @@
const proxy = require("http-proxy-middleware");
const { createProxyMiddleware } = require("http-proxy-middleware");
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

http-proxy-middleware dependency version has been upgraded in the new CRA.

# JSON string map, key being the (unroutable) path used by the remote app for API calls,
# and value being the URL to proxy to. Used only in local development mode, to bypass CORS.
# More info: https://create-react-app.dev/docs/proxying-api-requests-in-development/
REMOTE_COMPONENTS_PROXY_PATHS=
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Used by setupProxy.js for local development. REACT_APP_ prefix is not required here.

@krithika369 krithika369 changed the title [WIP] Add Ability to load custom Experiment Engine UI Add ability to load custom Experiment Engine UI Sep 28, 2021
ui/.gitignore Outdated
@@ -15,3 +15,6 @@ yarn-error.log*

.env.development.local
/.eslintcache

# linting
.husky/
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I realise now there is a .gitignore inside this generated folder, so it's unnecessary to mention it here - removed.

@@ -14,4 +14,13 @@ REACT_APP_USER_DOCS_URL=[{"href":"https://github.com/gojek/turing","label":"Turi
# Link to the .proto file used by the Kafka Result Logger when using Protobuf serialization
REACT_APP_RESULT_LOG_PROTO_URL=

# Default Experiment Engine Remote UI settings
REACT_APP_DEFAULT_EXPERIMENT_ENGINE_REMOTE_APPNAME=
REACT_APP_DEFAULT_EXPERIMENT_ENGINE_REMOTE_URL=
Copy link
Contributor

Choose a reason for hiding this comment

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

Since these two variables needs to be defined simultaneously (either both or none), they can be combined into a single env variable defined as JSON object, similar to REACT_APP_USER_DOCS_URL.

ui/src/bootstrap.js Show resolved Hide resolved
ui/src/experiment/ExperimentsRouter.js Show resolved Hide resolved
}

// Load component from remote host
const Component = React.lazy(
Copy link
Contributor

Choose a reason for hiding this comment

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

can it be called ExperimentsLandingPage then?

);

return (
<React.Suspense fallback="Loading Experiments">
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the difference between this fallback="Loading Experiments" and "Loading Experiment Exgine ..." at L:36? In what scenarios each of them will be shown?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Loading Experiment Engine happens when the remoteEntry.js file is being retrieved and appended to the html head. This part is now required because we are not setting the remote module's configurations in craco.config.js at build time anymore.

fallback="Loading Experiments" or later for the router form, may be fallback="Loading Experiment Configuration" is a placeholder while React lazy-loads the component (using loadComponent in the preceding lines). It's hardly noticeable but still required - without the fallback property, the rendering will fail in the first attempt.

Refactored the file to use <FallbackView/> for all scenarios where the remote component is not successfully loaded.

Comment on lines 22 to 54
if (!ready || failed) {
return (
<EuiPage>
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
<PageTitle title="Experiments" />
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiText size="s" color="subdued">
{!defaultExperimentEngine.url
? "No default Experiment Engine configured"
: !ready
? "Loading Experiment Exgine ..."
: "Failed to load Experiment Exgine"}
</EuiText>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}

// Load component from remote host
const Component = React.lazy(
loadComponent(defaultExperimentEngine.appName, "./ExperimentsLandingPage")
);

return (
<React.Suspense fallback="Loading Experiments">
<Component projectId={projectId} />
</React.Suspense>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (!ready || failed) {
return (
<EuiPage>
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
<PageTitle title="Experiments" />
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiText size="s" color="subdued">
{!defaultExperimentEngine.url
? "No default Experiment Engine configured"
: !ready
? "Loading Experiment Exgine ..."
: "Failed to load Experiment Exgine"}
</EuiText>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
}
// Load component from remote host
const Component = React.lazy(
loadComponent(defaultExperimentEngine.appName, "./ExperimentsLandingPage")
);
return (
<React.Suspense fallback="Loading Experiments">
<Component projectId={projectId} />
</React.Suspense>
);
// Load component from remote host
const ExperimentsLandingPage = React.lazy(
loadComponent(defaultExperimentEngine.appName, "./ExperimentsLandingPage")
);
return ready
? (
<React.Suspense fallback="Loading Experiments">
<ExperimentsLandingPage projectId={projectId} />
</React.Suspense>
) : (
<EuiPage>
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
<PageTitle title="Experiments" />
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiText size="s" color="subdued">
{ failed
? (!!defaultExperimentEngine.url
? "Failed to load Experiment Exgine"
: "No default Experiment Engine configured"
) : "Loading Experiment Exgine ..."
}
</EuiText>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);

Copy link
Contributor

Choose a reason for hiding this comment

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

But more specifically, I wonder if this component should be broke down into two:

  1. When defaultExperimentEngine.url is not configured at all then we don't need to try to load something from remote host and just render "No default Experiment Engine configured" msg
  2. When defaultExperimentEngine.url is configured, then useDynamicScript and loadComponent are called and proper remote component is rendered.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. Refactored this file.

Copy link
Contributor

@romanwozniak romanwozniak left a comment

Choose a reason for hiding this comment

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

Also, your changes introducing many formatting changes, which makes the review process less pleasant. We use prettier for having a shared formatting configuration (it's defined here), and formatting is triggered with husky's pre-commit hook.

Seems like you are using a newer version of prettier than I do and --jsx-bracket-same-line option is deprecated since v2.40, hence the discrepancy. Can you please replace --jsx-bracket-same-line in package.json with --bracket-same-line and reformat your branch with:

yarn prettier "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}" --bracket-same-line --write

@krithika369
Copy link
Collaborator Author

@romanwozniak thanks for the review. I've addressed your comments. Could you have another look?

@krithika369 krithika369 merged commit d836c15 into caraml-dev:main Sep 30, 2021
@krithika369 krithika369 deleted the exp_engine_module_federation branch June 14, 2022 08:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants