-
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
Remove react references from core Notifications
apis
#49573
Remove react references from core Notifications
apis
#49573
Conversation
Pinging @elastic/kibana-platform (Team:Platform) |
6840ed2
to
13399f4
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
d9444ab
to
d5be874
Compare
(kibana-react) properly export reactMount
(toast API) properly export new Toast type
createNotifications: do not wrap if text
fix xpack unit tests
d5be874
to
71f530b
Compare
src/core/public/index.ts
Outdated
export { MountPoint, UnmountCallback } from './utils'; | ||
|
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.
That seems to be the first time we have types in core that are used in different services (here both Notification
and Overlay
), so I did put them in utils
. However not sure that's really the correct place. Maybe we want to create a core/public/common
or core/public/shared
? WDYT ?
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 think this is something we can expose from the Application Service, but still export them from the top-level (e.g. import { Mount } from 'src/core/public';
. Right now the app service has interfaces for AppMountParameters
and App
which have similar properties:
export interface App extends AppBase {
mount: (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise<AppUnmount>;
}
export interface AppMountParameters {
element: HTMLElement;
}
We could have a generic Mount
handler exported from there which can be inherited as well to solve these:
export type Unmount = () => void | Promise<() => void>;
export interface MountParameters {
element: HTMLElement;
}
export type Mount<T extends MountParameters = MountParameters> = (params: T) => Unmount;
// These could now be used like:
title?: string | Mount;
// And I could use them in the Application Service as well:
export interface AppMountParameters extends MountParameters {
// ...
}
export type AppMounter = Mount<AppMountParameters>;
Thoughts? cc: @joshdover
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.
Just to be sure I understand this correctly:
export interface App extends AppBase {
mount: (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise<AppUnmount>;
}
would become
export interface AppMountParameters extends MountParameters {
context: AppMountContext;
}
export type AppMounter = Mount<AppMountParameters>;
export interface App extends AppBase {
mount: AppMounter;
}
Is that correct ?
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.
Also, as these type/interfaces are going to be imported from other core/public
modules, is it the correct good practice to put them in core/public/application
and import it from here in other modules (like core/public/notifications
), or should we put it in a more 'shared' module ?
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 actually of the opinion we should hold off on doing this until we remove context in #49691. As it is right now, I don't think the examples above are actually quite compatible with the existing AppMount interface. No sense in breaking that interface twice, just to share types.
In terms of where to put the shared types now, I think a src/core/public/types
file makes sense. We have a similar file on the server-side.
dismissToast={this.props.dismissToast} | ||
toasts={this.state.toasts.map(convertToEui)} | ||
dismissToast={toast => this.props.dismissToast(toast.id)} |
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.
As we now got our own Toast
type that's different from EUI, dismiss now also accepts the toast id to avoid falsy type conversion
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 shorten with a destructure:
dismissToast={({ id }) => this.props.dismissToast(id)}
export type ToastInput = string | ToastInputFields | Promise<ToastInputFields>; | ||
export type ToastInput = string | ToastInputFields; |
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 promise<>
was unused, actually broken (somewhere in the code we object spread ToastInput
assuming that's not a promise), and made some conversion quite hard, so I removed them. (and I don't think that really makes sense, in case of async toast, the waiting can be done in the caller code)
export { CoreStart }; | ||
|
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.
Same issue as in #48431, please see here for reasons of this removal.
title, | ||
text: <>{body || null}</>, | ||
title: isString(title) ? title : reactMount(title), | ||
text: isString(body) ? body : reactMount(<>{body || null}</>), |
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.
Would work without this change, but this avoid using a MountWrapper when using actual 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.
Maybe for simplicitly we can use reactMount
for any case? If we don't, can we use standard type checking, like typeof title === 'string'
instead importing something from lodash
.
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.
Agreed, the helper function should be able to handle this just fine.
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.
Ok, will remove the useless check then.
const mount = (element: HTMLElement) => { | ||
ReactDOM.render(<I18nProvider>{component}</I18nProvider>, element); | ||
return () => ReactDOM.unmountComponentAtNode(element); | ||
}; | ||
// used for proper snapshot serialization | ||
mount.__reactMount__ = component; | ||
return mount; |
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 __reactMount__
part is only using for tests and snapshots. We could remove it for production build, as it's private 'api'. Did not found any exemple of production code removal in kibana though, even if I know that's totally doable with webpack and env conditions. WDYT ?
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 believe process.env.NODE_ENV === 'production'
should work.
const reactComponent = (toastInput as any).text; | ||
const wrapper = mountWithIntl(reactComponent); | ||
const mountPoint = (toastInput as any).text; | ||
const wrapper = mountWithIntl(mountPoint.__reactMount__); |
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 fact that we keep a reference to the react element in the mountpoint for serialization actually allows to do that. As the enzyme API is way better than manually mounting the mountpoint in tests (even if it does work), I think this trick is actually a good thing here.
💚 Build Succeeded |
@eliperelman @joshdover I think all requested changes has been made. PTAL Some points:
EDIT: Also, as PR is effectively introducing breaking API changes in some of core API's, should I label this as |
💚 Build Succeeded |
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 for transform plugin LGTM
|
||
## MountPoint type | ||
|
||
A function that will should mount DOM content inside the provided container element and return a handler to unmount it. |
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.
A function that will should mount DOM content inside the provided container element and return a handler to unmount it. | |
A function that should mount DOM content inside the provided container element and return a handler to unmount it. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
💚 Build Succeeded |
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
💔 Build Failed |
This comment has been minimized.
This comment has been minimized.
💚 Build Succeeded |
@eliperelman PTAL :) |
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.
Looks fantastic, great work!
src/core/public/utils/mount.tsx
Outdated
export const mountReactNode = (component: React.ReactNode): MountPoint => ( | ||
element: HTMLElement | ||
) => { | ||
ReactDOM.render(<I18nProvider>{component}</I18nProvider>, element); |
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: You can destructure these imports to make this a little more succinct.
import { render, unmountComponentAtNode } from 'react-dom';
// ...
render(<I18nProvider>{component}</I18nProvider>, element);
return () => unmountComponentAtNode(element);
💚 Build Succeeded |
* add reactMount util to kibana_react (kibana-react) properly export reactMount * add MountPoint types and utility * adapt toast API to no longer accept react elements (toast API) properly export new Toast type * adapt calls by using reactMount createNotifications: do not wrap if text * update generated doc * add custom snapshot serializer for reactMount * fix unit tests fix xpack unit tests * adapt non-ts calls * do not add __reactMount__ property in production * remove string check on createNotifications * fix typo and small fix using obj spread * improve react mount snapshot serializer * simplify convertToEui * rename reactMount to toMountPoint * adapt newly added calls * move mount types to proper file * use new Mount types for OverlayBanner apis * fixing typo * adapt new calls * use destructured imports
* 'master' of github.com:elastic/kibana: (27 commits) [Rollup] Fix for clone job workflow (elastic#50501) Empty message "No data available" for Labels and User metadata sections missing (elastic#49846) [APM] Duration by Country map doesn't take `transactionName` into account (elastic#50315) Remove react references from core `Notifications` apis (elastic#49573) Updated APM Indices endpoints to use the SavedObjectsClient from the legacy request context, and set the apm-indices schema object to be namspace-agnostic [Metrics UI] Calculate interval based on the dataset's period (elastic#50194) chore(NA): add new platform discovered plugins as entry points to check for dependencies on clean dll tasks (elastic#50610) [Telemetry] change of optin status telemetry (elastic#50158) [SIEM][Detection Engine] REST API Additions (elastic#50514) [DOCS] Removes dashboard-only mode doc (elastic#50441) [Filters] Fix operator overflowing out popover (elastic#50030) Change telemetry optIn to default to true (elastic#50490) [Maps] make grid rectangles the default symbolization for geo grid source (elastic#50169) Allow registered applications to hide Kibana chrome (elastic#49795) Upgrade EUI to v14.9.0 (elastic#49678) [Metrics UI] Convert layouts to use React components (elastic#49134) [Search service] Add support for ES request preference (elastic#49424) [Newsfeed/Lint] fix chained fn lint (elastic#50515) [Monitoring] Fix logstash pipelines page in multi-cluster environment (elastic#50166) [SIEM] Events viewer fixes (elastic#50175) ...
…#50616) * Remove react references from core `Notifications` apis (#49573) * add reactMount util to kibana_react (kibana-react) properly export reactMount * add MountPoint types and utility * adapt toast API to no longer accept react elements (toast API) properly export new Toast type * adapt calls by using reactMount createNotifications: do not wrap if text * update generated doc * add custom snapshot serializer for reactMount * fix unit tests fix xpack unit tests * adapt non-ts calls * do not add __reactMount__ property in production * remove string check on createNotifications * fix typo and small fix using obj spread * improve react mount snapshot serializer * simplify convertToEui * rename reactMount to toMountPoint * adapt newly added calls * move mount types to proper file * use new Mount types for OverlayBanner apis * fixing typo * adapt new calls * use destructured imports * adapt call in 7.x
💚 Build Succeeded |
Summary
Refactors core Notifications / Toasts apis to be framework agnostic.
Similar to #48431, but for the
Notification
core API (which only containsToast
ATM)PR does the following:
ToastsApi
to no longer accept react components, and instead use the newly introducedMountPoint
toMountPoint
inkibana_react
to convert from react node toMountPoint
toMountPoint
custom serializer for toMountPoint
Enzyme provides a proper snapshot serializer for react elements. As we switched from react elements to
MountPoint
, which are functions, that made the snapshot comparison kinda bogus, as everyMountPoint
serialize to[function ()]
. So I added a custom serializer for react MountPoints, that render to something way more readable and diff-able.for exemple when testing call snapshot to the Toast API, we had that kind of snapshots:
Without the custom formatter, that converted to
With the formatter, that converts to
As react is the main framework used in kibana, most 'agnostic' calls are going to be done using the
toMountPoint
utility, so it really felt like this was very important for testability and readability.Fixes #36425
Common bits with #48431
As both this and #48431 are not merged yet, the base code for
MountPoint
incore
,reactMount
inkibana-react
and their utilities are present on both PR. I did not rebase on on the other as we don't know which one will be merged first.Checklist
Use
strikethroughsto remove checklist items you don't feel are applicable to this PR.- [ ] This was checked for cross-browser compatibility, including a check against IE11- [ ] Any text added follows EUI's writing guidelines, uses sentence case text and includes i18n support- [ ] This was checked for keyboard-only and screenreader accessibilityFor maintainers
Dev Docs
The core
NotificationService
andToastsApi
methods are now framework agnostic and no longer accept react components as input. Please usekibana_react
'stoMountPoint
utility to convert a react node to a mountPoint.