Fixed routing by project full name instead of by project ID (#23)

This commit is contained in:
Kateryna Musina 2017-01-04 15:01:26 +01:00 коммит произвёл Kenneth Skovhus
Родитель ebe7f58d05
Коммит 3453eae8f4
18 изменённых файлов: 210 добавлений и 83 удалений

Просмотреть файл

@ -5,3 +5,15 @@ https://github.com/airbnb/javascript/tree/master/react#basic-rules
On top of it some extra rules:
1. No semicolons in javascript
2. Minimizing bundle size
Prefer imporing bindings from library libs directory(if available) instead of importing full lib bindings, especially when importing
Bootstrap or Material-UI components:
Good:
`import Navbar from 'react-bootstrap/lib/Navbar'`
`import Route from 'react-router/lib/Route'`
Bad:
`import { Navbar } from 'react-bootstrap'`
`import { Route, IndexRoute } from 'react-router'`

Просмотреть файл

@ -5,7 +5,7 @@ import React, { Component } from 'react'
import Col from 'react-bootstrap/lib/Col'
import Row from 'react-bootstrap/lib/Row'
import ListGroupItem from 'react-bootstrap/lib/ListGroupItem'
import { buildPullRequestLink, buildProjectLink } from 'routes/helpers'
import TestAvatar from 'components/TestAvatar'
import { Link } from 'react-router'
import { fromNow } from 'utils/datetime'
@ -45,7 +45,11 @@ class PullRequestListItem extends Component {
<div style={{ display: 'table' }}>
<TestAvatar />
<div style={{ paddingLeft: '10px', display: 'table' }}>
<Link to={pullRequest.link}>{pullRequest.title}</Link>
<Link
to={buildPullRequestLink(pullRequest.target.repository.fullName, pullRequest.id)}
>
{pullRequest.title}
</Link>
<div style={{ fontSize: '12px', color: 'grey', fontStyle: 'italic' }}>
<strong>{pullRequest.owner.fullName}</strong>
<span> updated {fromNow(pullRequest.updated)}</span>
@ -59,7 +63,9 @@ class PullRequestListItem extends Component {
<div>
<Link
style={{ textDecoration: 'none', color: 'rgb(59, 120, 155)' }}
to={pullRequest.originLink}
to={{
pathname: buildProjectLink(pullRequest.origin.repository.fullName),
query: { branch: pullRequest.origin.branch } }}
>
{pullRequest.origin.repository.name}
<span style={{ color: '#8ea7b6' }}>#</span>{pullRequest.origin.branch}
@ -73,7 +79,7 @@ class PullRequestListItem extends Component {
<div style={{ overflowWrap: 'break-word' }}>
<Link
style={{ textDecoration: 'none', color: 'rgb(59, 120, 155)' }}
to={pullRequest.targetLink}
to={buildProjectLink(pullRequest.target.repository.fullName)}
>
{pullRequest.target.repository.name}
</Link>

Просмотреть файл

@ -7,6 +7,7 @@ import RepositoryItem from './index'
const repository = {
name: 'Some-Repository',
groupPath: 'group/subgroup',
description: 'Testing out the list item',
id: '5',
owner: { fullName: 'William Sprent' },

Просмотреть файл

@ -6,7 +6,7 @@ import Row from 'react-bootstrap/lib/Row'
import ListGroupItem from 'react-bootstrap/lib/ListGroupItem'
import { Link } from 'react-router'
import { fromNow } from 'utils/datetime'
import { buildProjectLinkNoBranch } from 'routes/helpers'
import { buildProjectLink } from 'routes/helpers'
const subHeader = text => (
<div className="sub-header">
@ -17,6 +17,7 @@ const subHeader = text => (
export type RepositoryType = {
name: string,
description: ?string,
groupPath: string,
id: string,
owner: { fullName: string },
updated: string,
@ -39,7 +40,11 @@ function RepositoryItem(props: Props) {
</div>
<div style={{ display: 'table' }}>
<div style={{ paddingLeft: '10px', display: 'table' }}>
<Link to={buildProjectLinkNoBranch(repository.id)}>{repository.name}</Link>
<Link
to={buildProjectLink(repository.name, repository.groupPath)}
>
{repository.name}
</Link>
<div style={{ fontSize: '12px', color: 'grey', fontStyle: 'italic' }}>
{repository.description}
</div>

Просмотреть файл

@ -10,6 +10,7 @@ const repos = [
name: 'Some-Repository1',
description: 'Testing out the list item',
id: '1',
groupPath: 'group/subgroup',
owner: { fullName: 'William Sprent' },
updated: '2016-12-09 10:00:12.250926',
},
@ -17,6 +18,7 @@ const repos = [
name: 'Some-Repository2',
description: 'Testing out the list item2',
id: '2',
groupPath: 'group/subgroup',
owner: { fullName: 'William Sprent' },
updated: '2016-12-09 10:00:12.250926',
},
@ -24,6 +26,7 @@ const repos = [
name: 'Some-Repository3',
description: 'Testing out the list item3',
id: '3',
groupPath: 'group/subgroup',
owner: { fullName: 'William Sprent' },
updated: '2016-12-09 10:00:12.250926',
},

Просмотреть файл

@ -1,6 +1,5 @@
/* @flow */
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { FiltersFields } from 'ducks/pullrequests'
@ -10,6 +9,7 @@ import PullRequestList from 'components/PullRequestList'
import BranchSelect from 'containers/BranchSelect'
import RepoSelect from 'containers/RepoSelect'
import OrderSelect from 'containers/OrderSelect'
import Alert from 'react-bootstrap/lib/Alert'
import type { OrderByType, DirectionType } from 'ducks/order'
export type Props = {
@ -84,6 +84,7 @@ class PullRequestsPaginated extends Component {
}
render() {
const pullRequestsExists = !!this.props.total
return (
<div>
<div
@ -118,11 +119,20 @@ class PullRequestsPaginated extends Component {
</div>
{this.props.isFetching && <LinearProgress />}
{this.props.error && <ErrorMessage error={this.props.error} />}
<PullRequestList
showRemoveButton
onPageSelect={this.handlePageSelect}
onRemoveClick={this.handleRemove} {...this.props}
/>
{!pullRequestsExists && !this.props.isFetching && !this.props.error &&
<Alert bsStyle="warning" style={{ fontSize: '13px' }}>
<strong>
<i className="fa fa-exclamation-circle" aria-hidden="true"></i> </strong>
There is no pull request
</Alert>
}
{pullRequestsExists &&
<PullRequestList
showRemoveButton
onPageSelect={this.handlePageSelect}
onRemoveClick={this.handleRemove} {...this.props}
/>
}
</div>
)
}

Просмотреть файл

@ -22,14 +22,14 @@ export const app = (store) => {
store.dispatch({ type: SIDE_BAR_SUBITEMS, subitems })
}
export const project = (store, id) => {
export const project = (store, name) => {
const items = [
{ title: 'Home', route: '/', icon: 'home' },
{ title: 'Project', route: `/project/${id}`, icon: 'folder' },
{ title: 'Files', route: `/project/${id}/files`, icon: 'files-o' },
{ title: 'Changelog', route: `/project/${id}/changelog`, icon: 'calendar' },
{ title: 'Pull Requests', route: `/project/${id}/pullrequests`, icon: 'tasks' },
{ title: 'Statistics', route: `/project/${id}/statistics`, icon: 'bar-chart' },
{ title: 'Project', route: `/project/${name}`, icon: 'folder' },
{ title: 'Files', route: `/project/${name}/files`, icon: 'files-o' },
{ title: 'Changelog', route: `/project/${name}/changelog`, icon: 'calendar' },
{ title: 'Pull Requests', route: `/project/${name}/pullrequests`, icon: 'tasks' },
{ title: 'Statistics', route: `/project/${name}/statistics`, icon: 'bar-chart' },
]
store.dispatch({ type: SIDE_BAR_ITEMS, items })

Просмотреть файл

@ -25,6 +25,7 @@ export const types = {
SET_REPOSITORIES_NAMES: 'REPOSITORIES/SET_REPOSITORIES_NAMES',
SET_GROUPS: 'REPOSITORIES/SET_GROUPS',
FETCH_REPOSITORIES: 'REPOSITORIES/FETCH_REPOSITORIES',
FETCH_REPOSITORY: 'REPOSITORIES/FETCH_REPOSITORY',
SEARCH_REPOSITORY: 'REPOSITORIES/SEARCH_REPOSITORY',
FETCH_REPOSITORY_BRANCHES: 'REPOSITORIES/FETCH_REPOSITORY_BRANCHES',
}
@ -100,6 +101,10 @@ export const searchRepository =
(filter: string, first: number): Object =>
({ type: types.SEARCH_REPOSITORY, filter, first })
export const fetchRepository = (name: string, queryStr: string): Object =>
fetchActionCreator(types.FETCH_REPOSITORY, { name }, queryStr,
(data: Object, cbArgs: Object): Array<Object> =>
[{ type: types.SET_REPOSITORY, node: parseRepository(data) }])
export const fetchRepositoryBranches = (id: number): Object =>
fetchActionCreator(types.FETCH_REPOSITORY_BRANCHES, { id }, REPOSITORY_BRANCHES,

Просмотреть файл

@ -4,6 +4,7 @@ import {
repositories,
groupsEntitiesSelector,
groups,
getRepositoryId,
} from '../index'
const expect = chai.expect
@ -320,4 +321,53 @@ describe('projects selectors', () => {
expect(groups(state, props)).to.eql([node1])
})
it('getRepositoryId selector', () => {
const node1 = {
id: 1,
name: 'name1',
description: 'description1',
fullName: 'group/name1',
}
const node2 = {
id: 2,
name: 'name2',
description: 'description2',
fullName: 'group/name2',
}
const node3 = {
id: 3,
name: 'name3',
description: 'description3',
fullName: 'group/name3',
}
const state = {
repositories: {
entities: {
1: node1,
2: node2,
3: node3,
},
},
}
const props = {
params: {
splat: 'group/name3',
},
}
expect(getRepositoryId(state, props)).to.equal('3')
const props2 = {
params: {
},
}
expect(getRepositoryId(state, props2)).to.equal(undefined)
})
})

Просмотреть файл

@ -61,3 +61,10 @@ export const repositoriesNames = createSelector(
repoNames => repoNames.map(x => ({ label: x.fullName, value: x.id }))
)
export const repoNameSelector = (state: Object, props: Object): any =>
_.findKey(state.repositories.entities, { fullName: props.params.splat })
export const getRepositoryId = createSelector(
repoNameSelector,
repoName => repoName
)

Просмотреть файл

@ -50,7 +50,7 @@ class Files extends Component {
updateCurrentNode = (nextProps) => {
const { params: { splat } } = nextProps
if (splat) {
const nestedItem = this.findChild(nextProps.data, splat.split('/'))
const nestedItem = this.findChild(nextProps.data, splat[0].split('/'))
if (nestedItem) {
this.setState({ currentNode: nestedItem.children })
}

Просмотреть файл

@ -1,7 +1,9 @@
/* @flow */
import React from 'react'
import React, { Component } from 'react'
import Helmet from 'react-helmet'
import { connect } from 'react-redux'
import { fetchRepository } from 'ducks/repositories'
export type Props = {
params: {
@ -9,22 +11,46 @@ export type Props = {
},
children?: Object,
theme?: Object,
};
function Project(props: Props) {
const { theme } = props
const childrenWithProps = React.Children.map(props.children,
child => React.cloneElement(child, {
theme,
})
)
return (
<div>
<Helmet title={`Project ${props.params.id}`} />
{childrenWithProps}
</div>
)
name: string,
}
export default Project
export const REPOSITORY_QUERY = `
query($name: String!) {
repository(name: $name) {
id
name,
fullName,
branches {
name
revision
}
}
}`
class Project extends Component {
componentDidMount() {
this.props.dispatch(fetchRepository(this.props.name, REPOSITORY_QUERY))
}
render() {
const childrenWithProps = React.Children.map(this.props.children,
child => React.cloneElement(child, {
theme: this.props.theme,
})
)
return (
<div>
<Helmet title={`Project ${this.props.name}`} />
{childrenWithProps}
</div>
)
}
}
export default connect(
(state, props) => ({
name: props.params.splat,
})
)(Project)

Просмотреть файл

@ -2,7 +2,7 @@
import React from 'react'
import Helmet from 'react-helmet'
import { connect } from 'react-redux'
import PullRequestsPaginated from 'containers/PullRequestsPaginated'
import { fetchPullRequests } from 'ducks/pullrequests'
import {
@ -10,11 +10,14 @@ import {
getPageFetchError,
getPullRequests } from 'ducks/pullrequests/selectors'
import { getRepositoryId } from 'ducks/repositories/selectors'
export type Props = {
project_pullrequests: Array<any>,
params: {
id: string,
},
repo: string,
items: Array<any>,
}
@ -37,10 +40,15 @@ function PullRequests(props: Props) {
hideRepoSelect
mapStateToProps={mapStateToProps}
fetchData={fetchPullRequests}
repo={props.params.id}
repo={props.repo}
/>
</div>
)
}
export default PullRequests
export default connect(
(state, props) => ({
repo: getRepositoryId(state, props),
})
)(PullRequests)

Просмотреть файл

@ -1,18 +0,0 @@
/* @flow */
import App from './App'
import Home from './Home'
import NotFound from './NotFound'
import Projects from './Projects' // eslint-disable-line import/no-named-as-default
import PullRequests from './PullRequests'
import Html from './Html'
export default {
App,
Home,
Html,
Projects,
PullRequests,
NotFound,
}

Просмотреть файл

@ -25,16 +25,22 @@ describe('routes helpers', () => {
it('buildPullRequestLink', () => {
expect(helpers.buildPullRequestLink('projectname', '1234'))
.equals('/project/projectname/pullrequests/1234')
.equals('/project/projectname/pullrequest/1234')
})
it('buildProjectLink', () => {
expect(helpers.buildProjectLink('projectname', 'testbranch'))
.equals('/project/projectname?branch=testbranch')
it('buildProjectLink with group', () => {
expect(helpers.buildProjectLink('projectname', 'group1/subgroup'))
.equals('/project/group1/subgroup/projectname')
})
it('buildProjectsLink', () => {
expect(helpers.buildProjectsLink('projectname'))
it('buildProjectLink with group and branch', () => {
expect(helpers.buildProjectLink('projectname', 'group1/subgroup', 'testbranch'))
.equals('/project/group1/subgroup/projectname?branch=testbranch')
})
it('buildProjectsLink with empty group', () => {
expect(helpers.buildProjectsLink('projectname', null))
.equals('/projects/projectname')
})

Просмотреть файл

@ -16,18 +16,19 @@ export const breadcrumbItems = (pathname: string): Array<LinkType> => {
export const groupPathFromPath = (path: string): string => path.replace(/^\/projects(\/)?/, '')
export const buildPullRequestLink =
(projectName: string, id: string): string => (`/project/${projectName}/pullrequests/${id}`)
(projectName: string, id: string): string => (`/project/${projectName}/pullrequest/${id}`)
export const buildProjectLink =
(projectName: string, branch: string): string => (`/project/${projectName}?branch=${branch}`)
export const buildProjectLinkNoBranch =
(projectName: string): string => (`/project/${projectName}`)
(name: string, groupPath: string, branch: string = ''): string => {
const branchQuery = branch ? `?branch=${branch}` : ''
const projectName = groupPath ? `${groupPath}/${name}` : name
return `/project/${projectName}${branchQuery}`
}
export const buildProjectsLink =
(suffix: string): string => (`/projects/${suffix}`)
export const helpers = {
buildPullRequestLink,
buildProjectLink,
buildProjectLinkNoBranch,
buildProjectsLink,
groupPathFromPath,
breadcrumbItems,

Просмотреть файл

@ -1,5 +1,6 @@
import React from 'react'
import { Route, IndexRoute } from 'react-router'
import Route from 'react-router/lib/Route'
import IndexRoute from 'react-router/lib/IndexRoute'
import { types } from 'ducks/session'
@ -15,7 +16,6 @@ import Overview from 'pages/Project/Overview'
import Changeset from 'pages/Project/Changeset'
import ProjectPullRequests from 'pages/Project/PullRequests'
import Issues from 'pages/Project/Issues'
import Statistics from 'pages/Project/Statistics'
import PullRequest from 'pages/Project/PullRequest'
import NewPullRequest from 'pages/Project/NewPullRequest'
@ -27,11 +27,11 @@ export default (store) => {
store.dispatch({ type: types.FETCH_USER_PROFILE })
}
const onProjectEnter = (nextState) => {
project(store, nextState.params.id)
project(store, nextState.params.splat)
}
const onPullRequestEnter = (nextState) => {
pullrequest(store, nextState.params.id, nextState.params.prid)
pullrequest(store, nextState.params.splat, nextState.params.prid)
}
const onChangesetEnter = (nextState) => {
@ -48,21 +48,22 @@ export default (store) => {
</Route>
<Route onEnter={onProjectEnter}>
<Route path="/project/:id" component={Project}>
<Route path="/project" component={Project}>
<IndexRoute component={Overview} />
<Route path="files/(**/)*.*" component={File} />
<Route path="files(/**)" component={Files} />
<Route path="changelog" component={Changelog} />
<Route path="pullrequests" component={ProjectPullRequests} />
<Route path="**/files/(**/)*.*" component={File} />
<Route path="**/files(/**)" component={Files} />
<Route path="**/changelog" component={Changelog} />
<Route path="**/pullrequests" component={ProjectPullRequests} />
<Route onEnter={onPullRequestEnter}>
<Route path="pullrequest/:prid(/:category)" component={PullRequest} />
<Route path="**/pullrequest/:prid(/:category)" component={PullRequest} />
</Route>
<Route onEnter={onChangesetEnter}>
<Route path="changeset/:hash" component={Changeset} />
<Route path="**/changeset/:hash" component={Changeset} />
</Route>
<Route path="issues" component={Issues} />
<Route path="newpullrequest" component={NewPullRequest} />
<Route path="statistics" component={Statistics} />
<Route path="**/issues" component={Issues} />
<Route path="**/newpullrequest" component={NewPullRequest} />
<Route path="**" component={Overview} />
</Route>
</Route>
</Route>

Просмотреть файл

@ -16,6 +16,7 @@ const subQuery = `
repository {
id
name
fullName
}
}
target {
@ -23,6 +24,7 @@ const subQuery = `
repository {
id
name
fullName
}
}
owner {
@ -58,6 +60,7 @@ export const projectPullRequestsQuery = `
repository {
id
name
fullName
}
}
target {
@ -65,6 +68,7 @@ export const projectPullRequestsQuery = `
repository {
id
name
fullName
}
}
owner {