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

Improve to manage notifications and proposals #2046

Merged
merged 16 commits into from
Jun 28, 2024

Conversation

Silver-IT
Copy link
Member

@Silver-IT Silver-IT commented Jun 6, 2024

@Silver-IT Silver-IT requested a review from taoeffect June 6, 2024 01:24
@Silver-IT Silver-IT self-assigned this Jun 6, 2024
@Silver-IT Silver-IT linked an issue Jun 6, 2024 that may be closed by this pull request
Copy link

cypress bot commented Jun 6, 2024

Passing run #2489 ↗︎

0 114 8 0 Flakiness 0

Details:

Merge 07f200c into 5da85fd...
Project: group-income Commit: 4158dfa481 ℹ️
Status: Passed Duration: 10:08 💡
Started: Jun 28, 2024 12:34 AM Ended: Jun 28, 2024 12:44 AM

Review all test suite changes for PR #2046 ↗︎

Silver-IT and others added 2 commits June 10, 2024 19:53
* PR for release v0.4.2

* feat: save notification body as object format

---------

Co-authored-by: Greg Slepak <[email protected]>
@Silver-IT Silver-IT changed the title Manage proposal status consistently Improve to manage notifications and proposals Jun 10, 2024
Copy link
Member Author

@Silver-IT Silver-IT left a comment

Choose a reason for hiding this comment

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

Ready for the review!
Worked on two Issues in this PR, instead of working on separate PRs, in order to reduce conflicts mainly in templates.js file.

Comment on lines +1036 to +1038
validate: actionRequireActiveMember(objectOf({
proposalIds: arrayOf(string)
})),
Copy link
Member Author

Choose a reason for hiding this comment

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

To make data meaningful, I've updated the type from array to object.

@@ -1948,13 +1976,15 @@ sbp('chelonia/defineContract', {
console.warn(`removeForeignKeys: ${e.name} error thrown:`, e)
})
},
'gi.contracts/group/emitNotificationAfterSyncing': async (contractIDs, notificationName, notificationData) => {
'gi.contracts/group/emitNotificationsAfterSyncing': async (contractIDs, notifications) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

Updated function to be able to emit multiple notifications.

@@ -52,7 +52,6 @@ export const CHATROOM_MEMBER_MENTION_SPECIAL_CHAR = '@'
export const CHATROOM_CHANNEL_MENTION_SPECIAL_CHAR = '#'

// chatroom events
export const CHATROOM_MESSAGE_ACTION = 'chatroom-message-action'
Copy link
Member Author

Choose a reason for hiding this comment

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

We don't use this action anymore unless we use the contracts of old versions. I remember this action was removed in the PR #1521 where @corrideat implemented E2E Protocol.

export function swapMentionIDForDisplayname (text: string): string {
export function swapMentionIDForDisplayname (
text: string,
options: Object = { escaped: true, forChat: true }
Copy link
Member Author

Choose a reason for hiding this comment

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

Added options argument and made this function more dynamic.

escaped: this indicates that the text contains escaped characters
forChat: this indicates that the function is being used for messages inside chatroom

Copy link
Member

Choose a reason for hiding this comment

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

Please add some inline comments to the code to explain what these parameters are along with some examples of which situations you would want to use one versus the other.

...template,
avatarUserID: template.avatarUserID || sbp('state/vuex/getters').ourIdentityContractId,
Copy link
Member Author

Choose a reason for hiding this comment

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

Updated order so that avatarUserID could be overridden.

@@ -0,0 +1,205 @@
<template lang='pug'>
Copy link
Member Author

Choose a reason for hiding this comment

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

Saved this file inside history folder, because I am not sure we could use this file or content later.

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Great work @Silver-IT!

Comment on lines 238 to 244
return text.split(regEx).map(t => {
return possibleMentions.includes(t)
? t[0] === CHATROOM_MEMBER_MENTION_SPECIAL_CHAR
? forChat ? t[0] + usernameFromID(t.slice(1)) : userDisplayNameFromID(t.slice(1))
: (forChat ? t[0] : '') + getChatroomNameById(t.slice(1))
: t
}).join('')
Copy link
Member

Choose a reason for hiding this comment

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

This is written in a far more confusing manner now. Can you please add some parenthesis around the nested ?:'s to make it clearer?

export function swapMentionIDForDisplayname (text: string): string {
export function swapMentionIDForDisplayname (
text: string,
options: Object = { escaped: true, forChat: true }
Copy link
Member

Choose a reason for hiding this comment

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

Please add some inline comments to the code to explain what these parameters are along with some examples of which situations you would want to use one versus the other.

Comment on lines 27 to 40

return {
body: L("{errName} during {activity} for '{action}' from {b_}{who}{_b} to '{contract}': '{errMsg}'", {
...LTags('b'),
errName: error.name,
activity,
action: action ?? opType,
who: meta?.username ?? message.signingKeyId(),
contract: contractName(contractID),
errMsg: error.message ?? '?'
}),
body: {
key: "{errName} during {activity} for '{action}' from {b_}{who}{_b} to '{contract}': '{errMsg}'",
args: {
...LTags('b'),
errName: error.name,
activity,
action: action ?? opType,
who: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${message.signingKeyId()}`,
contract: sbp('state/vuex/state').contracts[contractID]?.type ?? contractID,
errMsg: error.message ?? '?'
}
},
Copy link
Member

Choose a reason for hiding this comment

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

All of the changes in this file must be reverted because user-facing strings must be wrapped in L functions or the strings utility will not find them.

Comment on lines 149 to 159
const args = {
name: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${data.creatorID}`,
...LTags('strong')
}
const bodyTemplateMap = {
ADD_MEMBER: () => L('{name} proposed to add a member to the group. Vote now!', { name }),
CHANGE_MINCOME: () => L('{name} proposed to change the group mincome. Vote now!', { name }),
CHANGE_DISTRIBUTION_DATE: () => L('{name} proposed to change the group distribution date. Vote now!', { name }),
CHANGE_VOTING_RULE: () => L('{name} proposed to change the group voting system. Vote now!', { name }),
REMOVE_MEMBER: () => L('{name} proposed to remove a member from the group. Vote now!', { name }),
GENERIC: () => L('{name} created a proposal. Vote now!', { name })
ADD_MEMBER: () => ({ key: '{strong_}{name}{_strong} proposed to add a member to the group. Vote now!', args }),
CHANGE_MINCOME: () => ({ key: '{strong_}{name}{_strong} proposed to change the group mincome. Vote now!', args }),
CHANGE_DISTRIBUTION_DATE: () => ({ key: '{strong_}{name}{_strong} proposed to change the group distribution date. Vote now!', args }),
CHANGE_VOTING_RULE: () => ({ key: '{strong_}{name}{_strong} proposed to change the group voting system. Vote now!', args }),
REMOVE_MEMBER: () => ({ key: '{strong_}{name}{_strong} proposed to remove a member from the group. Vote now!', args }),
GENERIC: () => ({ key: '{strong_}{name}{_strong} created a proposal. Vote now!', args })
Copy link
Member

Choose a reason for hiding this comment

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

All of the changes in this file must be reverted because user-facing strings must be wrapped in L functions or the strings utility will not find them.

Comment on lines 220 to 238
const makeBodyBasedOnCreator = (key) => {
return isMe
? { key: '{strong_}Your{_strong} ' + key, args }
: {
key: "{strong_}{name}'s{_strong} " + key,
args: { ...args, name: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${creatorID}` }
}
}
const bodyTemplateMap = {
[PROPOSAL_INVITE_MEMBER]:
(opts) => L('{creator} proposal to add {member} to the group was {closedWith}.', opts),
() => makeBodyBasedOnCreator('proposal to add {member} to the group was {strong_}{closedWith}{_strong}.'),
[PROPOSAL_REMOVE_MEMBER]:
(opts) => L('{creator} proposal to remove {member} from the group was {closedWith}.', opts),
() => makeBodyBasedOnCreator('proposal to remove {member} from the group was {strong_}{closedWith}{_strong}.'),
[PROPOSAL_GROUP_SETTING_CHANGE]:
(opts) => L('{creator} proposal to change group\'s {setting} to {value} was {closedWith}.', opts),
() => makeBodyBasedOnCreator("proposal to change group's {setting} to {value} was {strong_}{closedWith}{_strong}."),
[PROPOSAL_PROPOSAL_SETTING_CHANGE]:
(opts) => L('{creator} proposal to change group\'s {setting} was {closedWith}.', opts), // TODO: define message
() => makeBodyBasedOnCreator("proposal to change group's {setting} was {strong_}{closedWith}{_strong}."),
[PROPOSAL_GENERIC]:
(opts) => L('{creator} proposal "{title}" was {closedWith}.', opts)
}
const statusMap = {
[STATUS_PASSED]: { icon: 'check', level: 'success', closedWith: L('accepted') },
[STATUS_FAILED]: { icon: 'times', level: 'danger', closedWith: L('rejected') },
[STATUS_CANCELLED]: { icon: 'times', level: 'danger', closedWith: L('cancelled') }, // TODO: define icon, level
[STATUS_EXPIRED]: { icon: 'times', level: 'danger', closedWith: L('expired') } // TODO: define icon, level
() => makeBodyBasedOnCreator('proposal "{title}" was {strong_}{closedWith}{_strong}.')
Copy link
Member

Choose a reason for hiding this comment

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

All of the changes in this file must be reverted because user-facing strings must be wrapped in L functions or the strings utility will not find them.

Comment on lines 312 to 324
'pledger': () => ({
key: 'A new distribution period ({period}) has started. Please check Payment TODOs.',
args
}),
'receiver': () => ({
key: 'A new distribution period ({period}) has started. Please update your income details if they have changed.',
args
})
}

return {
avatarUserID: data.creatorID,
body: bodyTemplate[data.memberType],
body: bodyTemplate[data.memberType](),
Copy link
Member

Choose a reason for hiding this comment

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

All of the changes in this file must be reverted because user-facing strings must be wrapped in L functions or the strings utility will not find them.

Comment on lines 4 to 30
.c-icon(
:class='{"is-warning": isYellowHorn}'
)
.c-icon(:class='{"is-warning": isYellowHorn}')
svg-yellow-horn(v-if='isYellowHorn')
svg-horn(v-else)
template(#header='')
.c-header
span.c-title.is-title-5(:class='interactiveMessage.proposalSeverity') {{interactiveMessage.proposalStatus}}
span.has-text-1 {{ humanDate(datetime, { hour: 'numeric', minute: 'numeric' }) }}
span TODO
template(#body='')
.c-text
| {{interactiveMessage.text}}
i18n.c-link(@click='$router.push({ path: "/dashboard#proposals" })') See proposal
.c-text TODO
</template>

<script>
import { mapGetters } from 'vuex'
import { L } from '@common/common.js'
import {
PROPOSAL_GROUP_SETTING_CHANGE,
PROPOSAL_INVITE_MEMBER,
PROPOSAL_REMOVE_MEMBER,
PROPOSAL_PROPOSAL_SETTING_CHANGE,
PROPOSAL_GENERIC,
PROPOSAL_VARIANTS
} from '@model/contracts/shared/constants.js'
import { getProposalDetails } from '@model/contracts/shared/functions.js'
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I'm not sure about this. I think I made a big mistake here when I said "go for it", not fully realizing the implications of my answer, that it would result in a lot of code getting deleted.

What we had here seems very useful. We might change our mind and decide that it's still important to notify users in the #general chatroom about proposals expiring, or about other situations in the group, in order to keep the engagement in the group high.

I'm sorry to ask this of you, as it is my fault for giving you a bad answer earlier, but can we keep all of the code that was here before, but update it so that it uses the STATUS_* variables instead?

I think we should still post these interactive messages to the group chat, so that people who aren't paying attention to the notifications can still be reminded to vote on the proposals.

@Silver-IT Silver-IT marked this pull request as draft June 21, 2024 21:05
Copy link
Member Author

@Silver-IT Silver-IT left a comment

Choose a reason for hiding this comment

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

Ready for the another review!

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Really great PR @Silver-IT! I tested it and it seems to work fairly well, but I wasn't able to test everything.

For example, I wasn't able to test expiring proposals. Is there an easy way to test that?

I also noticed that chatroom messages for when proposals are created or expiring or closed are not posted to the #general chatroom. Can they be posted? It seems like something that would be useful.

frontend/model/notifications/templates.js Outdated Show resolved Hide resolved
frontend/model/notifications/templates.js Show resolved Hide resolved
frontend/model/notifications/templates.js Show resolved Hide resolved
scope: 'group'
}
},
MEMBER_LEFT (data: { groupID: string, memberID: string }) {
const name = strong(userDisplayNameFromID(data.memberID))
Copy link
Member

Choose a reason for hiding this comment

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

This function seemed useful to DRY things, why get rid of it?

return {
body: L("{errName} during {activity} for '{action}' from {b_}{who}{_b} to '{contract}': '{errMsg}'", {
...LTags('b'),
errName: error.name,
activity,
action: action ?? opType,
who: meta?.username ?? message.signingKeyId(),
contract: contractName(contractID),
who: `${CHATROOM_MEMBER_MENTION_SPECIAL_CHAR}${message.signingKeyId()}`,
Copy link
Member

Choose a reason for hiding this comment

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

Please note: the message.signingKeyId() is NOT a contractID. So you cannot use it to get the username and therefore CHATROOM_MEMBER_MENTION_SPECIAL_CHAR is also inappropriate here.

In fact it seems there's currently no simple way to figure out this information from a GIMessage, and not all GIMessages contain this information.

So I've created #2125 to address this and assigned it to @corrideat, but for now let's just remove this who variable altogether.

@Silver-IT
Copy link
Member Author

I wasn't able to test expiring proposals. Is there an easy way to test that?

To be honest, I didn't test the expiring proposals for this PR, because they are already tested before and I didn't make any changes related it.
But I remember I was testing it using manual update of system clock. 😁🙈

I also noticed that chatroom messages for when proposals are created or expiring or closed are not posted to the #general chatroom. Can they be posted? It seems like something that would be useful.

Sure, why not? I will make it work then.

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Additional minor change request here

proposal: objectMaybeOf({
proposalId: string,
proposalType: string,
expires_date_ms: number,
createdDate: string,
creatorID: string,
variant: unionOf(...Object.values(PROPOSAL_VARIANTS).map(v => literalOf(v)))
variant: unionOf([STATUS_EXPIRING].map(v => literalOf(v))) // NOTE: only expiring proposals could be notified at the moment
Copy link
Member

Choose a reason for hiding this comment

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

@Silver-IT Please feel free to add all of the types of proposals here, as we want to be able to notify the #general chatroom of anything related to proposals (being opened, closed, etc.).

Copy link
Member Author

Choose a reason for hiding this comment

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

I was planning to add all types while I would be working on Issue #2138 after this PR is merged. Do you want me to do it in this PR?

Copy link
Member

Choose a reason for hiding this comment

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

OK, we can save it for a separate PR 👍

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Approved!

@taoeffect taoeffect merged commit 41dc1f1 into master Jun 28, 2024
4 checks passed
@taoeffect taoeffect deleted the 1984-manage-proposal-status-consistently branch June 28, 2024 23:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Notification message should be saved in Object format Manage proposal status consistently
2 participants