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

Fix(Issue#202): counts in one request #225

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

jufab
Copy link
Contributor

@jufab jufab commented Oct 26, 2023

Summary

I made a counter resource and used it for display counter on the team sidebar.

Ticket Link

Fixes #202

@jufab jufab requested a review from larkox as a code owner October 26, 2023 00:49
@jufab jufab changed the title Issue 202 Fix(Issue#202): counts in one request Oct 26, 2023
@larkox larkox requested a review from mickmister October 26, 2023 13:29
@mickmister mickmister added 2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester labels Oct 27, 2023
Copy link
Contributor

@mickmister mickmister left a comment

Choose a reason for hiding this comment

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

Thanks for this improvement @jufab 👍

I have a few comments and one request to minimize the fetching of lists from the frontend

webapp/src/actions.js Outdated Show resolved Hide resolved
webapp/src/components/sidebar_buttons/sidebar_buttons.jsx Outdated Show resolved Hide resolved
webapp/src/index.js Outdated Show resolved Hide resolved
webapp/src/index.js Outdated Show resolved Hide resolved
webapp/src/index.js Outdated Show resolved Hide resolved
server/plugin.go Outdated
Comment on lines 358 to 359
p.API.LogError("Unable marhsal count issue list to json err=" + err.Error())
p.handleErrorWithCode(w, http.StatusInternalServerError, "Unable marhsal count issue list to json", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: to de-duplicate some string creation here. Same with the block above this one

Suggested change
p.API.LogError("Unable marhsal count issue list to json err=" + err.Error())
p.handleErrorWithCode(w, http.StatusInternalServerError, "Unable marhsal count issue list to json", err)
msg := "Unable marshal count issue list to json"
p.API.LogError(msg, "err", err.Error())
p.handleErrorWithCode(w, http.StatusInternalServerError, msg, err)

Copy link
Contributor

Choose a reason for hiding this comment

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

I see now that this is copied from the function above. I'm still thinking we can make this change here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just modified all the error messages the same way

Copy link
Contributor

Choose a reason for hiding this comment

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

@mickmister What are your opinions on moving the log statement inside the "handleErrorWithCode" function instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

@raghavaggarwal2308 That sounds fine but we would want to change every call to handleErrorWithCode as well to avoid double logging. And that's definitely out of scope of this PR

server/plugin.go Outdated
Comment on lines 366 to 368
}

}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: extra newline at the end of this function and listStore.CountIssues. This is something we should have the linter configured to enforce, but I'm not sure if we have this particular convention enforced anywhere

server/store.go Outdated
Comment on lines 344 to 348
return &CountIssue{
In: len(inList),
My: len(myList),
Out: len(outList),
}, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Might as well just return the actual lists right? Then the frontend doesn't need to do any other requests

server/plugin.go Outdated Show resolved Hide resolved
webapp/src/actions.js Outdated Show resolved Hide resolved
@raghavaggarwal2308
Copy link
Contributor

@jufab Just added a few minor comments, other than that LGTM. You still need to fix the comments provided by @mickmister

@jufab
Copy link
Contributor Author

jufab commented Nov 2, 2023

Sorry for that but I revised my MR proposal with this single request version for counters and display

Copy link
Contributor

@mickmister mickmister 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 on this @jufab. Thanks for addressing the changes 👍

I have a few more requests on this. There are some conventions that have changed on the frontend to cater towards the new data being transferred, though I think previous component props had a nice separation of concerns. Please let me know your thoughts @jufab

server/issue.go Outdated
Comment on lines 34 to 39
// CountIssue for all counter issues
type CountIssue struct {
In int `json:"in"`
My int `json:"my"`
Out int `json:"out"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't used anywhere anymore so we should remove this type definition

Suggested change
// CountIssue for all counter issues
type CountIssue struct {
In int `json:"in"`
My int `json:"my"`
Out int `json:"out"`
}

server/plugin.go Outdated
@@ -116,6 +118,7 @@ func (p *Plugin) initializeAPI() {

p.router.HandleFunc("/add", p.checkAuth(p.handleAdd)).Methods(http.MethodPost)
p.router.HandleFunc("/list", p.checkAuth(p.handleList)).Methods(http.MethodGet)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this endpoint anymore? Or are we still calling this in some cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No need so i delete it but getIssueList is used by command

Comment on lines +174 to +176
msg := "Unable to get telemetry payload from JSON"
p.API.LogError(msg, "err", err.Error())
p.handleErrorWithCode(w, http.StatusBadRequest, msg, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry I was unclear about my request before. We shouldn't edit the log statements in functions unrelated to this PR's purpose. We can clean this up in a separate effort/PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry too. should I put everything back the way it was before?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes everything that is not related to the main feature/effort of the PR

Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we do change the logging back the way it was for places unrelated the purpose of the PR, but I don't want to drag this PR on for that reason

@@ -15,6 +15,8 @@ export const REMOVE_ASSIGNEE = pluginId + '_remove_assignee';
export const GET_ISSUES = pluginId + '_get_issues';
export const GET_OUT_ISSUES = pluginId + '_get_out_issues';
export const GET_IN_ISSUES = pluginId + '_get_in_issues';
export const GET_COUNT_ISSUES = pluginId + '_get_count_issues';
Copy link
Contributor

Choose a reason for hiding this comment

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

This will also be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -174,6 +175,25 @@ export const list = (reminder = false, listName = 'my') => async (dispatch, getS
return {data};
};

export const fetchAllIssue = () => async (dispatch, getState) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
export const fetchAllIssue = () => async (dispatch, getState) => {
export const fetchAllIssueLists = () => async (dispatch, getState) => {

issues: state['plugins-com.mattermost.plugin-todo'].issues,
inIssues: state['plugins-com.mattermost.plugin-todo'].inIssues,
outIssues: state['plugins-com.mattermost.plugin-todo'].outIssues,
allIssues: state['plugins-com.mattermost.plugin-todo'].allIssues,
Copy link
Contributor

@mickmister mickmister Nov 7, 2023

Choose a reason for hiding this comment

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

I think it makes sense to keep the convention of feeding the component the issues the way we were doing before:

function mapStateToProps(state) {
    const allIssues = getAllIssues(state); // from selectors.js
    return {
        myIssues: allIssues.my, // note that I changed the prop to be `myIssues`
        inIssues: allIssues.in,
        outIssues: allIssues.out,

Copy link
Contributor

Choose a reason for hiding this comment

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

Though as mentioned elsewhere, we can use individual selectors for the in issues. The main reason I'm thinking this, is it simplifies what the component needs to worry about

@@ -113,6 +114,15 @@ const outIssues = (state = [], action) => {
}
};

const allIssues = (state = {}, action) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's handle the undefined state issue here instead of elsewhere

Suggested change
const allIssues = (state = {}, action) => {
const allIssuesInitialState = {
my: [],
in: [],
out: [],
};
const allIssues = (state = allIssuesInitialState, action) => {


import SidebarRight from './sidebar_right.jsx';

function mapStateToProps(state) {
return {
todos: getIssues(state),
allIssues: state['plugins-com.mattermost.plugin-todo'].allIssues,
Copy link
Contributor

Choose a reason for hiding this comment

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

We should use the selectors whenever possible. Reading the previous code, I think we can continue using the getInIssues etc. selectors to make the consumer code less complicated. The selector would call the getAllIssues selector and return allIssues.in

Copy link
Contributor

Choose a reason for hiding this comment

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

I also like the convention of myTodos rather than just todos

Comment on lines 69 to 70
const refresh = () => {
store.dispatch(fetchAllIssue());
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have the websocket message that gets sent from the server contain the lists, rather than fetching them here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's after

registry.registerWebSocketEventHandler(`custom_${pluginId}_refresh`, refresh);

Copy link
Contributor

@mickmister mickmister Nov 9, 2023

Choose a reason for hiding this comment

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

I'm not sure what this means. Are you saying it's not possible to deliver the data in the websocket message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This line is the definition of the function that is then executed in the websocket.

Comment on lines 27 to 29
export const getIssues = (state) => getPluginState(state).issues;
export const getInIssues = (state) => getPluginState(state).inIssues;
export const getOutIssues = (state) => getPluginState(state).outIssues;
Copy link
Contributor

Choose a reason for hiding this comment

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

These selectors should probably be doing this now:

Suggested change
export const getIssues = (state) => getPluginState(state).issues;
export const getInIssues = (state) => getPluginState(state).inIssues;
export const getOutIssues = (state) => getPluginState(state).outIssues;
export const getIssues = (state) => getAllIssues(state).my;
export const getInIssues = (state) => getAllIssues(state).in;
export const getOutIssues = (state) => getAllIssues(state).out;

@codecov-commenter
Copy link

codecov-commenter commented Nov 7, 2023

Codecov Report

Attention: 94 lines in your changes are missing coverage. Please review.

Comparison is base (d1f1d09) 6.42% compared to head (2d27842) 6.30%.

❗ Current head 2d27842 differs from pull request most recent head 99b959c. Consider uploading reports for the commit 99b959c to get more accurate results

Additional details and impacted files
@@            Coverage Diff            @@
##           master    #225      +/-   ##
=========================================
- Coverage    6.42%   6.30%   -0.13%     
=========================================
  Files          11      11              
  Lines        1712    1746      +34     
=========================================
  Hits          110     110              
- Misses       1594    1628      +34     
  Partials        8       8              
Files Coverage Δ
server/issue.go 0.00% <ø> (ø)
server/list.go 0.00% <0.00%> (ø)
server/plugin.go 0.00% <0.00%> (ø)

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jufab
Copy link
Contributor Author

jufab commented Nov 7, 2023

I think I resolved all comment 😄

@mickmister mickmister self-requested a review November 9, 2023 04:58
Copy link
Contributor

@mickmister mickmister left a comment

Choose a reason for hiding this comment

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

Thanks @jufab! I have just a few more suggestions

export const getIssues = (state) => getPluginState(state).issues;
export const getInIssues = (state) => getPluginState(state).inIssues;
export const getOutIssues = (state) => getPluginState(state).outIssues;
export const getIssues = (state) => getAllIssues(state).my;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rename this to getMyIssues?

Suggested change
export const getIssues = (state) => getAllIssues(state).my;
export const getMyIssues = (state) => getAllIssues(state).my;

if err != nil {
msg := "Unable to get issues for user"
p.API.LogError(msg, "err", err.Error())
p.handleErrorWithCode(w, http.StatusInternalServerError, msg, err)
return
}

if len(issues) > 0 && r.URL.Query().Get("reminder") == "true" && p.getReminderPreference(userID) {
if allListIssue != nil && len(allListIssue.My) > 0 && r.URL.Query().Get("reminder") == "true" && p.getReminderPreference(userID) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting. I think we are changing the logic here, but I think we are actually fixing a bug. LGTM 👍

@@ -329,7 +319,7 @@ func (p *Plugin) handleList(w http.ResponseWriter, r *http.Request) {
nt := time.Unix(now/1000, 0).In(timezone)
lt := time.Unix(lastReminderAt/1000, 0).In(timezone)
if nt.Sub(lt).Hours() >= 1 && (nt.Day() != lt.Day() || nt.Month() != lt.Month() || nt.Year() != lt.Year()) {
p.PostBotDM(userID, "Daily Reminder:\n\n"+issuesListToString(issues))
p.PostBotDM(userID, "Daily Reminder:\n\n"+issuesListToString(allListIssue.My))
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah this is a good bug fix here too. Nice work 👍

@@ -150,12 +152,12 @@ export default class SidebarRight extends React.PureComponent {

switch (this.state.list) {
case MyListName:
todos = this.props.allIssues.my || [];
todos = this.props.myIssues || [];
Copy link
Contributor

Choose a reason for hiding this comment

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

These empty arrays are going to cause re-renders. But this.props.myIssues should always be defined anyway right? So these actually don't have an effect. Either way I think that this component shouldn't be responsible for this check

@@ -122,15 +124,15 @@ export default class SidebarRight extends React.PureComponent {
}

getInIssues() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
getInIssues() {
getInIssuesCount() {

It looks like these 3 functions aren't actually called though. I think we can just remove them

@@ -51,7 +51,9 @@ const InListName = 'in';

export default class SidebarRight extends React.PureComponent {
static propTypes = {
allIssues: PropTypes.arrayOf(PropTypes.object),
myIssues: PropTypes.array,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
myIssues: PropTypes.array,
myIssues: PropTypes.array.isRequired,

Same for the two lines below this one. (GitHub isn't letting me comment on multiple lines for some reason)

image

@@ -13,7 +13,9 @@ export default class SidebarButtons extends React.PureComponent {
theme: PropTypes.object.isRequired,
isTeamSidebar: PropTypes.bool,
showRHSPlugin: PropTypes.func.isRequired,
allIssues: PropTypes.object,
myIssues: PropTypes.array,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

Suggested change
myIssues: PropTypes.array,
myIssues: PropTypes.array.isRequired,

@@ -63,7 +67,7 @@ export default class SidebarButtons extends React.PureComponent {
}}
>
<i className='icon icon-check'/>
{' ' + allIssues && allIssues.my ? allIssues.my.length : 0}
{' ' + myIssues ? myIssues.length : 0}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think myIssues will always be defined right?

@mickmister
Copy link
Contributor

@jufab Is this ready for another review? Just wondering

@jufab
Copy link
Contributor Author

jufab commented Nov 10, 2023

yes ready 😊

@jufab
Copy link
Contributor Author

jufab commented Nov 16, 2023

Do I have something else to do?

@larkox larkox requested a review from mickmister November 17, 2023 10:45
Copy link
Contributor

@mickmister mickmister left a comment

Choose a reason for hiding this comment

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

LGTM, thanks @jufab!

Comment on lines +174 to +176
msg := "Unable to get telemetry payload from JSON"
p.API.LogError(msg, "err", err.Error())
p.handleErrorWithCode(w, http.StatusBadRequest, msg, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we do change the logging back the way it was for places unrelated the purpose of the PR, but I don't want to drag this PR on for that reason

@mickmister mickmister removed the 2: Dev Review Requires review by a core committer label Nov 17, 2023
@avas27JTG avas27JTG added this to the v1.0.0 milestone Dec 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3: QA Review Requires review by a QA tester
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Combine the webapp plugin's requests for refreshing counts into one request
6 participants