-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
GH-1081 Auto-dismiss notification. #1355
Conversation
I think the original idea was to have the auto-dismiss be optional (with configurable delay). See #1081 We can continue the discussion in that issue? |
let adjustedCloseTimer = this.defaultAutoCloseTimeout; | ||
// Give more time before activating the autoClose Notification when user has choices | ||
if (properties.actions !== undefined && properties.actions.length > 1) { | ||
adjustedCloseTimer = adjustedCloseTimer * properties.actions.length; |
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 for reference, we discussed this offline and the conclusion is that it's better to make it auto-dismissable for certain severity level i.e info/warning and make it must-be-dismissed-manually for things like errors.
Also I don't think using the number of possible actions in the message is a good factor to use for the duration of the message, as there's probably no correlation between the number of actions a user can do on that message and the severity 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.
I overlooked your comment, as already collapsed.
That's the thing, all kind of use cases require different options. I think, it's up to the MessageService
clients to decide about timeout and other properties of messages. OTOH we can have default options for most common cases.
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.
Let's start with adding an optional timeout
property to Message
@@ -29,6 +29,8 @@ export interface Notification { | |||
|
|||
export class Notifications { | |||
|
|||
private defaultAutoCloseTimeout = 5000; | |||
private closeTimer: NodeJS.Timer; |
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.
- please do not use node types in
browser/*
. - if multiple messages are pushed,
Notifications.show
will be called that many times, but thiscloseTimer
will be reassigned with latest only. actually, closing the first message will clear last timer.
@@ -29,6 +29,8 @@ export interface Notification { | |||
|
|||
export class Notifications { | |||
|
|||
private defaultAutoCloseTimeout = 5000; |
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, it's better to use preferences for this kind of defaults.
}); | ||
} | ||
|
||
// Do not auto-close the "error" notification | ||
if (properties.icon.toLowerCase() !== 'error') { |
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.
it should be configurable via properties of a Message
as required in #1081 (comment), because the client needs to decide, if this message should be sticky or not.
4ee02bc
to
e0e62e4
Compare
I Have a question: |
@@ -28,16 +28,16 @@ export class WindowImpl implements Window { | |||
return undefined; | |||
}; | |||
if (type === MessageType.Error) { | |||
return this.messageService.error(message, ...actionTitles).then(mapActionType); | |||
return this.messageService.error(message, undefined, ...actionTitles).then(mapActionType); |
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.
we should introduce function overload to keep the less verbose API for most common use cases.
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.
Overload method added, so no need to use undefined anymore
* If we set autoDismiss: 0, then the notification preference will be ignored and notification remains until user intervention. | ||
*/ | ||
|
||
log(message: string, autoDismiss?: number | undefined, ...actions: string[]): Promise<string | undefined> { |
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.
function overload would help to add more optional message option while keeping simple API for most common use cases. for example:
log(message: string, ...actions: string[]): Promise<string | undefined>;
log(message: string, options?: MessageOptions, ...actions: string[]): Promise<string | undefined>;
log(message: string, ...args: any[]): Promise<string | undefined> {
// TODO decompose optionaly MessageOptions from args[0]
// return ...
}
clients would see these two overloads in content assist. we would need to unwrap the arguments.
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 we should add MessageOptions
to encapsulate, as we might add more of them.
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.
@AlexTugarev what about also putting actions in that MessageOptions structure?
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.
Good question. Should the options describe the message, or it's rendering/behavior.
I'd also like to improve actions
of messages, as they currently are label and value at the same time. Have a look at the resolution of resulting promises, that's what you selected.
OTOH the very first and simplest signature from above is quiet handy, and should be kept.
But do you think, we need to tackle that point now?
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 sorry I didn't see the message above where you mentionned function overloads. Yes that could work also, I just thought that if they were also options they could go in the same structure, but indeed it looks like the structure might not be appropriate for them.
export const NotificationConfigSchema: PreferenceSchema = { | ||
"type": "object", | ||
"properties": { | ||
"notification.autoDismiss": { |
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.
please rename it to something like notification.timeout
. I'm planing to revise more parts of user notifications in #1360, and I think we should distinct between dismissing and hiding a message. a timeout is more generics, which leaves more room for interpretation.
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 mention it, hiding message and keeping them in kind of an inbox are ideas from #1360. I'm aware of this PR to target the closing of messages after the timeout period elapsed.
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.
auto-dismiss rename to timeout
@@ -45,7 +45,7 @@ export class CppClientContribution extends BaseLanguageClientContribution { | |||
const ERROR_MESSAGE = "Error starting C/C++ language server. " + | |||
"Please make sure 'clangd' is installed on your system. " + | |||
"You can refer to the clangd page for instructions."; | |||
this.messageService.error(ERROR_MESSAGE, READ_INSTRUCTIONS_ACTION).then(selected => { | |||
this.messageService.error(ERROR_MESSAGE, undefined, READ_INSTRUCTIONS_ACTION).then(selected => { |
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.
you see, it's inconvenient to add this undefined
parameter all over the places.
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.
agree, no more needed to use undefined with the overload method
@@ -35,14 +41,17 @@ export class NotificationsMessageClient extends MessageClient { | |||
label: action, | |||
fn: element => onCloseFn(action) | |||
}); | |||
|
|||
const timeout = (message.timeout || 0); |
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.
if message.actions
are undefined or empty, timeout should be undefined, right?
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.
Timeout should be undefined when there is some actions defined. If no actions defined, then the user can still set a timeout to 0 (keep notification until user interaction) or any timeout set in milisec or the user preferences.
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.
you're right! the existence of actions should be the first criteria.
but I think instead of patching the timeout in NotificationsMessageClient.showMessage
and evaluating everything in Notifications.contructor
, we should compute the effective timeout here onve. considering the requirement could we calculate the timeout like this?
const timeout = (message.action)
? undefined
: (message.timeout || this.preferences['notification.timeout'])
could you update the PR, please?
}); | ||
} | ||
// Auto-dismiss is only allowed when there is only one action available. |
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.
please move the the check to showToast
, it will simplify things here.
@@ -29,6 +30,7 @@ export interface Notification { | |||
|
|||
export class Notifications { | |||
|
|||
private closeTimer: any; |
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.
it's number | undefined
if you use window.setTimeout
below.
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 should go into the scope of
createNotificationElement
- we can avoid use
any
here, as already commented
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.
Move the closeTimer to createNotificationElement
ready to be reviewed |
@@ -72,8 +74,16 @@ export class Notifications { | |||
button.addEventListener('click', () => { | |||
action.fn(handler); | |||
close(); | |||
clearTimeout(this.closeTimer); |
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.
@lmcbout here you're referencing the closeTimer
property of the Notifications
instance. this is not, what you want to say it should do, right? you want to clear the timeout of the single message, but this way each click handler will clear the timeout of the latest shown message. I think I already commented on this, but GH comments were rooted out.
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.
Craete a clearTimeout() and setTimeout, so I can use the window.setTimeout and window.cearTimeout() method
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.
No need to create the set and clear timeout method, using wndow.set instead
}); | ||
} | ||
if (properties.timeout !== undefined && properties.timeout > 0) { |
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.
minor: !!properties.timeout && ...
is shorter
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 this minor modification
@@ -72,8 +74,16 @@ export class Notifications { | |||
button.addEventListener('click', () => { |
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.
shouldn't we clear the timeout before fn
call?
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.
Should not matter, but I will mve the clearTimeout before the close() call
}); | ||
} | ||
if (properties.timeout !== undefined && properties.timeout > 0) { |
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.
you can move this if
statement up before L70 to make a local closeTimer
.
something like const closeTimer = !!properties.timeout ? window.setTimeout() : undefined
should work.
then you can use it in click handler.
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.
Move the closeTimer before and use it as a const
}); | ||
} | ||
if (properties.timeout !== undefined && properties.timeout > 0) { | ||
this.closeTimer = setTimeout(() => { | ||
if (this.closeTimer !== undefined) { |
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.
if
won't be necessary then.
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.
Removed
@@ -35,14 +37,24 @@ export class NotificationsMessageClient extends MessageClient { | |||
label: action, | |||
fn: element => onCloseFn(action) | |||
}); | |||
|
|||
// If some actions are defined, we don't set the timeout |
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.
convert to JSDoc, 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.
Using macro does not need anymore the comment
@@ -21,12 +21,17 @@ export interface Message { | |||
type: MessageType; | |||
text: string; | |||
actions?: string[]; | |||
timeout?: number; |
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.
shouldn't that be a options?: MessageOptions
property?
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.
Will be name options: MessageOptions
|
||
@injectable() | ||
export class MessageService { | ||
|
||
constructor( | ||
@inject(MessageClient) protected readonly client: MessageClient | ||
) { } | ||
/** | ||
* | ||
* MessageSevice uses some methods to report information either log, info, warn and error |
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.
please, convert to JSDoc.
/** | ||
* | ||
* MessageSevice uses some methods to report information either log, info, warn and error | ||
* One parameter is optional: @param options.timeout |
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 doc should go to MessageOptions, right?
let actionsValue: string[] | undefined; | ||
|
||
if (args && args.length > 0) { | ||
if (args[0]['timeout'] !== undefined) { |
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.
please use type guards if possible. also, I think we can assume args[0]
to be message options if type is object.
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 be nice, if you add the type guard in the namespace of MessageOptions
.
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.
In the next version, I remove theoverloa method by addibg the actions and timeout within the same API, so the typeguard are not require, the code is simplier here
} else { | ||
actionsValue = args; | ||
} | ||
return this.client.showMessage({ type: messageType, timeout: timeoutvalue, text: messageText, actions: actionsValue }); |
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.
there is no need to explode the properties of options here.
log(message: string, ...actions: string[]): Promise<string | undefined> { | ||
return this.client.showMessage({ type: MessageType.Log, text: message, actions: actions || [] }); | ||
log(message: string, ...actions: string[]): Promise<string | undefined>; | ||
log(message: string, options?: MessageOptions): Promise<string | undefined>; |
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 one actually needed? please check, if it's same as the next line.
With the commit : fd4b468 we need to use the API data structure ({message:...}) and we can add optional data as well |
@lmcbout, we discussed that kind of change before, and decided to have simple parameters to be less verbose. just compare the two examples, and consider that a lot of use cases won't require anythings but the message. also, this is not about us avoid overload methods for MessageService, but to provide a decent set of methods for API clients to cover different use cases and avoid unnecessary structures. I don't see the need to break the |
Add notifications preferences to allow auto-dismiss Auto-Dismiss can be used/disabled for each notification since it is optional When the user has more than one action available, the auto-Dismiss timeout will not be applied Signed-off-by: Jacques Bouthillier <[email protected]>
Adjusted the MessageOption API to include the optional timeout and actions within the same API. This allowed me to simplify the code and allow the end-user to enter just a message string if no actions or timeout were added. Refering to some link on the web, it seems the way I implemented the optional parameter is the right one. Also, all web sites were in favor not to use to much the overload in Typescript. |
Which sources?
Sources? That sounds a bit arbitrary for an explanation. Are there technical reasons? |
options?: MessageOptions; | ||
} | ||
|
||
export interface MessageOptions { |
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 options were meant to add more hints, so that MessageClient
s could decide on how to handle them.
please, move the actions back to Message
.
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.
should be message.actions
, not message.options!.actions
.
@lmcbout please, reintroduce the overload methods for such a change does require to adapt all caller sites, but there is no noticeable advantage. I think we should keep this convenience API for actions at the moment. also, as stated before, we can consider to revise the actions. but this should be in a different scope then #1081. let's focus on the original request. |
@AlexTugarev There are also other links talking on the subject, but stop here. |
@AlexTugarev I just created a new version for the auto-dismiss with the function overload. I think I covered all your previous comments. If there is something missing, I would be happy to get your version for the fix, I will be happy to merge it if ever you find something missing form previous version. |
@lmcbout, could you please reopen this? |
That sounds like Shlomibo tried to make a case to change overloads in TS, which was declined. I do not see a reason to stop using it, just because people wish it would be extended.
Couldn't agree more. It's the point about making the breaking change right now. |
Auto-dismiss the notification
Increase the timer when the user has choices to decide
Signed-off-by: Jacques Bouthillier [email protected]