-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Introduce alert snackbar UI #4244
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
Conversation
3da73d9 to
655ba3a
Compare
stephanwlee
left a comment
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.
This PR introduces the concept of error "details", a string which
is defined in a TS module that will be surfaced in the UI.
This did not sufficiently describe your design choice; it looks like the change is recommending putting what should be localized in the action payload which kind of means that, for instance, there is a network error or whatever, you would be firing an action, presumably synchronously. That is conceptually regarding an action as a mutation/command, not event.
Are you willfully making the exception here? Does it need to be be done that way?
| import {MatIconModule} from './mat_icon_module'; | ||
| import {PluginsModule} from './plugins/plugins_module'; | ||
| import {ROOT_REDUCERS, loggerMetaReducerFactory} from './reducer_config'; | ||
| import {ReloaderModule} from './reloader/reloader_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.
Is this an unused import cleanup?
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.
Yes. Although not related to the change, it is a small drive-by that fixes a lint warning upon sync, since this file is changed.
655ba3a to
0c2c195
Compare
|
In the followup, #4245, this approach requires us to include 'canCreateNewPins' and 'wasPinned' to the cardPinStateToggled action payload. Overall, my impression of this approach is that it's slightly more complex than it might deserve to be. I would be fine with this approach, or doing the simpler composite-action (noting that it would be an exception to our best practice. Also open to any other ideas, if we have any. |
| package(default_visibility = ["//tensorboard:internal"]) | ||
|
|
||
| tf_ts_library( | ||
| name = "actions", |
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.
make sure this is only usable by the effects.
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.
Visibility reduced to alerts/.
tensorboard/webapp/alert/types.ts
Outdated
| * An alert structure used when creating newly reported alerts. | ||
| */ | ||
| export interface AlertReport { | ||
| details: 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.
I do not think this is sufficient to migrate existing code. We will need to have an optional actionType for an alert that can have actions (you may also want to have localized string for those actions).
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 would like to add further fields such as actionType in a separate PR, and keep this one scoped to things that will be used in the followup. Existing embedders will also want the snackbar to have an 'x' close button, which I believe may require upgrading the snackbar.open to .openFromComponent.
tensorboard/webapp/alert/types.ts
Outdated
| * An alert structure used when creating newly reported alerts. | ||
| */ | ||
| export interface AlertReport { | ||
| details: 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.
details -> localizedMessage?
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.
Done.
tensorboard/webapp/reducer_config.ts
Outdated
| // return !isDevMode() | ||
| // ? (reducer) => (state, action) => { | ||
| // return reducer(state, action); | ||
| // } | ||
| // : logger; |
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.
revert please.
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.
Whoops, done.
| <main #main> | ||
| <router-outlet></router-outlet> | ||
| </main> | ||
| <alert-snackbar></alert-snackbar> |
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, this is not great for composability but I have no good recommendation here.
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. Ideally these below (hash-storage, page-title, settings-polymer-interop) might also be added in some other way. I haven't thought of a way I like yet, but we could imagine that a more composable approach could be applied to all of them at once in the future.
| store = TestBed.inject<Store<State>>(Store) as MockStore<State>; | ||
| }); | ||
|
|
||
| it('should open the snackbar on each alert', () => { |
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.
omit the world "should" in test; it is redundant and not needed.
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.
Done.
stephanwlee
left a comment
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 don't think we immediately in this PR require the actionable snackbar so I will be okay if you want to do a follow up.
| * An alert structure used when creating newly reported alerts. | ||
| */ | ||
| export interface AlertReport { | ||
| localizedMessage: 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.
I may have left a comment in the other PR but I don't think I find any comment.
How would you plan on supporting different actions in the snackbar? i.e., in some cases, we want to have button to retry sending a request. In order to replace our current error handling, we would need that facility. Without it, we will have two ways to achieve the same thing.
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'd like to do this in a separate PR, if that works for you. This is what I had in mind:
extend AlertReport with a new field:
export interface AlertReport {
localizedMessage: string;
retryCallback?: (store: Store) => void;
}
used like so:
AlertActionModule.registerActionAlerts([
{
localizedMessage: "Fetch failed",
retryCallback: (store) => {
// ...
store.dispatch(fetchAction(...));
}
},
])
with the AlertSnackbarContainer using .openFromComponent, instead of .open,
to open an AlertSnackbarComponent, which contains:
// template
<div *ngIf="!!alertInfo.retryCallback" (click)="onRetryClicked">Retry</div>
// component
@Input() alertInfo: AlertInfo
onRetryClicked() { this.alertInfo.retryCallback(); }
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 wish it to be more generic than just retryCallback (and it, I don't think should be a callback since it opens the API for just about anything) but I will look forward to seeing the change afterwards.
| tap((alert) => { | ||
| this.showAlert(alert!.localizedMessage); | ||
| }) |
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 do this in the subscribe instead.
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.
Done.
psybuzz
left a comment
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.
Thanks for the review!
| * An alert structure used when creating newly reported alerts. | ||
| */ | ||
| export interface AlertReport { | ||
| localizedMessage: 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.
I'd like to do this in a separate PR, if that works for you. This is what I had in mind:
extend AlertReport with a new field:
export interface AlertReport {
localizedMessage: string;
retryCallback?: (store: Store) => void;
}
used like so:
AlertActionModule.registerActionAlerts([
{
localizedMessage: "Fetch failed",
retryCallback: (store) => {
// ...
store.dispatch(fetchAction(...));
}
},
])
with the AlertSnackbarContainer using .openFromComponent, instead of .open,
to open an AlertSnackbarComponent, which contains:
// template
<div *ngIf="!!alertInfo.retryCallback" (click)="onRetryClicked">Retry</div>
// component
@Input() alertInfo: AlertInfo
onRetryClicked() { this.alertInfo.retryCallback(); }
| tap((alert) => { | ||
| this.showAlert(alert!.localizedMessage); | ||
| }) |
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.
Done.
| * An alert structure used when creating newly reported alerts. | ||
| */ | ||
| export interface AlertReport { | ||
| localizedMessage: 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.
I wish it to be more generic than just retryCallback (and it, I don't think should be a callback since it opens the API for just about anything) but I will look forward to seeing the change afterwards.
Diffbase: #4221
Followup: #4245
Introduces an alert snackbar UI, which surfaces application
errors to the user in the corner of the screen.
Technical design discussion:
On the surface, there are many ways to architect an alert/error
system in ngrx, where our goal is to allow any feature in TB or
a TB embedder to trigger alerts in the snackbar UI.
Creating an 'Alert' feature store, whose reducer listens to other
features' actions would work for TB, but not embedders, since
TB core does not have access to ngrx actions from external
features.
One way to workaround this composability problem is to force
embedders to override the Alerts module and
composeReducers.Upon receiving an embedder-specific error, the embedder's
Alerts module can set the latest error accordingly. However,
this approach assumes that a reducer can determine whether
an action should produce an alert or not, based on the action's
payload.
In the followup, the 'cardPinStateToggled' action provides a
counterexample: it should only trigger an alert if there are too
many current pinned cards.
One way to workaround this is to introduce AlertEffects as a
layer that listens to actions from all features, and converts them
into generic 'alertReported' actions (assuming effects can be
composed, similar to reducers). However, this leads us to a world
where AlertModule depends heavily on other features. For
example, if AlertModule depends on feature F, it becomes
difficult to create a TB application / embedder that needs Alerts
without needing F. This is a design constraint we'd like to follow,
as it gives flexibility in bundling TB with only a subset of the
current features.
One might imagine that specific features can do the translation
themselves, using specific effects to convert actions into
'alertReported' actions. However, this synchronous dispatch of
composite actions goes against a best practice. In other
scenarios, it is known to lead to hard-to-reason-about in-between
states, and makes undoing or reverting state changes difficult
due to the processing of multiple actions.
Thus, we finally land on the existing patch. Essentially, it hides
away the composite action using Angular's DI framework. The
actions that should trigger alerts are registered once, and are
dispatched by the AlertEffects, rather than specific features.
Googlers, see test sync cl/338344258