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 wrapper and utilities around EuiThemeProvider #117368

Merged
merged 35 commits into from
Nov 17, 2021

Conversation

pgayvallet
Copy link
Contributor

@pgayvallet pgayvallet commented Nov 3, 2021

Summary

Stage 1 of #108972

This PR is the first stage to leverage the EuiTheme via EuiThemeProvider introduced by EUI.

Once the PR is merged, I will open a distinct issue for stage 2, where all application owners will have to adapt their application mounting and their usages of toMountPoint to leverage this new API.

changes:

core

  • Add a new theme service to core's client-side API

    • Introduce a CoreTheme type, containing all the theme-related informations required by EUI (currently only the darkMode uiSetting value)
    • The service exposes a theme$ observable emitting the theme
  • Adapt core's internal usages of react to wrap the nodes with a new <CoreThemeProvider> HOC. The HOC consumes core's CoreTheme and wrap the underlying react node with <EuiThemeProvider>.

  • Also expose the theme$ observable from AppMountParameter, to ease usage with plugin applications.

kibana_react

  • Add a new <KibanaThemeProvider> HOC, and the associated wrapWithTheme utility function
  • Adapt toMountPoint to accept an (optional, at least for now) theme$ parameter
    • when provided, the react node will be wrapped with <KibanaThemeProvider>

Checklist

@pgayvallet pgayvallet added release_note:skip Skip the PR/issue when compiling release notes Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v8.1.0 EUI labels Nov 4, 2021
Copy link
Contributor

@jportner jportner left a comment

Choose a reason for hiding this comment

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

Code review only, Security changes to add themeServiceMock to tests LGTM

@botelastic botelastic bot added the Team:Fleet Team label for Observability Data Collection Fleet team label Nov 9, 2021
@elasticmachine
Copy link
Contributor

Pinging @elastic/fleet (Team:Fleet)

Copy link
Member

@sabarasaba sabarasaba left a comment

Choose a reason for hiding this comment

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

stack management changes LGTM

Copy link
Contributor

@thompsongl thompsongl left a comment

Choose a reason for hiding this comment

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

Looks great, @pgayvallet! Confirmed that variables are accessible via useEuiTheme in chrome/ui and that color mode switching works.

What is the scope of part 2? Update every single plugin, I assume?

const coreTheme = useObservable(theme$, defaultTheme);
const euiTheme = useMemo(() => convertCoreTheme(coreTheme), [coreTheme]);
return (
<EuiThemeProvider colorMode={euiTheme.colorMode} theme={euiTheme.euiThemeSystem}>
Copy link
Contributor

Choose a reason for hiding this comment

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

Question for @cchaos: This is using the (soon-to-be) legacy theme until elastic/eui#5121 merges. Should we configure to use Amsterdam in in the meantime?

dismissToast={(toastId: string) => this.api!.remove(toastId)}
toasts$={this.api!.get$()}
/>
<CoreThemeProvider theme$={theme.theme$}>
Copy link
Contributor

Choose a reason for hiding this comment

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

we use the same pattern in all the integration points:

<i18n.Context>
  <CoreThemeProvider theme$={theme.theme$}>
    ...

Maybe it makes sense to provide it as a single CoreIntegrationPoint component?

src/core/public/theme/types.ts Outdated Show resolved Hide resolved

public setup({ injectedMetadata }: SetupDeps): ThemeServiceSetup {
const theme = injectedMetadata.getTheme();
this.theme$ = new BehaviorSubject<CoreTheme>({ darkMode: theme.darkMode });
Copy link
Contributor

Choose a reason for hiding this comment

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

When are we going to support theme updates?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When we'll eventually allow theme customization by the user. This would allow, for example, to preview the theme overrides dynamically. Having an observable for the theme is to anticipate this, and to avoid a breaking change in the API later when we'll have to support it.

But for now, I guess using of({ darkMode: theme.darkMode }) instead of using a behavior subject would be perfectly fine, if that was the question

Copy link
Contributor

Choose a reason for hiding this comment

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

But for now, I guess using of({ darkMode: theme.darkMode }) instead of using a behavior subject would be perfectly fine, if that was the question

yes, let's start with more clear API.

@pgayvallet
Copy link
Contributor Author

@thompsongl

What is the scope of part 2? Update every single plugin, I assume?

That's correct, part 2 is asking each team to update their applications and usages of toMountPoint to use the theme wrapper.

Copy link
Contributor

@jloleysens jloleysens left a comment

Choose a reason for hiding this comment

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

Proposed changes for app services LGTM

Comment on lines +24 to +28
export const KibanaThemeProvider: FC<KibanaThemeProviderProps> = ({ theme$, children }) => {
const theme = useObservable(theme$, defaultTheme);
const colorMode = useMemo(() => getColorMode(theme), [theme]);
return <EuiThemeProvider colorMode={colorMode}>{children}</EuiThemeProvider>;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

this makes sense to me, I can see how this can be added without requiring refactors for consumers.

@elastic elastic deleted a comment from kibanamachine Nov 16, 2021
@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
core 315 320 +5
kibanaReact 301 305 +4
total +9

Public APIs missing comments

Total count of every public API that lacks a comment. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats comments for more detailed information.

id before after diff
core 1027 1031 +4
kibanaReact 205 213 +8
total +12

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
core 132.3KB 132.4KB +2.0B
devTools 2.3KB 2.3KB +29.0B
savedObjectsManagement 82.6KB 82.7KB +54.0B
total +85.0B

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
core 269.2KB 270.8KB +1.6KB
devTools 9.3KB 9.3KB +11.0B
kibanaReact 57.9KB 58.3KB +410.0B
total +2.0KB
Unknown metric groups

API count

id before after diff
core 2309 2318 +9
kibanaReact 229 237 +8
total +17

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@pgayvallet pgayvallet merged commit 04d5762 into elastic:main Nov 17, 2021
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label Nov 17, 2021
TinLe pushed a commit to TinLe/kibana that referenced this pull request Nov 20, 2021
* draft implementation

* refactor and do not use COLOR_MODES_STANDARD

* plug CoreThemeProvider to rendering service

* plug CoreThemeProvider to toast service

* plug CoreThemeProvider to overlay service

* adapt tests

* adapt application tests

* adapt core apps

* fix tests and snapshots

* fix types

* fix setup mock

* fix more types

* add theme support to `toMountPoint`

* add theme start to public API

* fix integration test

* self-review, first batch

* self-review, second batch

* add tests for core theme provider

* add theme service tests

* add more tests

* add test for to_mount_point

* update generated doc

* fix type in story file

* fix type in fleet story context

* use proper theme$ for devtool's mount

* update generated doc

* review comments

* introduce CoreContextProvider

Co-authored-by: Kibana Machine <[email protected]>
dmlemeshko pushed a commit that referenced this pull request Nov 29, 2021
* draft implementation

* refactor and do not use COLOR_MODES_STANDARD

* plug CoreThemeProvider to rendering service

* plug CoreThemeProvider to toast service

* plug CoreThemeProvider to overlay service

* adapt tests

* adapt application tests

* adapt core apps

* fix tests and snapshots

* fix types

* fix setup mock

* fix more types

* add theme support to `toMountPoint`

* add theme start to public API

* fix integration test

* self-review, first batch

* self-review, second batch

* add tests for core theme provider

* add theme service tests

* add more tests

* add test for to_mount_point

* update generated doc

* fix type in story file

* fix type in fleet story context

* use proper theme$ for devtool's mount

* update generated doc

* review comments

* introduce CoreContextProvider

Co-authored-by: Kibana Machine <[email protected]>
pgayvallet added a commit that referenced this pull request Feb 20, 2024
## Summary

Fix #89340

Implements a third option, `system`, for the `theme:darkMode`
uiSettings, which will follow the system's theme preference (light/dark)
when Kibana loads.


https://github.com/elastic/kibana/assets/1532934/82078697-8bf5-41df-add1-4ecfed6e1dea

**Note: system theme refresh still requires the user to reload Kibana -
please see the next section for the reasons if you're interested**


## How theming works in Kibana, again?

This is an excellent question, thanks for asking. And the answer is,
"well, it's complicated".

We have multiples sources of "themed" styles in Kibana, ordered from
"best" to "worse":

#### 1. the EUI/JSS Theming

It was initially implemented in
#117368. All react applications
and react mountpoints are supposed to be wrapped by a
`KibanaThemeProvider` that bridges core's `theme$` values to the
`EuiProvider`.


https://github.com/elastic/kibana/blob/477505a2dd86d8f103f3aef16b3b4b76dff0b27a/packages/core/theme/core-theme-browser-internal/src/core_theme_provider.tsx#L11

This one was already dynamic and just works perfectly. If
`core.theme.theme$` changes, the new values is received by the theme
provider, which automatically changes the styles accordingly, creating
this sexy "it just works" effect:


https://github.com/elastic/kibana/assets/1532934/f3e61ca7-f3ed-4c37-aa46-76ee68c1a628

If everything theme-related was using this approach, dynamic theme
reload would have been possible. However, Kibana has a lot of legacy, so
as you can imagine, it wasn't that easy.

So, **don't get false hopes** (as I did when I tried it...) from this
video. Dynamic theme swap **could not** be implemented in this PR. And
the reasons are just below.

#### 2. Per-theme css files


https://github.com/elastic/kibana/blob/6443b571642c453a9232a04297fddfb0a918c0dc/packages/core/rendering/core-rendering-server-internal/src/render_utils.ts#L40-L54

We have a bunch of dark/light variations of some css files that are
computed in the rendering service, server-side, to be injected into the
page template.

Of course, this doesn't play well with dynamic theming, given the UI
then doesn't know which css should be swapped, and which one should be
added instead.

However, porting the responsibilities of which theme css files to load
to the browser was achievable, and done in this PR. core's browser-side
`theme` provider now receives the list of theme files for both light and
dark theme (via the injected metadata), and inject them in the document
(instead of having them pre-injected in the page template by the
rendering service server-side).

So this one wasn't a blocker for dynamic theme reload.  

#### 3. Plugin styles 

This is where the problems start.

Plugins can ship their own styles too, by importing them from components
or from their entrypoint.

E.g this component


https://github.com/elastic/kibana/blob/f1dc1e1869566c1d61a08bb67eb3d4ac2f47834e/src/plugins/controls/public/control_group/component/control_group_component.tsx#L9

importing this file:


https://github.com/elastic/kibana/blob/bafb23580b18781e711575cd8c0e17a6e3f4f740/src/plugins/controls/public/control_group/control_group.scss#L107-L110

Which relies on a theme variable, `$euiFormBackgroundColor`

So how does it works under the hood? How is that
`$euiFormBackgroundColor` variable interpolated? Using which theme?

Well, technically, how the styles are effectively loaded differs
slightly between dev and production (different webpack loaders/config),
but in both cases, it depends on the `__kbnThemeTag__` variable that is
injected to the global scope by the `bootstrap.js` script.

This `__kbnThemeTag__` tag (apparently) **can** be modified after page
load. However it doesn't magically reload everything, so styles already
loaded for one theme will not reload. If a component and its imported
styles were already compiled / injected, then they won't reload

As a short video is better than 3 blocks of text, just see:


https://github.com/elastic/kibana/assets/1532934/3087ffd6-80d8-42bf-ab17-691ec408ea6f

That was the first blocker for supporting "dynamic" reloads of the
system theme.

#### 4. Static inline styles

Last but not least, we have some static style injected in the template,
that also depend on the current theme.


https://github.com/elastic/kibana/blob/6443b571642c453a9232a04297fddfb0a918c0dc/packages/core/rendering/core-rendering-server-internal/src/views/styles.tsx#L52-L54

Of course this plays very badly with dynamic theming. And worth noting,
those styles are used by the "Loading Elastic" screen, where Core (and
therefore Core's theming service) isn't loaded yet, which made the
things even more complicated.

This was the second blocker for supporting "dynamic" reloads of the
system theme.

#### 5. `euiThemeVars`

Actually TIL (not that I was asking for it)

We're exposing the EUI theme variable via the `euiThemeVars` of the
`@kbn/ui-theme` package:

E.g.


https://github.com/elastic/kibana/blob/c7e785383a87f7e18509c601a5c089c755ac028e/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx#L41


https://github.com/elastic/kibana/blob/c7e785383a87f7e18509c601a5c089c755ac028e/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx#L50

So I did my best, and made it that this export was a proxy, and that
Core's theme service was dynamically swapping the target of the proxy
depending on the system's theme...


https://github.com/elastic/kibana/blob/b0a0017811d7402f76709af7ab1b8e12334e64a5/packages/kbn-ui-theme/src/theme.ts#L30-L42

Unfortunately it doesn't fully work for dynamic system theme switch,
given modules/code can keep references of the property directly (e.g.
the snippet a few lines on top), and there's nothing to dynamically
reload components when the proxy changes.

So yet another blocker for dynamic theme switch. 


## Release Notes

Add a new option, `system`, to the `theme:darkMode` Kibana advanced
setting, that can be used to have Kibana's theme follow the system's
(light or dark)

---------

Co-authored-by: kibanamachine <[email protected]>
fkanout pushed a commit to fkanout/kibana that referenced this pull request Mar 4, 2024
)

## Summary

Fix elastic#89340

Implements a third option, `system`, for the `theme:darkMode`
uiSettings, which will follow the system's theme preference (light/dark)
when Kibana loads.


https://github.com/elastic/kibana/assets/1532934/82078697-8bf5-41df-add1-4ecfed6e1dea

**Note: system theme refresh still requires the user to reload Kibana -
please see the next section for the reasons if you're interested**


## How theming works in Kibana, again?

This is an excellent question, thanks for asking. And the answer is,
"well, it's complicated".

We have multiples sources of "themed" styles in Kibana, ordered from
"best" to "worse":

#### 1. the EUI/JSS Theming

It was initially implemented in
elastic#117368. All react applications
and react mountpoints are supposed to be wrapped by a
`KibanaThemeProvider` that bridges core's `theme$` values to the
`EuiProvider`.


https://github.com/elastic/kibana/blob/477505a2dd86d8f103f3aef16b3b4b76dff0b27a/packages/core/theme/core-theme-browser-internal/src/core_theme_provider.tsx#L11

This one was already dynamic and just works perfectly. If
`core.theme.theme$` changes, the new values is received by the theme
provider, which automatically changes the styles accordingly, creating
this sexy "it just works" effect:


https://github.com/elastic/kibana/assets/1532934/f3e61ca7-f3ed-4c37-aa46-76ee68c1a628

If everything theme-related was using this approach, dynamic theme
reload would have been possible. However, Kibana has a lot of legacy, so
as you can imagine, it wasn't that easy.

So, **don't get false hopes** (as I did when I tried it...) from this
video. Dynamic theme swap **could not** be implemented in this PR. And
the reasons are just below.

#### 2. Per-theme css files


https://github.com/elastic/kibana/blob/6443b571642c453a9232a04297fddfb0a918c0dc/packages/core/rendering/core-rendering-server-internal/src/render_utils.ts#L40-L54

We have a bunch of dark/light variations of some css files that are
computed in the rendering service, server-side, to be injected into the
page template.

Of course, this doesn't play well with dynamic theming, given the UI
then doesn't know which css should be swapped, and which one should be
added instead.

However, porting the responsibilities of which theme css files to load
to the browser was achievable, and done in this PR. core's browser-side
`theme` provider now receives the list of theme files for both light and
dark theme (via the injected metadata), and inject them in the document
(instead of having them pre-injected in the page template by the
rendering service server-side).

So this one wasn't a blocker for dynamic theme reload.  

#### 3. Plugin styles 

This is where the problems start.

Plugins can ship their own styles too, by importing them from components
or from their entrypoint.

E.g this component


https://github.com/elastic/kibana/blob/f1dc1e1869566c1d61a08bb67eb3d4ac2f47834e/src/plugins/controls/public/control_group/component/control_group_component.tsx#L9

importing this file:


https://github.com/elastic/kibana/blob/bafb23580b18781e711575cd8c0e17a6e3f4f740/src/plugins/controls/public/control_group/control_group.scss#L107-L110

Which relies on a theme variable, `$euiFormBackgroundColor`

So how does it works under the hood? How is that
`$euiFormBackgroundColor` variable interpolated? Using which theme?

Well, technically, how the styles are effectively loaded differs
slightly between dev and production (different webpack loaders/config),
but in both cases, it depends on the `__kbnThemeTag__` variable that is
injected to the global scope by the `bootstrap.js` script.

This `__kbnThemeTag__` tag (apparently) **can** be modified after page
load. However it doesn't magically reload everything, so styles already
loaded for one theme will not reload. If a component and its imported
styles were already compiled / injected, then they won't reload

As a short video is better than 3 blocks of text, just see:


https://github.com/elastic/kibana/assets/1532934/3087ffd6-80d8-42bf-ab17-691ec408ea6f

That was the first blocker for supporting "dynamic" reloads of the
system theme.

#### 4. Static inline styles

Last but not least, we have some static style injected in the template,
that also depend on the current theme.


https://github.com/elastic/kibana/blob/6443b571642c453a9232a04297fddfb0a918c0dc/packages/core/rendering/core-rendering-server-internal/src/views/styles.tsx#L52-L54

Of course this plays very badly with dynamic theming. And worth noting,
those styles are used by the "Loading Elastic" screen, where Core (and
therefore Core's theming service) isn't loaded yet, which made the
things even more complicated.

This was the second blocker for supporting "dynamic" reloads of the
system theme.

#### 5. `euiThemeVars`

Actually TIL (not that I was asking for it)

We're exposing the EUI theme variable via the `euiThemeVars` of the
`@kbn/ui-theme` package:

E.g.


https://github.com/elastic/kibana/blob/c7e785383a87f7e18509c601a5c089c755ac028e/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx#L41


https://github.com/elastic/kibana/blob/c7e785383a87f7e18509c601a5c089c755ac028e/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx#L50

So I did my best, and made it that this export was a proxy, and that
Core's theme service was dynamically swapping the target of the proxy
depending on the system's theme...


https://github.com/elastic/kibana/blob/b0a0017811d7402f76709af7ab1b8e12334e64a5/packages/kbn-ui-theme/src/theme.ts#L30-L42

Unfortunately it doesn't fully work for dynamic system theme switch,
given modules/code can keep references of the property directly (e.g.
the snippet a few lines on top), and there's nothing to dynamically
reload components when the proxy changes.

So yet another blocker for dynamic theme switch. 


## Release Notes

Add a new option, `system`, to the `theme:darkMode` Kibana advanced
setting, that can be used to have Kibana's theme follow the system's
(light or dark)

---------

Co-authored-by: kibanamachine <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting EUI release_note:skip Skip the PR/issue when compiling release notes Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Team:Fleet Team label for Observability Data Collection Fleet team v8.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.