Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Commit

Permalink
feat: Change Requests UI
Browse files Browse the repository at this point in the history
  • Loading branch information
dcramer committed Dec 31, 2018
1 parent fc54610 commit b75857c
Show file tree
Hide file tree
Showing 16 changed files with 422 additions and 14 deletions.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"*.log": true,
"*.egg-info": true,
".vscode/tags": true,
".mypy_cache": true
".mypy_cache": true,
".venv": true,
"kubernetes/zeus/dist": true,
},
"files.trimTrailingWhitespace": false,
"files.trimFinalNewlines": false,
Expand Down
31 changes: 31 additions & 0 deletions webapp/actions/changeRequests.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import api from '../api';
import {LOAD_CHANGE_REQUEST_LIST, PRE_LOAD_CHANGE_REQUEST_LIST} from '../types';

export const loadChangeRequestsForRepository = (repoFullName, query) => {
return dispatch => {
dispatch({
type: PRE_LOAD_CHANGE_REQUEST_LIST
});
api
.get(`/repos/${repoFullName}/change-requests`, {
query
})
.then(items => {
dispatch({
type: LOAD_CHANGE_REQUEST_LIST,
items
});
});
};
};

export const loadChangeRequestsForUser = (userID = 'me', query) => {
return dispatch => {
api.get(`/users/${userID}/change-requests`, {query}).then(items => {
dispatch({
type: LOAD_CHANGE_REQUEST_LIST,
items
});
});
};
};
68 changes: 68 additions & 0 deletions webapp/components/ChangeRequestList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';

import ChangeRequestListItem from '../components/ChangeRequestListItem';
import {ResultGrid, Column, Header} from '../components/ResultGrid';

export default class ChangeRequestList extends Component {
static propTypes = {
repo: PropTypes.object,
changeRequestList: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.array,
includeAuthor: PropTypes.bool,
includeRepo: PropTypes.bool,
params: PropTypes.object
};

static defaultProps = {
columns: ['coverage', 'duration', 'date']
};

render() {
let {
changeRequestList,
columns,
includeAuthor,
includeRepo,
params,
repo
} = this.props;
return (
<ResultGrid>
<Header>
<Column>Change Request</Column>
{columns.indexOf('coverage') !== -1 && (
<Column width={90} textAlign="center" hide="sm">
Coverage
</Column>
)}
{columns.indexOf('duration') !== -1 && (
<Column width={90} textAlign="center" hide="sm">
Duration
</Column>
)}
{columns.indexOf('date') !== -1 && (
<Column width={120} textAlign="right" hide="sm">
When
</Column>
)}
</Header>
<div>
{changeRequestList.map(cr => {
return (
<ChangeRequestListItem
key={cr.id}
columns={columns}
changeRequest={cr}
params={params}
repo={repo}
includeAuthor={includeAuthor}
includeRepo={includeRepo}
/>
);
})}
</div>
</ResultGrid>
);
}
}
130 changes: 130 additions & 0 deletions webapp/components/ChangeRequestListItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import {Flex, Box} from 'grid-styled';

import BuildListItem from './BuildListItem';
import ListItemLink from './ListItemLink';
import ObjectAuthor from './ObjectAuthor';
import {Column, Row} from './ResultGrid';
import TimeSince from './TimeSince';

export default class ChangeRequestListItem extends Component {
static propTypes = {
changeRequest: PropTypes.object.isRequired,
repo: PropTypes.object,
date: PropTypes.any,
includeAuthor: PropTypes.bool,
includeRepo: PropTypes.bool,
columns: PropTypes.array
};

static defaultProps = {
includeAuthor: true,
columns: ['coverage', 'duration', 'date']
};

render() {
let {changeRequest, columns, includeAuthor, includeRepo} = this.props;
let repo = this.props.repo || changeRequest.repository;

// TODO(dcramer):
// let link = changeRequest.number
// ? `/${repo.full_name}/change-requests/${changeRequest.number}`
// : `/${repo.full_name}/revisions/${changeRequest.head_revision.sha}`;

let link = `/${repo.full_name}/revisions/${changeRequest.head_revision.sha}`;

if (changeRequest.latest_build)
return (
<BuildListItem
build={changeRequest.latest_build}
repo={repo}
date={changeRequest.committed_at || changeRequest.created_at}
columns={columns}
/>
);

return (
<ListItemLink to={link}>
<Row>
<Column>
<Flex>
<Box width={15} mr={8} />
<Box flex="1" style={{minWidth: 0}}>
<Message>{changeRequest.message.split('\n')[0]}</Message>
<Meta>
{includeRepo ? (
<RepoLink to={`/${repo.full_name}`}>
{repo.owner_name}/{repo.name}
</RepoLink>
) : null}
<Commit>{changeRequest.head_revision.sha.substr(0, 7)}</Commit>
{includeAuthor ? (
<Author>
<ObjectAuthor data={changeRequest} />
</Author>
) : null}
</Meta>
</Box>
</Flex>
</Column>
{columns.indexOf('coverage') !== -1 && (
<Column width={90} textAlign="center" hide="sm" />
)}
{columns.indexOf('duration') !== -1 && (
<Column width={90} textAlign="center" hide="sm" />
)}
{columns.indexOf('date') !== -1 && (
<Column width={120} textAlign="right" hide="sm">
<TimeSince date={this.props.date || changeRequest.created_at} />
</Column>
)}
</Row>
</ListItemLink>
);
}
}

const Message = styled.div`
font-size: 15px;
line-height: 1.2;
font-weight: 500;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
`;

const Author = styled.div`
font-family: 'Monaco', monospace;
font-size: 12px;
font-weight: 600;
`;

const Commit = styled(Author)`
font-weight: 400;
`;

const RepoLink = styled(Author)`
font-weight: 400;
`;

const Meta = styled.div`
display: flex;
font-size: 12px;
color: #7f7d8f;
> div {
margin-right: 12px;
&:last-child {
margin-right: 0;
}
}
svg {
vertical-align: bottom !important;
margin-right: 5px;
color: #bfbfcb;
}
`;
1 change: 1 addition & 0 deletions webapp/components/RepositoryHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default class RepositoryHeader extends Component {
Overview
</NavItem>
<NavItem to={`${basePath}/revisions`}>Commits</NavItem>
<NavItem to={`${basePath}/change-requests`}>Change Requests</NavItem>
<NavItem to={`${basePath}/builds`}>Builds</NavItem>
<NavItem to={`${basePath}/coverage`}>Coverage</NavItem>
<NavItem to={`${basePath}/tests`}>Tests</NavItem>
Expand Down
85 changes: 85 additions & 0 deletions webapp/pages/RepositoryChangeRequestList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';

import {loadChangeRequestsForRepository} from '../actions/changeRequests';
import {subscribe} from '../decorators/stream';

import AsyncPage from '../components/AsyncPage';
import AsyncComponent from '../components/AsyncComponent';
import ChangeRequestList from '../components/ChangeRequestList';
import Paginator from '../components/Paginator';

class RepositoryChangeRequestList extends AsyncPage {
static propTypes = {
...AsyncPage.propTypes,
repo: PropTypes.object.isRequired
};

renderBody() {
return <ChangeRequestListBody {...this.props} />;
}
}

class ChangeRequestListBody extends AsyncComponent {
static propTypes = {
repo: PropTypes.object.isRequired,
changeRequestList: PropTypes.array,
links: PropTypes.object
};

fetchData() {
return new Promise(resolve => {
let {repo} = this.props;
this.props.loadChangeRequestsForRepository(
repo.full_name,
this.props.location.query
);
return resolve();
});
}

renderBody() {
return (
<div>
<ChangeRequestList
params={this.props.params}
changeRequestList={this.props.changeRequestList}
repo={this.props.repo}
/>
<Paginator links={this.props.links} {...this.props} />
</div>
);
}
}

// We force the repo param into props so that we can read it as part of connect
// in order to filter down the data we're propagating to the child
// XXX(dcramer): this is super tricky/sketch atm
const DecoratedRepositoryChangeRequestList = connect(
({changeRequests}, {repo}) => ({
changeRequestList: changeRequests.items.filter(
cr => !cr.repository || cr.repository.full_name === repo.full_name
),
links: changeRequests.links,
loading: !changeRequests.loaded
}),
{loadChangeRequestsForRepository}
)(
subscribe(({repo}) => [`repos:${repo.full_name}:change-requests`])(
RepositoryChangeRequestList
)
);

export default class RepositoryChangeRequestListWithRepoProp extends Component {
static contextTypes = {
...AsyncPage.contextTypes,
repo: PropTypes.object.isRequired
};

render() {
return (
<DecoratedRepositoryChangeRequestList {...this.props} repo={this.context.repo} />
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,26 @@ exports[`RepositoryDetails renders with repo in context 1`] = `
</a>
</Link>
</Styled(Link)>
<Styled(Link)
activeClassName="active"
to="/gh/getsentry/zeus/change-requests"
>
<Link
activeClassName="active"
className="sc-ifAKCX fbekww"
onlyActiveOnIndex={false}
style={Object {}}
to="/gh/getsentry/zeus/change-requests"
>
<a
className="sc-ifAKCX fbekww"
onClick={[Function]}
style={Object {}}
>
Change Requests
</a>
</Link>
</Styled(Link)>
<Styled(Link)
activeClassName="active"
to="/gh/getsentry/zeus/builds"
Expand Down
26 changes: 26 additions & 0 deletions webapp/reducers/changeRequests.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {LOAD_CHANGE_REQUEST_LIST, PRE_LOAD_CHANGE_REQUEST_LIST} from '../types';

const initialState = {
items: [],
links: {},
loaded: false
};

export default (state = initialState, action = {}) => {
switch (action.type) {
case PRE_LOAD_CHANGE_REQUEST_LIST:
return {
...state,
loaded: false
};
case LOAD_CHANGE_REQUEST_LIST:
return {
...state,
items: [...action.items],
links: {...action.items.links},
loaded: true
};
default:
return state;
}
};
Loading

0 comments on commit b75857c

Please sign in to comment.