Skip to content
Open
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
108 changes: 102 additions & 6 deletions webapp/src/components/sidebar_right/sidebar_right.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import Scrollbars from 'react-custom-scrollbars-2';
import ReactSelect from 'react-select';

import {makeStyleFromTheme} from 'mattermost-redux/utils/theme_utils';

import {RHSStates} from '../../constants';
import {getStyleForReactSelect} from '@/utils/styles';

import GithubItems from './github_items';

Expand Down Expand Up @@ -43,6 +47,13 @@ function mapGithubItemListToPrList(gilist) {
});
}

function getRepoName(item) {
if (item.repository_url) {
return item.repository_url.replace(/.+\/repos\//, '');
}
return item.repository?.full_name ?? null;
}

function shouldUpdateDetails(prs, prevPrs, targetState, currentState, prevState) {
if (currentState === targetState) {
if (currentState !== prevState) {
Expand Down Expand Up @@ -81,6 +92,15 @@ export default class SidebarRight extends React.PureComponent {
}).isRequired,
};

constructor(props) {
super(props);
this.state = {selectedRepo: ''};
}

handleRepoFilterChange = (selectedOption) => {
this.setState({selectedRepo: selectedOption ? selectedOption.value : ''});
}

componentDidMount() {
if (this.props.yourPrs && this.props.rhsState === RHSStates.PRS) {
this.props.actions.getYourPrsDetails(mapGithubItemListToPrList(this.props.yourPrs));
Expand All @@ -92,6 +112,22 @@ export default class SidebarRight extends React.PureComponent {
}

componentDidUpdate(prevProps) {
// Reset repo filter when switching RHS tabs
if (prevProps.rhsState !== this.props.rhsState) {
this.setState({selectedRepo: ''}); // eslint-disable-line react/no-did-update-set-state
}

// Validate selectedRepo against the current list — reset if the repo
// is no longer present (stale state when the underlying data changes)
if (this.state.selectedRepo) {
const currentRepos = [...new Set(
this.getCurrentItems().map(getRepoName).filter(Boolean),
)];
if (!currentRepos.includes(this.state.selectedRepo)) {
this.setState({selectedRepo: ''}); // eslint-disable-line react/no-did-update-set-state
}
}

if (shouldUpdateDetails(this.props.yourPrs, prevProps.yourPrs, RHSStates.PRS, this.props.rhsState, prevProps.rhsState)) {
this.props.actions.getYourPrsDetails(mapGithubItemListToPrList(this.props.yourPrs));
}
Expand All @@ -101,6 +137,22 @@ export default class SidebarRight extends React.PureComponent {
}
}

getCurrentItems = () => {
const {yourPrs, reviews, unreads, yourAssignments, rhsState} = this.props;
switch (rhsState) {
case RHSStates.PRS:
return yourPrs || [];
case RHSStates.REVIEWS:
return reviews || [];
case RHSStates.UNREADS:
return unreads || [];
case RHSStates.ASSIGNMENTS:
return yourAssignments || [];
default:
return [];
}
}

render() {
const baseURL = this.props.enterpriseURL ? this.props.enterpriseURL : 'https://github.com';
let orgQuery = '';
Expand Down Expand Up @@ -134,17 +186,39 @@ export default class SidebarRight extends React.PureComponent {
githubItems = unreads;
title = 'Unread Messages';
listUrl = baseURL + '/notifications';

break;
case RHSStates.ASSIGNMENTS:

githubItems = yourAssignments;
title = 'Your Assignments';
listUrl = baseURL + '/pulls?q=is%3Aopen+archived%3Afalse+assignee%3A' + username + orgQuery;

break;
default:
break;
}

// Extract unique repo names for filter dropdown
const repoNames = [...new Set(

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 repo-name extraction logic is duplicated. Please factor it into a small helper at module scope so both call sites stay in sync if the GitHub response shape ever changes.

function getRepoName(item) {
    if (item.repository_url) {
        return item.repository_url.replace(/.+\/repos\//, '');
    }
    return item.repository?.full_name ?? null;
}

githubItems.map(getRepoName).filter(Boolean),
)].sort();

// Build react-select options
const repoOptions = [
{value: '', label: 'All repositories'},
...repoNames.map((repo) => ({value: repo, label: repo})),
];

// Filter items by selected repo
const {selectedRepo} = this.state;
const selectedOption = repoOptions.find((opt) => opt.value === selectedRepo) || repoOptions[0];
const filteredItems = selectedRepo ?
githubItems.filter((item) => getRepoName(item) === selectedRepo) :
githubItems;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const style = getStyle(this.props.theme);

return (
<React.Fragment>
<Scrollbars
Expand All @@ -163,10 +237,24 @@ export default class SidebarRight extends React.PureComponent {
rel='noopener noreferrer'
>{title}</a>
</strong>
{repoNames.length > 1 && (
<div style={style.repoFilterContainer}>
<ReactSelect
value={selectedOption}
onChange={this.handleRepoFilterChange}
options={repoOptions}
styles={getStyleForReactSelect(this.props.theme)}
aria-label='Filter by repository'
isSearchable={false}
menuPortalTarget={document.body}
menuPlacement='auto'
/>
</div>
)}
</div>
<div>
<GithubItems
items={githubItems}
items={filteredItems}
theme={this.props.theme}
showReviewSLA={rhsState === RHSStates.REVIEWS}
reviewTargetDays={this.props.reviewTargetDays || 0}
Expand All @@ -178,8 +266,16 @@ export default class SidebarRight extends React.PureComponent {
}
}

const style = {
sectionHeader: {
padding: '15px',
},
};
const getStyle = makeStyleFromTheme((theme) => {
return {
sectionHeader: {
padding: '15px',
},
repoFilterContainer: {
marginLeft: '8px',
minWidth: '160px',
maxWidth: '220px',
fontSize: '12px',
},
};
});