Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
49 changes: 40 additions & 9 deletions server/plugin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const (
requestTimeout = 30 * time.Second
oauthCompleteTimeout = 2 * time.Minute

channelIDParam = "channelId"
channelIDParam = "channelId"
organisationParam = "organization"
)

type OAuthState struct {
Expand Down Expand Up @@ -1441,14 +1442,21 @@ func (p *Plugin) getReposByOrg(c *UserContext, w http.ResponseWriter, r *http.Re

opt := github.ListOptions{PerPage: 50}

orgString := r.URL.Query().Get("organization")
orgString := r.URL.Query().Get(organisationParam)

if orgString == "" {
c.Log.Warnf("Organization query param is empty")
p.writeAPIError(w, &APIErrorResponse{Message: "Organization query is empty, must include organization name ", StatusCode: http.StatusBadRequest})
return
}

channelIDString := r.URL.Query().Get(channelIDParam)
if channelIDString == "" {
c.Log.Warnf("Channel ID query param is empty")
p.writeAPIError(w, &APIErrorResponse{Message: "Channel ID query is empty, must include Channel ID ", StatusCode: http.StatusBadRequest})
return
}

orgList := strings.Split(orgString, ",")
var allRepos []*github.Repository

Expand Down Expand Up @@ -1489,21 +1497,44 @@ func (p *Plugin) getReposByOrg(c *UserContext, w http.ResponseWriter, r *http.Re
}

// Only send repositories which are part of the requested organization(s)
type RepositoryResponse struct {
Name string `json:"name,omitempty"`
FullName string `json:"full_name,omitempty"`
Permissions map[string]bool `json:"permissions,omitempty"`
}

resp := make([]*RepositoryResponse, len(allRepos))
repoResp := make([]RepoResponse, len(allRepos))
for i, r := range allRepos {
resp[i] = &RepositoryResponse{
repoResp[i] = RepoResponse{
Name: r.GetName(),
FullName: r.GetFullName(),
Permissions: r.GetPermissions(),
}
}

resp := RepositoryResponse{
Repos: repoResp,
}

// Add default repo if available
defaultRepo, dErr := p.GetDefaultRepo(c.GHInfo.UserID, channelIDString)
if dErr != nil {
c.Log.WithError(dErr).Warnf("Failed to get the default repo for the channel. UserID: %s. ChannelID: %s", c.GHInfo.UserID, channelIDString)
}

if defaultRepo != "" {
config := p.getConfiguration()
baseURL := config.getBaseURL()
owner, repo := parseOwnerAndRepo(defaultRepo, baseURL)
defaultRepository, err := getRepository(c.Ctx, owner, repo, githubClient)
if err != nil {
c.Log.WithError(err).Warnf("Failed to get the default repo %s/%s", owner, repo)
}

if defaultRepository != nil {
resp.DefaultRepo = RepoResponse{
Name: defaultRepository.GetName(),
FullName: defaultRepository.GetFullName(),
Permissions: defaultRepository.Permissions,
}
}
}

p.writeJSON(w, resp)
}

Expand Down
7 changes: 6 additions & 1 deletion server/plugin/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package plugin
import (
"context"
"fmt"
"net/http"
"strings"
"unicode"

Expand Down Expand Up @@ -195,11 +196,15 @@ func (p *Plugin) isValidGitHubUsername(username string, userInfo *GitHubUserInfo
if cErr := p.useGitHubClient(userInfo, func(userInfo *GitHubUserInfo, token *oauth2.Token) error {
ghUser, _, err := githubClient.Users.Get(context.Background(), username)
if err != nil {
if gErr, ok := err.(*github.ErrorResponse); ok && gErr.Response.StatusCode == http.StatusNotFound {
return ErrNotFound
}

return err
}

if ghUser == nil {
return fmt.Errorf("%w", ErrNotFound)
return ErrNotFound
}

return nil
Expand Down
4 changes: 2 additions & 2 deletions webapp/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ export function getOrgs() {
};
}

export function getReposByOrg(organization: string) {
export function getReposByOrg(organization: string, channelId: string) {
return async (dispatch: DispatchFunc) => {
let data;
try {
data = await Client.getRepositoriesByOrganization(organization);
data = await Client.getRepositoriesByOrganization(organization, channelId);
} catch (error) {
return {error: data};
}
Expand Down
4 changes: 2 additions & 2 deletions webapp/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export default class Client {
return this.doGet<ChannelRepositoriesData>(`${this.url}/repositories?channelId=${channelId}`);
}

getRepositoriesByOrganization = async (organization: string): Promise<RepositoryData[] | ApiError> => {
return this.doGet<RepositoryData[]>(`${this.url}/repos_by_org?organization=${organization}`);
getRepositoriesByOrganization = async (organization: string, channelId: string): Promise<YourReposData[] | ApiError> => {
return this.doGet<YourReposData[]>(`${this.url}/repos_by_org?organization=${organization}&channelId=${channelId}`);
}

getPrsDetails = async (prList: {url: string, number: number}[]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,10 @@ import PropTypes from 'prop-types';

import ReactSelectSetting from '@/components/react_select_setting';

const initialState = {
invalid: false,
error: null,
org: '',
};

export default class GithubRepoSelector extends PureComponent {
static propTypes = {
yourOrgs: PropTypes.array.isRequired,
yourReposByOrg: PropTypes.array,
yourReposByOrg: PropTypes.object,
theme: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
Expand All @@ -28,39 +22,74 @@ export default class GithubRepoSelector extends PureComponent {
}).isRequired,
};

static defaultProps = {
yourReposByOrg: {repos: []},
};

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

componentDidMount() {
this.props.actions.getOrgs();
}

getReposArray = () => {
const {yourReposByOrg} = this.props;

if (yourReposByOrg?.repos?.length > 0) {
return yourReposByOrg.repos;
}

if (yourReposByOrg?.defaultRepo) {
return [yourReposByOrg.defaultRepo];
}

return [];
}

componentDidUpdate(prevProps) {
if (prevProps.yourOrgs !== this.props.yourOrgs) {
if (this.props.yourOrgs.length) {
this.onChangeForOrg(0, this.props.yourOrgs[0].login);
const repos = this.getReposArray();
const defaultRepo = this.props.yourReposByOrg?.defaultRepo;
const prevDefaultRepo = prevProps.yourReposByOrg?.defaultRepo;

if ((!this.props.value || (defaultRepo && defaultRepo.full_name !== prevDefaultRepo?.full_name)) && defaultRepo) {
this.onChangeForRepo(defaultRepo.name, defaultRepo.full_name);
} else if (!defaultRepo && !this.props.value && repos.length > 0) {
this.onChangeForRepo(repos[0].name, repos[0].full_name);
}

if (prevProps.yourOrgs !== this.props.yourOrgs && this.props.yourOrgs.length > 0) {
const newOrg = this.props.yourOrgs[0].login;
if (this.state.org !== newOrg) {
this.onChangeForOrg(newOrg);
}
}
}

onChangeForOrg = (_, org) => {
onChangeForOrg = (org) => {
if (this.state.org !== org) {
this.setState({org});
this.props.actions.getReposByOrg(org);
this.props.actions.getReposByOrg(org, this.props.currentChannelId);
this.props.onChange(null);
}
}

onChangeForRepo = (_, name) => {
const repo = this.props.yourReposByOrg.find((r) => r.full_name === name);
this.props.onChange({name, permissions: repo.permissions});
const repos = this.getReposArray();

const repo = repos.find((r) => r.full_name === name);
if (repo) {
this.props.onChange({name, permissions: repo.permissions});
}
}

render() {
const orgOptions = this.props.yourOrgs.map((item) => ({value: item.login, label: item.login}));
const repoOptions = this.props.yourReposByOrg.map((item) => ({value: item.full_name, label: item.name}));
const orgOptions = this.props.yourOrgs.map((org) => ({value: org.login, label: org.login}));

const repos = this.getReposArray();
const repoOptions = repos.map((repo) => ({value: repo.full_name, label: repo.name}));

let orgSelector = null;
let helperTextForRepoSelector = 'Returns GitHub repositories connected to the user account';
Expand Down
3 changes: 3 additions & 0 deletions webapp/src/components/github_repo_selector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';

import manifest from '@/manifest';

import {getReposByOrg, getOrgs} from '../../actions';
Expand All @@ -14,6 +16,7 @@ function mapStateToProps(state) {
return {
yourOrgs: state[`plugins-${manifest.id}`].yourOrgs,
yourReposByOrg: state[`plugins-${manifest.id}`].yourReposByOrg,
currentChannelId: getCurrentChannelId(state),
};
}

Expand Down
Loading