-
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
[toasts] migrate toastNotifications to the new platform #21772
Conversation
About the name, this might cause confusion with the notifications service that Chris E. added (#19236). We could resolve it by renaming that one (maybe to something like |
19b7178
to
c4d9b86
Compare
9162dd8
to
c22b828
Compare
This comment has been minimized.
This comment has been minimized.
c22b828
to
6993cfd
Compare
This comment has been minimized.
This comment has been minimized.
6993cfd
to
5ce3b58
Compare
💚 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, I've tested this PR with the testbed snipped for success, warning and danger toasts - everything worked as expected.
@@ -52,6 +53,18 @@ jest.mock('./fatal_errors', () => ({ | |||
FatalErrorsService: MockFatalErrorsService, | |||
})); | |||
|
|||
const mockNotificationStartContract = {}; |
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.
note: I'm experimenting with mocking right now, trying to find the ideal way to mock modules :) There is also another alternative I use, it's not better, just wanted to share:
jest.mock('./notifications');
......
import { NotificationsService } from './notifications';
const MockNotificationsService = NotificationsService as jest.Mock<NotificationsService>;
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.
Yeah, I've played with options similar to that, and functions that use generics and require()
to do the same thing, but ultimately found this to be relatively elegant. Definitely think we could improve it still.
}); | ||
|
||
expect(mockFatalErrorInit).toHaveBeenCalledTimes(1); | ||
expect(mockFatalErrorInit).toHaveBeenCalledWith(fatalErrorsStartContract); | ||
}); | ||
|
||
it('passes toasts service to ui/notify/fatal_errors', () => { |
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.
typo: ui/notify/fatal_errors
---> ui/notify/toasts
.
export let toastNotifications: ToastNotifications; | ||
|
||
export function __newPlatformInit__(toasts: ToastsStartContract) { | ||
toastNotifications = new ToastNotifications(toasts); |
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.
question: in other services you throw if __newPlatformInit__
is called twice, is there any reason to not do the same here as well?
|
||
private onChangeCallback?: () => void; | ||
|
||
constructor(private toasts: ToastsStartContract) { |
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: private readonly toasts: ...
?
@@ -18,7 +18,7 @@ | |||
*/ |
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.
question: why do need this separate .d.ts
file when we have toast_notifications.ts
? Can we get rid of 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.
Forgot to delete when I converted the stub service to TS
<EuiGlobalToastList | ||
toasts={this.state.toasts} | ||
dismissToast={this.props.dismissToast} | ||
toastLifeTimeMs={6000} |
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.
question: why don't you want to make it configurable as it was before? I'd agree that it will help us to have consistent look and feel across Kibana, but I see APM uses 5000
with EuiGlobalToastList
for some reason. Maybe we can move this magic number to a constant closer to the top of this file at least?
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.
Technically, the original implementation is hard-coded too, it's just not as obvious. I chose to hard-code this value to prevent toasts from behaving differently throughout Kibana, and to prevent people from loading them with too much content (allowing longer lifetimes allows for longer content).
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.
Hmm, didn't realize that APM had its own implementation of the global notifications list... I assume I can remove those and they can use the notifications from the new platform...
@sqren now that the toast notifications are available from the new platform you won't have to use angular to use them, and I won't have to worry that your global toast list will conflict with the new platform's global toast list, would you mind if I migrated APM to use that rather than its own instance?
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.
@spalger APM is using the EUI component so no Angular. What are the advantages to having this in the platform instead of using EUI directly?
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.
Oh, are you saying that there might be multiple EuiGlobalToastList
on the page, because the platform also adds one?
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.
Exactly, the new platform will now be adding the <EuiGlobalToastList/>
to every page, not just the Angular ones.
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.
Since APM isn't using our notification APIs, and there isn't anything that will cause double notifications today, I'm going to go ahead with this as-is and work on #21948 after this is merged.
import { GlobalToastList } from './global_toast_list'; | ||
|
||
function render(props: Partial<GlobalToastList['props']> = {}) { | ||
return <GlobalToastList dismissToast={jest.fn()} toasts$={Rx.empty()} {...props} />; |
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.
expect(shallow(render())).toMatchSnapshot(); | ||
}); | ||
|
||
it('subscribes to toasts$ on mount and unsubs on unmount', () => { |
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.
typo: unsubs
---> unsubscribes
? Or just subs to ..... and unsubs ....
to keep symmetry 🤣
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.
lol 😝
}).not.toThrowError(); | ||
}); | ||
|
||
it('emptys the content of the targetDomElement', () => { |
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.
typo: empties
?
Anybody have any thoughts on the naming similarity with the other notifications service? |
@cjcenizal sorry, I lost your earlier comment in the shuffle while I tried to figure out what was happening on CI. Ultimately, I would rather not replace the I like this strategy because it matches the way that people are going to be consuming these services, and reflects the directory structure where this service can be found, which I think is really valuable for casual observers or new-comers to the project. site note: I kind of wish it also signaled that we're referring to the browser version of the service, but that is likely relevant from context in most situations... I think if we get in the habit of referring to the core/new-platform services as All that said, I give my vote to @epixa because he has probably thought this through already and his reasoning likely makes more sense than mine. |
Thanks for sharing your reasoning @spalger, I think all of that makes sense! When we migrate over the X-Pack notifications I think we'll need to change the name of that module or else conversations about "Kibana notifications" vs "X-Pack notifications" will just become confusing. Though I don't think that should affect this PR at all. |
💚 Build Succeeded |
…21892) While debugging failures I saw in #21772 I found myself encountering failure messages like `Error: expected undefined to sort of equal true`, and other more cryptic errors caused by methods like `PageObjects.dashboard.saveDashboard()` not ensuring that the dashboard was actually saved before resolving. As part of the debugging effort I noticed that the `saveDashboard()` method does have some awareness of the success condition, but rather than asserting success within the method it returns a success boolean for the caller to check, which was only being done in a handful of tests in `test/functional/apps/dashboard/_dashboard_time.js` but was ignored the vast majority of the time. I think that most of the time we are calling `PageObjects.dashboard.saveDashboard()` we correctly assume that if the dashboard couldn't be saved for some reason the promise will be rejected and the test would fail. If the method was called `maybeSaveDashboard()` or `tryToSaveDashboard()` there might be a signal to consumers that they should check for success conditions, but that would also lead to the same checks all over the place. Instead, this PR reverses the responsibility of checking for success so that code calling `PageObjects.dashboard.saveDashboard()` can continue to assume that if something went wrong their test will fail. It also improves the error message by not using `expect(boolean).to.equal(boolean)`, instead implementing a basic `if()` statement and throwing an error with a meaningful message when something goes wrong. ```js const isDashboardSaved = await testSubjects.exists('saveDashboardSuccess'); expect(isDashboardSaved).to.eql(true); ``` is now ```js if (!await testSubjects.exists('saveDashboardSuccess')) { throw new Error('Expected to find "saveDashboardSuccess" toast after saving dashboard'); } ``` --- I think this type of change could be made to a lot of methods, and would make failures a lot easier to debug and possibly a lot less flaky if we were checking for success conditions in nearly every method we put in our PageObjects. I think it's safe to say that most of the methods we have in PageObjects do not check for actual success criteria, and sometimes that's okay: a method called `clickButton()` can safely resolve once the click method has been called, but a method like `addSampleDataSet()` should be verifying that the sample data set it set out to add was actually added.
…lastic#21892) While debugging failures I saw in elastic#21772 I found myself encountering failure messages like `Error: expected undefined to sort of equal true`, and other more cryptic errors caused by methods like `PageObjects.dashboard.saveDashboard()` not ensuring that the dashboard was actually saved before resolving. As part of the debugging effort I noticed that the `saveDashboard()` method does have some awareness of the success condition, but rather than asserting success within the method it returns a success boolean for the caller to check, which was only being done in a handful of tests in `test/functional/apps/dashboard/_dashboard_time.js` but was ignored the vast majority of the time. I think that most of the time we are calling `PageObjects.dashboard.saveDashboard()` we correctly assume that if the dashboard couldn't be saved for some reason the promise will be rejected and the test would fail. If the method was called `maybeSaveDashboard()` or `tryToSaveDashboard()` there might be a signal to consumers that they should check for success conditions, but that would also lead to the same checks all over the place. Instead, this PR reverses the responsibility of checking for success so that code calling `PageObjects.dashboard.saveDashboard()` can continue to assume that if something went wrong their test will fail. It also improves the error message by not using `expect(boolean).to.equal(boolean)`, instead implementing a basic `if()` statement and throwing an error with a meaningful message when something goes wrong. ```js const isDashboardSaved = await testSubjects.exists('saveDashboardSuccess'); expect(isDashboardSaved).to.eql(true); ``` is now ```js if (!await testSubjects.exists('saveDashboardSuccess')) { throw new Error('Expected to find "saveDashboardSuccess" toast after saving dashboard'); } ``` --- I think this type of change could be made to a lot of methods, and would make failures a lot easier to debug and possibly a lot less flaky if we were checking for success conditions in nearly every method we put in our PageObjects. I think it's safe to say that most of the methods we have in PageObjects do not check for actual success criteria, and sometimes that's okay: a method called `clickButton()` can safely resolve once the click method has been called, but a method like `addSampleDataSet()` should be verifying that the sample data set it set out to add was actually added.
…lastic#21892) While debugging failures I saw in elastic#21772 I found myself encountering failure messages like `Error: expected undefined to sort of equal true`, and other more cryptic errors caused by methods like `PageObjects.dashboard.saveDashboard()` not ensuring that the dashboard was actually saved before resolving. As part of the debugging effort I noticed that the `saveDashboard()` method does have some awareness of the success condition, but rather than asserting success within the method it returns a success boolean for the caller to check, which was only being done in a handful of tests in `test/functional/apps/dashboard/_dashboard_time.js` but was ignored the vast majority of the time. I think that most of the time we are calling `PageObjects.dashboard.saveDashboard()` we correctly assume that if the dashboard couldn't be saved for some reason the promise will be rejected and the test would fail. If the method was called `maybeSaveDashboard()` or `tryToSaveDashboard()` there might be a signal to consumers that they should check for success conditions, but that would also lead to the same checks all over the place. Instead, this PR reverses the responsibility of checking for success so that code calling `PageObjects.dashboard.saveDashboard()` can continue to assume that if something went wrong their test will fail. It also improves the error message by not using `expect(boolean).to.equal(boolean)`, instead implementing a basic `if()` statement and throwing an error with a meaningful message when something goes wrong. ```js const isDashboardSaved = await testSubjects.exists('saveDashboardSuccess'); expect(isDashboardSaved).to.eql(true); ``` is now ```js if (!await testSubjects.exists('saveDashboardSuccess')) { throw new Error('Expected to find "saveDashboardSuccess" toast after saving dashboard'); } ``` --- I think this type of change could be made to a lot of methods, and would make failures a lot easier to debug and possibly a lot less flaky if we were checking for success conditions in nearly every method we put in our PageObjects. I think it's safe to say that most of the methods we have in PageObjects do not check for actual success criteria, and sometimes that's okay: a method called `clickButton()` can safely resolve once the click method has been called, but a method like `addSampleDataSet()` should be verifying that the sample data set it set out to add was actually added.
Fixes elastic#20698 As part of the transition of APIs necessary for migrating the Chrome to the new platform, this moves the core logic for the toastNotifications out of `ui/notify` and into the new platform as the `core.notifications.toasts` service. I chose to use the `notifications` namespace here as I plan for the [`banners` service](https://github.com/elastic/kibana/blob/494c267cd97ba0c48c2acd10a8fff2c27defd0bb/src/ui/public/notify/banners/banners.js) from `ui/notify` to eventually live at `core.notifications.banners`. If you disagree with this strategy and would prefer that we use something like `core.toastNotifications` let me know. For the most part this service just does the same thing as the ui service did, so functionality should be exactly the same. To test the notifications I suggest using the testbed like so: https://gist.github.com/spalger/81097177c88dee142700fab25de88932
6.5/6.x: 27bc4d2 |
Fixes #20698
As part of the transition of APIs necessary for migrating the Chrome to the new platform, this moves the core logic for the toastNotifications out of
ui/notify
and into the new platform as thecore.notifications.toasts
service. I chose to use thenotifications
namespace here as I plan for thebanners
service fromui/notify
to eventually live atcore.notifications.banners
. If you disagree with this strategy and would prefer that we use something likecore.toastNotifications
let me know.For the most part this service just does the same thing as the ui service did, so functionality should be exactly the same. To test the notifications I suggest using the testbed like so: https://gist.github.com/spalger/81097177c88dee142700fab25de88932