Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion webapp/src/components/sidebar_right/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import {getReviewsDetails, getYourPrsDetails} from '../../actions';
import {getReviewsDetails, getYourPrsDetails, getSidebarContent} from '../../actions';

import {getSidebarData} from 'src/selectors';

Expand All @@ -30,6 +30,7 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
getYourPrsDetails,
getReviewsDetails,
getSidebarContent,
}, dispatch),
};
}
Expand Down
42 changes: 42 additions & 0 deletions webapp/src/components/sidebar_right/sidebar_right.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import Scrollbars from 'react-custom-scrollbars-2';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';

import {RHSStates} from '../../constants';

Expand Down Expand Up @@ -78,10 +79,31 @@ export default class SidebarRight extends React.PureComponent {
actions: PropTypes.shape({
getYourPrsDetails: PropTypes.func.isRequired,
getReviewsDetails: PropTypes.func.isRequired,
getSidebarContent: PropTypes.func.isRequired,
}).isRequired,
};

constructor(props) {
super(props);
this.state = {refreshing: false};
}

handleRefresh = async (e) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see a couple of issues here. setState is async so two fast clicks can both pass the this.state.refreshing check before either update lands. Use an instance flag for the gate. We also need unmount safety.

Suggested rewrite:

componentDidMount() {
    this._mounted = true;
    // ...
}
componentWillUnmount() {
    this._mounted = false;
}
handleRefresh = async (e) => {
    e?.preventDefault();
    if (this._refreshing) {
        return;
    }
    this._refreshing = true;
    this.setState({refreshing: true});
    try {
        await this.props.actions.getSidebarContent();
    } finally {
        this._refreshing = false;
        if (this._mounted) {
            this.setState({refreshing: false});
        }
    }
};

if (e) {
e.preventDefault();
}
if (this.state.refreshing) {
return;
}
this.setState({refreshing: true});
await this.props.actions.getSidebarContent();
this.setState({refreshing: false});
};

componentDidMount() {
// Auto-refresh on open to guarantee latest data (issue #131)
this.handleRefresh();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The auto refresh on mount is redundant because getSidebarContent() is already dispatched from sidebar_buttons.jsx:42 and websocket/index.js:74,84. By the time the RHS opens the data is fresh.

This adds an extra GitHub search call every time the user opens the RHS, which is expensive. I'd suggest removing the auto refresh entirely and relying on the manual button and existing WS-driven updates. If you want to keep it, please mirror the E2E guard from sidebar_buttons.jsx.


if (this.props.yourPrs && this.props.rhsState === RHSStates.PRS) {
this.props.actions.getYourPrsDetails(mapGithubItemListToPrList(this.props.yourPrs));
}
Expand Down Expand Up @@ -163,6 +185,19 @@ export default class SidebarRight extends React.PureComponent {
rel='noopener noreferrer'
>{title}</a>
</strong>
<OverlayTrigger
key='rhsRefreshButton'
placement='left'
overlay={<Tooltip id='rhsRefreshTooltip'>{'Refresh'}</Tooltip>}
>
<a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

href='#'
style={style.refreshButton}
onClick={this.handleRefresh}
>
<i className={'fa fa-refresh' + (this.state.refreshing ? ' fa-spin' : '')}/>
</a>
</OverlayTrigger>
</div>
<div>
<GithubItems
Expand All @@ -181,5 +216,12 @@ export default class SidebarRight extends React.PureComponent {
const style = {
sectionHeader: {
padding: '15px',
display: 'flex',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This switches every RHS state's header to flex layout not just the ones with the refresh button. Could you attach screenshots of all four states (PRS, REVIEWS, UNREADS, ASSIGNMENTS) on both light and dark themes? Want to make sure the title alignment doesn't regress anywhere.

justifyContent: 'space-between',
alignItems: 'center',
},
refreshButton: {
color: 'rgba(0, 0, 0, 0.4)',

@nang2049 nang2049 Jun 29, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hard-coded color will be invisible on dark themes. this.props.theme is already available on the component please derive the color the same way sidebar_buttons.jsx does. Please convert the bottom-of-file style object to a memoized factory:

import {makeStyleFromTheme, changeOpacity} from 'mattermost-redux/utils/theme_utils';
const getStyle = makeStyleFromTheme((theme) => ({
    sectionHeader: {
        padding: '15px',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
    },
    refreshButton: {
        color: changeOpacity(theme.centerChannelColor, 0.6),
        cursor: 'pointer',
        background: 'transparent',
        border: 'none',
        padding: 0,
    },
}));

cursor: 'pointer',
},
};