diff --git a/package.json b/package.json index 0f3329b2eed71f..7ee3292737816c 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "eslint-plugin-react": "^3.3.0", "extract-text-webpack-plugin": "^0.8.2", "file-loader": "^0.8.4", + "history": "^1.9.1", "jquery": "^2.1.3", "json-loader": "^0.5.2", "less": "^2.3.1", @@ -32,7 +33,7 @@ "react-document-title": "^1.0.2", "react-dom": "^0.14.0", "react-lazy-load": "benvinegar/react-lazy-load#64fd716d9fb243e43b1b8040f7fba52f2191f2cb", - "react-router": "benvinegar/react-router#46998d030de3802ace768a673b07272586f7bf99", + "react-router": "^1.0.0-rc3", "react-sticky": "^3.0.0", "reflux": "^0.3.0", "select2": "^3.5.1", diff --git a/src/sentry/static/sentry/app/components/events/eventEntries.jsx b/src/sentry/static/sentry/app/components/events/eventEntries.jsx index df6683917228d4..52c611d03eb286 100644 --- a/src/sentry/static/sentry/app/components/events/eventEntries.jsx +++ b/src/sentry/static/sentry/app/components/events/eventEntries.jsx @@ -15,6 +15,8 @@ var EventEntries = React.createClass({ propTypes: { group: PropTypes.Group.isRequired, event: PropTypes.Event.isRequired, + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired, // TODO(dcramer): ideally isShare would be replaced with simple permission // checks isShare: React.PropTypes.bool @@ -75,6 +77,7 @@ var EventEntries = React.createClass({ } }); + let {orgId, projectId} = this.props; return (
{!utils.objectIsEmpty(evt.errors) && @@ -87,7 +90,9 @@ var EventEntries = React.createClass({ event={evt} /> + event={evt} + orgId={orgId} + projectId={projectId} /> {!utils.objectIsEmpty(evt.user) && { return (
  • - {tag.key} = {tag.value} - + {isUrl(tag.value) && diff --git a/src/sentry/static/sentry/app/components/group/seenInfo.jsx b/src/sentry/static/sentry/app/components/group/seenInfo.jsx index 962a1d710d8400..493661b9f61166 100644 --- a/src/sentry/static/sentry/app/components/group/seenInfo.jsx +++ b/src/sentry/static/sentry/app/components/group/seenInfo.jsx @@ -9,7 +9,9 @@ var SeenInfo = React.createClass({ date: React.PropTypes.any.isRequired, release: React.PropTypes.shape({ version: React.PropTypes.string.isRequired - }) + }), + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired }, render() { @@ -22,7 +24,7 @@ var SeenInfo = React.createClass({
    {utils.defined(release) && [
    Release:
    , -
    +
    ]} ); diff --git a/src/sentry/static/sentry/app/components/group/sidebar.jsx b/src/sentry/static/sentry/app/components/group/sidebar.jsx index 8e18c0130b35de..9b9c52b5ef91eb 100644 --- a/src/sentry/static/sentry/app/components/group/sidebar.jsx +++ b/src/sentry/static/sentry/app/components/group/sidebar.jsx @@ -43,6 +43,8 @@ var GroupSidebar = React.createClass({ return ( diff --git a/src/sentry/static/sentry/app/components/group/tagDistributionMeter.jsx b/src/sentry/static/sentry/app/components/group/tagDistributionMeter.jsx index f2214663f16a79..6b2541c62dd113 100644 --- a/src/sentry/static/sentry/app/components/group/tagDistributionMeter.jsx +++ b/src/sentry/static/sentry/app/components/group/tagDistributionMeter.jsx @@ -1,19 +1,17 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import ApiMixin from "../../mixins/apiMixin"; import PropTypes from "../../proptypes"; import TooltipMixin from "../../mixins/tooltip"; import {escape, percent} from "../../utils"; var TagDistributionMeter = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { group: PropTypes.Group.isRequired, tag: React.PropTypes.string.isRequired, - name: React.PropTypes.string + name: React.PropTypes.string, + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired }, mixins: [ @@ -82,9 +80,7 @@ var TagDistributionMeter = React.createClass({ let otherPct = percent(totalValues - totalVisible, totalValues); let otherPctLabel = Math.floor(otherPct); - let params = {...this.context.router.getCurrentParams()}; - params.tagKey = this.props.tag; - + let {orgId, projectId} = this.props; return (
    {data.topValues.map((value) => { @@ -92,31 +88,29 @@ var TagDistributionMeter = React.createClass({ var pctLabel = Math.floor(pct); return ( - ' + escape(value.name) + '
    ' + pctLabel + '%'}> {pctLabel}% {value.name} - + ); })} {hasOther && - ' + otherPctLabel + '%'}> {otherPctLabel}% Other - + }
  • ); diff --git a/src/sentry/static/sentry/app/components/groupList.jsx b/src/sentry/static/sentry/app/components/groupList.jsx index ab41bfbfe1ffce..83e0cd43b8f4d4 100644 --- a/src/sentry/static/sentry/app/components/groupList.jsx +++ b/src/sentry/static/sentry/app/components/groupList.jsx @@ -11,13 +11,15 @@ import StreamGroup from "../components/stream/group"; import utils from "../utils"; var GroupList = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { query: React.PropTypes.string.isRequired, - canSelectGroups: React.PropTypes.bool + canSelectGroups: React.PropTypes.bool, + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired + }, + + contextTypes: { + location: React.PropTypes.object }, mixins: [ @@ -49,8 +51,11 @@ var GroupList = React.createClass({ this.fetchData(); }, - routeDidChange() { - this.fetchData(); + componentDidUpdate(prevProps) { + if (prevProps.orgId !== this.props.orgId || + prevProps.projectId !== this.props.projectId) { + this.fetchData(); + } }, componentWillUnmount() { @@ -85,15 +90,14 @@ var GroupList = React.createClass({ }, getGroupListEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); - var queryParams = router.getCurrentQuery(); + var queryParams = this.context.location.query; queryParams.limit = 50; queryParams.sort = 'new'; queryParams.query = this.props.query; var querystring = jQuery.param(queryParams); - return '/projects/' + params.orgId + '/' + params.projectId + '/groups/?' + querystring; + let props = this.props; + return '/projects/' + props.orgId + '/' + props.projectId + '/groups/?' + querystring; }, onGroupChange() { @@ -124,12 +128,22 @@ var GroupList = React.createClass({ wrapperClass = "stream-no-bulk-actions"; } + var {orgId, projectId} = this.props; + return (
      {this.state.groupIds.map((id) => { - return ; + return ( + + ); })}
    diff --git a/src/sentry/static/sentry/app/components/header/index.jsx b/src/sentry/static/sentry/app/components/header/index.jsx index 9eb52956b2a7da..a6fd2cdedc5964 100644 --- a/src/sentry/static/sentry/app/components/header/index.jsx +++ b/src/sentry/static/sentry/app/components/header/index.jsx @@ -28,7 +28,7 @@ var Header = React.createClass({
  • Docs
  • {this.props.orgId ? - {logo} + {logo} : {logo} } diff --git a/src/sentry/static/sentry/app/components/header/userNav.jsx b/src/sentry/static/sentry/app/components/header/userNav.jsx index 2ad249792a586e..c4b8cd2a98f455 100644 --- a/src/sentry/static/sentry/app/components/header/userNav.jsx +++ b/src/sentry/static/sentry/app/components/header/userNav.jsx @@ -29,7 +29,7 @@ var UserNav = React.createClass({ title={title}> Account {user.isSuperuser && - Admin + Admin } Sign out diff --git a/src/sentry/static/sentry/app/components/listLink.jsx b/src/sentry/static/sentry/app/components/listLink.jsx index 6153d9df9c9865..5379f9f04ccff0 100644 --- a/src/sentry/static/sentry/app/components/listLink.jsx +++ b/src/sentry/static/sentry/app/components/listLink.jsx @@ -1,14 +1,15 @@ import React from "react"; -import Router from "react-router"; +import {Link, History} from "react-router"; import classNames from 'classnames'; var ListLink = React.createClass({ + mixins: [History], + displayName: 'ListLink', propTypes: { activeClassName: React.PropTypes.string.isRequired, to: React.PropTypes.string.isRequired, - params: React.PropTypes.object, query: React.PropTypes.object, onClick: React.PropTypes.func, @@ -18,10 +19,6 @@ var ListLink = React.createClass({ isActive: React.PropTypes.func }, - contextTypes: { - router: React.PropTypes.func - }, - getDefaultProps() { return { activeClassName: 'active' @@ -29,8 +26,7 @@ var ListLink = React.createClass({ }, isActive() { - return (this.props.isActive || this.context.router.isActive) - .call(this, this.props.to, this.props.params, this.props.query); + return (this.props.isActive || this.history.isActive)(this.props.to, this.props.query); }, getClassName() { @@ -39,7 +35,7 @@ var ListLink = React.createClass({ if (this.props.className) _classNames[this.props.className] = true; - if (this.isActive(this.props.to, this.props.params, this.props.query)) + if (this.isActive()) _classNames[this.props.activeClassName] = true; return classNames(_classNames); @@ -48,9 +44,9 @@ var ListLink = React.createClass({ render() { return (
  • - + {this.props.children} - +
  • ); } diff --git a/src/sentry/static/sentry/app/components/menuItem.jsx b/src/sentry/static/sentry/app/components/menuItem.jsx index 15c42bb9e720f4..94b454e1e11df4 100644 --- a/src/sentry/static/sentry/app/components/menuItem.jsx +++ b/src/sentry/static/sentry/app/components/menuItem.jsx @@ -1,5 +1,5 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import classNames from "classnames"; var MenuItem = React.createClass({ @@ -15,14 +15,9 @@ var MenuItem = React.createClass({ href: React.PropTypes.string, // router link to: React.PropTypes.string, - params: React.PropTypes.object, query: React.PropTypes.object, }, - contextTypes: { - router: React.PropTypes.func - }, - handleClick(e) { if (this.props.onSelect) { e.preventDefault(); @@ -33,16 +28,15 @@ var MenuItem = React.createClass({ renderAnchor() { if (this.props.to) { return ( - {this.props.children} - + ); } return ( diff --git a/src/sentry/static/sentry/app/components/organizations/homeSidebar.jsx b/src/sentry/static/sentry/app/components/organizations/homeSidebar.jsx index 396533ca610dc9..1164b469a12ca2 100644 --- a/src/sentry/static/sentry/app/components/organizations/homeSidebar.jsx +++ b/src/sentry/static/sentry/app/components/organizations/homeSidebar.jsx @@ -13,7 +13,6 @@ var HomeSidebar = React.createClass({ var access = this.getAccess(); var features = this.getFeatures(); var org = this.getOrganization(); - var orgParams = {orgId: org.slug}; var urlPrefix = ConfigStore.get('urlPrefix') + '/organizations/' + org.slug; // Allow injection via getsentry et all @@ -22,13 +21,14 @@ var HomeSidebar = React.createClass({ children.push(cb(org)); }); + let orgId = org.slug; return (
    General
    {access.has('org:read') && diff --git a/src/sentry/static/sentry/app/components/projectHeader/index.jsx b/src/sentry/static/sentry/app/components/projectHeader/index.jsx index 16f159e61bc7a1..0c2c28b3d7f35e 100644 --- a/src/sentry/static/sentry/app/components/projectHeader/index.jsx +++ b/src/sentry/static/sentry/app/components/projectHeader/index.jsx @@ -1,16 +1,11 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import ConfigStore from "../../stores/configStore"; import ProjectSelector from "./projectSelector"; var ProjectHeader = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - render() { - var routeParams = this.context.router.getCurrentParams(); var navSection = this.props.activeSection; var urlPrefix = ConfigStore.get('urlPrefix'); var project = this.props.project; @@ -24,23 +19,23 @@ var ProjectHeader = React.createClass({
    • - + Dashboard - +
    • - + Issues - +
    • - + Releases - +
    • {access.has('project:write') &&
    • - + Settings
    • @@ -49,8 +44,7 @@ var ProjectHeader = React.createClass({
    + projectId={project.slug}/>
    diff --git a/src/sentry/static/sentry/app/components/projectHeader/projectSelector.jsx b/src/sentry/static/sentry/app/components/projectHeader/projectSelector.jsx index 163991e5daedc8..14bf7c3c0c7587 100644 --- a/src/sentry/static/sentry/app/components/projectHeader/projectSelector.jsx +++ b/src/sentry/static/sentry/app/components/projectHeader/projectSelector.jsx @@ -1,22 +1,12 @@ import React from "react"; import ReactDOM from "react-dom"; -import Router from "react-router"; +import {Link} from "react-router"; import jQuery from "jquery"; import ConfigStore from "../../stores/configStore"; import DropdownLink from "../dropdownLink"; import MenuItem from "../menuItem"; var ProjectSelector = React.createClass({ - childContextTypes: { - router: React.PropTypes.func - }, - - getChildContext() { - return { - router: this.props.router - }; - }, - getInitialState() { return { filter: '' @@ -74,25 +64,16 @@ var ProjectSelector = React.createClass({ getProjectNode(team, project, highlightText, hasSingleTeam) { var org = this.props.organization; - var projectRouteParams = { - orgId: org.slug, - projectId: project.slug - }; - + let orgId = org.slug; + let projectId = project.slug; var label = this.getProjectLabel(team, project, hasSingleTeam); - if (!this.props.router) { - return ( - - {this.highlight(label, highlightText)} - - ); - } - return ( - + + {this.highlight(label, highlightText)} ); @@ -113,22 +94,14 @@ var ProjectSelector = React.createClass({ }, getLinkNode(team, project) { - var org = this.props.organization; - var label = this.getProjectLabel(team, project); + let org = this.props.organization; + let label = this.getProjectLabel(team, project); - if (!this.props.router) { - return ( - {label} - ); - } - - var projectRouteParams = { - orgId: org.slug, - projectId: project.slug - }; + let orgId = org.slug; + let projectId = project.slug; return ( - {label} + {label} ); }, diff --git a/src/sentry/static/sentry/app/components/searchBar.jsx b/src/sentry/static/sentry/app/components/searchBar.jsx index be6f3d2d222545..fa08fb0a03ddcf 100644 --- a/src/sentry/static/sentry/app/components/searchBar.jsx +++ b/src/sentry/static/sentry/app/components/searchBar.jsx @@ -3,9 +3,6 @@ import ReactDOM from "react-dom"; import PureRenderMixin from 'react-addons-pure-render-mixin'; var SearchBar = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, mixins: [PureRenderMixin], diff --git a/src/sentry/static/sentry/app/components/stream/group.jsx b/src/sentry/static/sentry/app/components/stream/group.jsx index ace396c363af76..09865c1f19dfb7 100644 --- a/src/sentry/static/sentry/app/components/stream/group.jsx +++ b/src/sentry/static/sentry/app/components/stream/group.jsx @@ -1,7 +1,7 @@ import jQuery from "jquery"; import React from "react"; import Reflux from "reflux"; -import Router from "react-router"; +import {Link} from "react-router"; import AssigneeSelector from "../assigneeSelector"; import Count from "../count"; @@ -15,16 +15,14 @@ import SelectedGroupStore from "../../stores/selectedGroupStore"; import {valueIsEqual} from "../../utils"; var StreamGroup = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - mixins: [ Reflux.listenTo(GroupStore, "onGroupChange") ], propTypes: { id: React.PropTypes.string.isRequired, + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired, statsPeriod: React.PropTypes.string.isRequired, canSelect: React.PropTypes.bool }, @@ -84,8 +82,6 @@ var StreamGroup = React.createClass({ }, render() { - var router = this.context.router; - var params = router.getCurrentParams(); var data = this.state.data; var userCount = data.userCount; @@ -105,11 +101,7 @@ var StreamGroup = React.createClass({ className += " level-" + data.level; - var routeParams = { - orgId: params.orgId, - projectId: params.projectId, - groupId: data.id - }; + let {id, orgId, projectId} = this.props; return (
  • @@ -120,12 +112,12 @@ var StreamGroup = React.createClass({ }

    - + {data.level} {data.title} - +

    {data.culprit} @@ -140,17 +132,17 @@ var StreamGroup = React.createClass({
  • {data.numComments !== 0 &&
  • - + {data.numComments} - +
  • } {data.logger &&
  • - + {data.logger} - +
  • } {data.annotations.map((annotation, key) => { diff --git a/src/sentry/static/sentry/app/components/version.jsx b/src/sentry/static/sentry/app/components/version.jsx index 0a1f1d480e9530..c3be11fbaedd5c 100644 --- a/src/sentry/static/sentry/app/components/version.jsx +++ b/src/sentry/static/sentry/app/components/version.jsx @@ -1,13 +1,11 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; var Version = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { version: React.PropTypes.string.isRequired, + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired }, getDefaultProps() { @@ -17,20 +15,18 @@ var Version = React.createClass({ }, render() { - var version = this.props.version; + // NOTE: version is encoded because it can contain slashes "/", + // which can interfere with URL construction + var version = encodeURIComponent(this.props.version); var shortVersion = version.length === 40 ? version.substr(0, 12) : version; - var params = this.context.router.getCurrentParams(); + + let {orgId, projectId} = this.props; + if (this.props.anchor) { return ( - + {shortVersion} - + ); } return {shortVersion}; diff --git a/src/sentry/static/sentry/app/index.js b/src/sentry/static/sentry/app/index.js index f6187ffd21b498..73a50291591e82 100644 --- a/src/sentry/static/sentry/app/index.js +++ b/src/sentry/static/sentry/app/index.js @@ -44,6 +44,7 @@ export default { Sentry: { api: require("./api"), routes: require("./routes"), + createHistory: require("history/lib/createBrowserHistory"), Alerts: require("./components/alerts"), ConfigStore: require("./stores/configStore"), diff --git a/src/sentry/static/sentry/app/mixins/routeMixin.jsx b/src/sentry/static/sentry/app/mixins/routeMixin.jsx deleted file mode 100644 index 16d2545741ae81..00000000000000 --- a/src/sentry/static/sentry/app/mixins/routeMixin.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from "react"; - -var RouteMixin = { - contextTypes: { - router: React.PropTypes.func - }, - - getInitialState() { - var router = this.context.router; - return { - activeRoutePath: router.getCurrentPath(), - activeParams: router.getCurrentParams(), - activeQuery: router.getCurrentQuery() - }; - }, - - componentWillReceiveProps(nextProps) { - var router = this.context.router; - if (this.state.activeRoutePath != router.getCurrentPath()) { - this.routeDidChange( - this.state.activeRoutePath, - this.state.activeParams, - this.state.activeQuery); - this.setState({ - activeRoutePath: router.getCurrentPath(), - activeParams: router.getCurrentParams(), - activeQuery: router.getCurrentQuery() - }); - } - }, -}; - -export default RouteMixin; - diff --git a/src/sentry/static/sentry/app/routes.jsx b/src/sentry/static/sentry/app/routes.jsx index 4669e2bf5241c6..d95b2c906dbd56 100644 --- a/src/sentry/static/sentry/app/routes.jsx +++ b/src/sentry/static/sentry/app/routes.jsx @@ -1,7 +1,5 @@ import React from "react"; -import Router from "react-router"; -var Route = Router.Route; -var DefaultRoute = Router.DefaultRoute; +import { Route, IndexRoute } from "react-router"; import Admin from "./views/admin"; import AdminOrganizations from "./views/adminOrganizations"; @@ -34,48 +32,51 @@ import SharedGroupDetails from "./views/sharedGroupDetails"; import Stream from "./views/stream"; var routes = ( - - - - + + + - - + + + + - - - - - - - - - - - - + + + + + + + + + + + + + - - - - + + + + - - + - - - - - - + + + + + + - + + ); diff --git a/src/sentry/static/sentry/app/views/admin/index.jsx b/src/sentry/static/sentry/app/views/admin/index.jsx index f72f14d559249a..dd333468142231 100644 --- a/src/sentry/static/sentry/app/views/admin/index.jsx +++ b/src/sentry/static/sentry/app/views/admin/index.jsx @@ -1,6 +1,5 @@ import DocumentTitle from "react-document-title"; import React from "react"; -import Router from "react-router"; import ConfigStore from "../../stores/configStore"; import Footer from "../../components/footer"; @@ -8,10 +7,6 @@ import Header from "../../components/header"; import ListLink from "../../components/listLink"; const Admin = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - getTitle() { return 'Sentry Admin'; }, @@ -29,7 +24,7 @@ const Admin = React.createClass({
    System
    - + {this.props.children}
    diff --git a/src/sentry/static/sentry/app/views/adminOrganizations.jsx b/src/sentry/static/sentry/app/views/adminOrganizations.jsx index 6ea7cebd97d06d..fad75f87d7ee0a 100644 --- a/src/sentry/static/sentry/app/views/adminOrganizations.jsx +++ b/src/sentry/static/sentry/app/views/adminOrganizations.jsx @@ -1,25 +1,18 @@ import jQuery from "jquery"; import React from "react"; -import Router from "react-router"; +import {Link, History} from "react-router"; import api from "../api"; import LoadingError from "../components/loadingError"; import LoadingIndicator from "../components/loadingIndicator"; import Pagination from "../components/pagination"; -import RouteMixin from "../mixins/routeMixin"; import SearchBar from "../components/searchBar.jsx"; const AdminOrganizations = React.createClass({ - mixins: [ - RouteMixin - ], - - contextTypes: { - router: React.PropTypes.func - }, + mixins: [History], getInitialState() { - let queryParams = this.context.router.getCurrentQuery(); + let queryParams = this.props.location.query; return { data: [], @@ -39,7 +32,7 @@ const AdminOrganizations = React.createClass({ }, fetchData() { - let queryParams = this.context.router.getCurrentQuery(); + let queryParams = this.props.location.query; api.request(`/organizations/`, { method: 'GET', @@ -61,33 +54,29 @@ const AdminOrganizations = React.createClass({ }); }, - routeDidChange() { - let queryParams = this.context.router.getCurrentQuery(); - this.setState({ - query: queryParams.query, - loading: true, - error: false - }, this.fetchData); + componentWillReceiveProps(nextProps) { + if (nextProps.location.search !== this.props.location.search) { + this.setState({ + query: this.props.location.query, + loading: true, + error: false + }, this.fetchData); + } }, onPage(cursor) { - let router = this.context.router; - let params = router.getCurrentParams(); - let queryParams = jQuery.extend({}, router.getCurrentQuery(), { + let queryParams = jQuery.extend({}, this.props.location.query, { cursor: cursor }); - router.transitionTo('adminOrganizations', params, queryParams); + this.history.pushState(null, '/manage/organizations/', queryParams); }, onSearch(query) { - let router = this.context.router; - let params = router.getCurrentParams(); - let targetQueryParams = {}; if (query !== '') targetQueryParams.query = query; - router.transitionTo("adminOrganizations", params, targetQueryParams); + this.history.pushState(null, '/manage/organizations/', targetQueryParams); }, renderLoading() { @@ -126,9 +115,9 @@ const AdminOrganizations = React.createClass({ return ( - + {item.name} -
    +
    {item.slug} — diff --git a/src/sentry/static/sentry/app/views/adminOverview/index.jsx b/src/sentry/static/sentry/app/views/adminOverview/index.jsx index 86dc9ad9d28d05..b6afaaf0051587 100644 --- a/src/sentry/static/sentry/app/views/adminOverview/index.jsx +++ b/src/sentry/static/sentry/app/views/adminOverview/index.jsx @@ -4,10 +4,6 @@ import ApiChart from "./apiChart"; import EventChart from "./eventChart"; const AdminOverview = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - getInitialState() { return { since: new Date().getTime() / 1000 - 3600 * 24 * 7, diff --git a/src/sentry/static/sentry/app/views/app.jsx b/src/sentry/static/sentry/app/views/app.jsx index d8fb5affe5fe1b..f762de3f69e58d 100644 --- a/src/sentry/static/sentry/app/views/app.jsx +++ b/src/sentry/static/sentry/app/views/app.jsx @@ -1,6 +1,5 @@ import React from "react"; import api from "../api"; -import Router from "react-router"; import Alerts from "../components/alerts"; import AlertActions from "../actions/alertActions.jsx"; import Indicators from "../components/indicators"; @@ -64,7 +63,7 @@ var App = React.createClass({
    - + {this.props.children}
    ); } diff --git a/src/sentry/static/sentry/app/views/groupActivity/index.jsx b/src/sentry/static/sentry/app/views/groupActivity/index.jsx index 6d3d050f53cb40..0d778c9230f374 100644 --- a/src/sentry/static/sentry/app/views/groupActivity/index.jsx +++ b/src/sentry/static/sentry/app/views/groupActivity/index.jsx @@ -48,9 +48,6 @@ var formatActivity = function(item) { var GroupActivity = React.createClass({ // TODO(dcramer): only re-render on group/activity change - contextTypes: { - router: React.PropTypes.func - }, mixins: [GroupState], diff --git a/src/sentry/static/sentry/app/views/groupDetails.jsx b/src/sentry/static/sentry/app/views/groupDetails.jsx index b71a69b19697c6..cd2c465836e0be 100644 --- a/src/sentry/static/sentry/app/views/groupDetails.jsx +++ b/src/sentry/static/sentry/app/views/groupDetails.jsx @@ -1,6 +1,5 @@ import React from "react"; import Reflux from "reflux"; -import Router from "react-router"; import api from "../api"; import DocumentTitle from "react-document-title"; import GroupHeader from "./groupDetails/header"; @@ -14,19 +13,10 @@ const ERROR_TYPES = { }; var GroupDetails = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - mixins: [ Reflux.listenTo(GroupStore, "onGroupChange") ], - propTypes: { - memberList: React.PropTypes.instanceOf(Array).isRequired, - setProjectNavSection: React.PropTypes.func.isRequired - }, - childContextTypes: { group: PropTypes.Group, }, @@ -83,7 +73,7 @@ var GroupDetails = React.createClass({ }, onGroupChange(itemIds) { - var id = this.context.router.getCurrentParams().groupId; + var id = this.props.params.groupId; if (itemIds.has(id)) { this.setState({ group: GroupStore.get(id), @@ -92,7 +82,7 @@ var GroupDetails = React.createClass({ }, getGroupDetailsEndpoint() { - var id = this.context.router.getCurrentParams().groupId; + var id = this.props.params.groupId; return '/groups/' + id + '/'; }, @@ -105,7 +95,7 @@ var GroupDetails = React.createClass({ render() { var group = this.state.group; - var params = this.context.router.getCurrentParams(); + var params = this.props.params; if (this.state.error) { switch (this.state.errorType) { @@ -127,9 +117,10 @@ var GroupDetails = React.createClass({ projectId={params.projectId} group={group} memberList={this.props.memberList} /> - + {React.cloneElement(this.props.children, { + memberList: this.props.memberList, + group: group + })} ); diff --git a/src/sentry/static/sentry/app/views/groupDetails/actions.jsx b/src/sentry/static/sentry/app/views/groupDetails/actions.jsx index c6f8cc64cb684c..2706622f153bec 100644 --- a/src/sentry/static/sentry/app/views/groupDetails/actions.jsx +++ b/src/sentry/static/sentry/app/views/groupDetails/actions.jsx @@ -1,4 +1,5 @@ import React from "react"; +import {History} from "react-router"; import api from "../../api"; import DropdownLink from "../../components/dropdownLink"; import GroupState from "../../mixins/groupState"; @@ -7,11 +8,10 @@ import MenuItem from "../../components/menuItem"; import LinkWithConfirmation from "../../components/linkWithConfirmation"; var GroupActions = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - - mixins: [GroupState], + mixins: [ + GroupState, + History + ], onDelete() { var group = this.getGroup(); @@ -29,10 +29,7 @@ var GroupActions = React.createClass({ } }); - this.context.router.transitionTo('stream', { - orgId: org.slug, - projectId: project.slug - }); + this.history.pushState(null, `/${org.slug}/${project.slug}/`); }, onToggleResolve() { diff --git a/src/sentry/static/sentry/app/views/groupDetails/eventToolbar.jsx b/src/sentry/static/sentry/app/views/groupDetails/eventToolbar.jsx index 78887ebdff6c55..96fce5fa4cbaca 100644 --- a/src/sentry/static/sentry/app/views/groupDetails/eventToolbar.jsx +++ b/src/sentry/static/sentry/app/views/groupDetails/eventToolbar.jsx @@ -1,4 +1,4 @@ -import Router from "react-router"; +import {Link} from "react-router"; import React from "react"; import PropTypes from "../../proptypes"; import DateTime from "../../components/dateTime"; @@ -15,55 +15,48 @@ var GroupEventToolbar = React.createClass({ render() { let evt = this.props.event; - let params = { - orgId: this.props.orgId, - projectId: this.props.projectId, - groupId: this.props.group.id - }; + let {orgId, projectId} = this.props; + let groupId = this.props.group.id; let eventNavNodes = [ (evt.previousEventID ? - - + : ), (evt.previousEventID ? - Older + to={`/${orgId}/${projectId}/group/${groupId}/events/${evt.previousEventID}/`} + className="btn btn-default">Older : Older ), (evt.nextEventID ? - Newer + to={`/${orgId}/${projectId}/group/${groupId}/events/${evt.nextEventID}/`} + className="btn btn-default">Newer : Newer ), (evt.nextEventID ? - - + : @@ -72,7 +65,7 @@ var GroupEventToolbar = React.createClass({ // TODO: possible to define this as a route in react-router, but without a corresponding // React component? - let jsonUrl = `/${params.orgId}/${params.projectId}/group/${params.groupId}/events/${evt.id}/json/`; + let jsonUrl = `/${orgId}/${projectId}/group/${groupId}/events/${evt.id}/json/`; return (
    diff --git a/src/sentry/static/sentry/app/views/groupDetails/header.jsx b/src/sentry/static/sentry/app/views/groupDetails/header.jsx index 9714a4f3fbcc24..c0bfaf4ff0f6fc 100644 --- a/src/sentry/static/sentry/app/views/groupDetails/header.jsx +++ b/src/sentry/static/sentry/app/views/groupDetails/header.jsx @@ -1,5 +1,6 @@ import React from "react"; -import Router from "react-router"; +// import Router from "react-router"; +import {Link, History} from "react-router"; import api from "../../api"; import AssigneeSelector from "../../components/assigneeSelector"; import Count from "../../components/count"; @@ -10,10 +11,13 @@ import ListLink from "../../components/listLink"; import ProjectState from "../../mixins/projectState"; var GroupHeader = React.createClass({ - mixins: [ProjectState], + mixins: [ + ProjectState, + History + ], contextTypes: { - router: React.PropTypes.func.isRequired + location: React.PropTypes.object }, propTypes: { @@ -41,9 +45,8 @@ var GroupHeader = React.createClass({ }, onShare() { - return this.context.router.transitionTo('sharedGroupDetails', { - shareId: this.props.group.shareId - }); + let {shareId} = this.props.group; + return this.history.pushState(null, `/share/group/${shareId}/`); }, onTogglePublic() { @@ -82,7 +85,9 @@ var GroupHeader = React.createClass({ className += " isResolved"; } - var params = this.context.router.getCurrentParams(); + let groupId = group.id, + projectId = this.getProject().slug, + orgId = this.getOrganization().slug; return (
    @@ -96,9 +101,9 @@ var GroupHeader = React.createClass({ {group.culprit} {group.logger && - + {group.logger} - + } {group.annotations.map((annotation) => { @@ -146,25 +151,27 @@ var GroupHeader = React.createClass({
      - + Details - + Comments {group.numComments} {features.has('user-reports') && - + User Reports {group.userReportCount} } - + Tags - + Similar Events
    diff --git a/src/sentry/static/sentry/app/views/groupEventDetails.jsx b/src/sentry/static/sentry/app/views/groupEventDetails.jsx index 92a5afba400b92..23a206a89b3f46 100644 --- a/src/sentry/static/sentry/app/views/groupEventDetails.jsx +++ b/src/sentry/static/sentry/app/views/groupEventDetails.jsx @@ -8,18 +8,12 @@ import GroupState from "../mixins/groupState"; import MutedBox from "../components/mutedBox"; import LoadingError from "../components/loadingError"; import LoadingIndicator from "../components/loadingIndicator"; -import RouteMixin from "../mixins/routeMixin"; var GroupEventDetails = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - mixins: [ ApiMixin, - GroupState, - RouteMixin + GroupState ], getInitialState() { @@ -35,12 +29,14 @@ var GroupEventDetails = React.createClass({ this.fetchData(); }, - routeDidChange(prevPath) { - this.fetchData(); + componentDidUpdate(prevProps) { + if (prevProps.params.eventId !== this.props.params.eventId) { + this.fetchData(); + } }, fetchData() { - var eventId = this.context.router.getCurrentParams().eventId || 'latest'; + var eventId = this.props.params.eventId || 'latest'; var url = (eventId === 'latest' || eventId === 'oldest' ? '/groups/' + this.getGroup().id + '/events/' + eventId + '/' : @@ -79,7 +75,7 @@ var GroupEventDetails = React.createClass({ render() { var group = this.getGroup(); var evt = this.state.event; - var params = this.context.router.getCurrentParams(); + var params = this.props.params; return (
    @@ -98,7 +94,11 @@ var GroupEventDetails = React.createClass({ : (this.state.error ? : - + )}
    diff --git a/src/sentry/static/sentry/app/views/groupEvents.jsx b/src/sentry/static/sentry/app/views/groupEvents.jsx index 8981f80883acd2..ee653ad3ba3c34 100644 --- a/src/sentry/static/sentry/app/views/groupEvents.jsx +++ b/src/sentry/static/sentry/app/views/groupEvents.jsx @@ -1,9 +1,8 @@ import React from "react"; -import Router from "react-router"; +import {History, Link} from "react-router"; import api from "../api"; import GroupState from "../mixins/groupState"; -import RouteMixin from "../mixins/routeMixin"; import DateTime from "../components/dateTime"; import Gravatar from "../components/gravatar"; @@ -12,13 +11,9 @@ import LoadingIndicator from "../components/loadingIndicator"; import Pagination from "../components/pagination"; var GroupEvents = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - mixins: [ GroupState, - RouteMixin + History ], getInitialState() { @@ -35,7 +30,7 @@ var GroupEvents = React.createClass({ }, fetchData() { - var queryParams = this.context.router.getCurrentQuery(); + var queryParams = this.props.location.query; this.setState({ loading: true, @@ -62,15 +57,21 @@ var GroupEvents = React.createClass({ }); }, - routeDidChange() { - this.fetchData(); + componentDidUpdate(prevProps) { + if (prevProps.params.groupId !== this.props.params.groupId) { + this.fetchData(); + } }, onPage(cursor) { - var router = this.context.router; - var queryParams = {...router.getCurrentQuery(), cursor: cursor}; + var queryParams = {...this.context.location.query, cursor: cursor}; - router.transitionTo('groupEvents', this.context.router.getCurrentParams(), queryParams); + let {orgId, projectId, groupId} = this.props.params; + this.history.pushState( + null, + `/${orgId}/${projectId}/group/${groupId}/events/`, + queryParams + ); }, render() { @@ -93,13 +94,9 @@ var GroupEvents = React.createClass({ } } + var {orgId, projectId, groupId} = this.props.params; + var children = this.state.eventList.map((event, eventIdx) => { - var linkParams = { - orgId: this.getOrganization().slug, - projectId: this.getProject().slug, - groupId: this.getGroup().id, - eventId: event.id - }; var tagMap = {}; event.tags.forEach((tag) => { tagMap[tag.key] = tag.value; @@ -109,10 +106,9 @@ var GroupEvents = React.createClass({
    - + - + {event.eventID}
    @@ -144,17 +140,19 @@ var GroupEvents = React.createClass({
    - - {tagList.map((tag) => { - return ( - - ); - })} - {hasUser && - - } + + + {tagList.map((tag) => { + return ( + + ); + })} + {hasUser && + + } + {children} diff --git a/src/sentry/static/sentry/app/views/groupTagValues.jsx b/src/sentry/static/sentry/app/views/groupTagValues.jsx index 11b9d27280f0d4..9cd5ac9e74f50c 100644 --- a/src/sentry/static/sentry/app/views/groupTagValues.jsx +++ b/src/sentry/static/sentry/app/views/groupTagValues.jsx @@ -1,5 +1,5 @@ import React from "react"; -import Router from "react-router"; +import {Link, History} from "react-router"; import jQuery from "jquery"; import api from "../api"; import Count from "../components/count"; @@ -11,11 +11,10 @@ import TimeSince from "../components/timeSince"; import {isUrl, percent} from "../utils"; var GroupTagValues = React.createClass({ - mixins: [GroupState], - - contextTypes: { - router: React.PropTypes.func - }, + mixins: [ + History, + GroupState + ], getInitialState() { return { @@ -32,9 +31,8 @@ var GroupTagValues = React.createClass({ }, fetchData() { - var router = this.context.router; - var params = router.getCurrentParams(); - var queryParams = router.getCurrentQuery(); + var params = this.props.params; + var queryParams = this.props.location.query; var querystring = jQuery.param(queryParams); this.setState({ @@ -75,12 +73,11 @@ var GroupTagValues = React.createClass({ }, onPage(cursor) { - var router = this.context.router; - var queryParams = jQuery.extend({}, router.getCurrentQuery(), { + var queryParams = jQuery.extend({}, this.props.location.query, { cursor: cursor }); - router.transitionTo('groupTagValues', router.getCurrentParams(), queryParams); + this.history.pushState(null, this.props.location.path, queryParams); }, render() { @@ -90,11 +87,11 @@ var GroupTagValues = React.createClass({ return ; } - var router = this.context.router; var tagKey = this.state.tagKey; var children = this.state.tagValueList.map((tagValue, tagValueIdx) => { var pct = percent(tagValue.count, tagKey.totalValues).toFixed(2); - var params = router.getCurrentParams(); + let orgId = this.getOrganization().slug; + let projectId = this.getProject().slug; return (
    ID - {tag.name} - User
    ID + {tag.name} + User
    @@ -102,12 +99,11 @@ var GroupTagValues = React.createClass({ {pct}% - {tagValue.name} - + {isUrl(tagValue.value) && diff --git a/src/sentry/static/sentry/app/views/groupTags.jsx b/src/sentry/static/sentry/app/views/groupTags.jsx index 878f39adf3171d..b2bfbc78bde25a 100644 --- a/src/sentry/static/sentry/app/views/groupTags.jsx +++ b/src/sentry/static/sentry/app/views/groupTags.jsx @@ -1,5 +1,4 @@ import React from "react"; -import Router from "react-router"; import {Link} from "react-router"; import ApiMixin from "../mixins/apiMixin"; import Count from "../components/count"; @@ -14,10 +13,6 @@ var GroupTags = React.createClass({ GroupState ], - contextTypes: { - router: React.PropTypes.func - }, - getInitialState() { return { tagList: null, @@ -69,41 +64,35 @@ var GroupTags = React.createClass({ } var children = []; - var router = this.context.router; + + var orgId = this.getOrganization().slug; + var projectId = this.getProject().slug; + var groupId = this.getGroup().id; if (this.state.tagList) { children = this.state.tagList.map((tag, tagIdx) => { var valueChildren = tag.topValues.map((tagValue, tagValueIdx) => { var pct = percent(tagValue.count, tag.totalValues); - var params = router.getCurrentParams(); return (
  • - {tagValue.name} - +
  • ); }); - var routeParams = { - orgId: this.getOrganization().slug, - projectId: this.getProject().slug, - groupId: this.getGroup().id, - tagKey: tag.key - }; - return (
    - More Details + More Details
    {tag.name} ()
    diff --git a/src/sentry/static/sentry/app/views/groupUserReports.jsx b/src/sentry/static/sentry/app/views/groupUserReports.jsx index 938ce065513df3..f1ad1e0b048f6c 100644 --- a/src/sentry/static/sentry/app/views/groupUserReports.jsx +++ b/src/sentry/static/sentry/app/views/groupUserReports.jsx @@ -1,5 +1,6 @@ import $ from "jquery"; import React from "react"; +import {History} from "react-router"; import api from "../api"; import Gravatar from "../components/gravatar"; import GroupState from "../mixins/groupState"; @@ -9,12 +10,10 @@ import TimeSince from "../components/timeSince"; import utils from "../utils"; var GroupUserReports = React.createClass({ - // TODO(dcramer): only re-render on group/activity change - contextTypes: { - router: React.PropTypes.func - }, - - mixins: [GroupState], + mixins: [ + GroupState, + History + ], getInitialState() { return { @@ -30,7 +29,7 @@ var GroupUserReports = React.createClass({ }, fetchData() { - var queryParams = this.context.router.getCurrentQuery(); + var queryParams = this.props.params; var querystring = $.param(queryParams); this.setState({ @@ -57,10 +56,14 @@ var GroupUserReports = React.createClass({ }, onPage(cursor) { - var router = this.context.router; - var queryParams = $.extend({}, router.getCurrentQuery(), {cursor: cursor}); + var queryParams = $.extend({}, this.props.location.query, {cursor: cursor}); - router.transitionTo('groupUserReports', this.context.router.getCurrentParams(), queryParams); + let {orgId, projectId, groupId} = this.props.params; + this.history.pushState( + null, + `/${orgId}/${projectId}/group/${groupId}/reports/`, + queryParams + ); }, render() { diff --git a/src/sentry/static/sentry/app/views/organizationDetails.jsx b/src/sentry/static/sentry/app/views/organizationDetails.jsx index 94c38564d809e9..bb617c1c8a9c04 100644 --- a/src/sentry/static/sentry/app/views/organizationDetails.jsx +++ b/src/sentry/static/sentry/app/views/organizationDetails.jsx @@ -1,5 +1,4 @@ import React from "react"; -import Router from "react-router"; import api from "../api"; import DocumentTitle from "react-document-title"; import Footer from "../components/footer"; @@ -8,7 +7,6 @@ import HookStore from "../stores/hookStore"; import LoadingError from "../components/loadingError"; import LoadingIndicator from "../components/loadingIndicator"; import PropTypes from "../proptypes"; -import RouteMixin from "../mixins/routeMixin"; import TeamStore from "../stores/teamStore"; const ERROR_TYPES = { @@ -16,18 +14,10 @@ const ERROR_TYPES = { }; var OrganizationDetails = React.createClass({ - mixins: [ - RouteMixin - ], - childContextTypes: { organization: PropTypes.Organization }, - contextTypes: { - router: React.PropTypes.func - }, - getChildContext() { return { organization: this.state.organization @@ -55,10 +45,8 @@ var OrganizationDetails = React.createClass({ this.setState(this.getInitialState(), this.fetchData); }, - routeDidChange(nextPath, nextParams) { - var router = this.context.router; - var params = router.getCurrentParams(); - if (nextParams.orgId != params.orgId) { + componentWillReceiveProps(nextProps) { + if (nextProps.params.orgId !== this.props.params.orgId) { this.remountComponent(); } }, @@ -92,9 +80,7 @@ var OrganizationDetails = React.createClass({ }, getOrganizationDetailsEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); - return '/organizations/' + params.orgId + '/'; + return '/organizations/' + this.props.params.orgId + '/'; }, getTitle() { @@ -130,14 +116,14 @@ var OrganizationDetails = React.createClass({ children.push(cb(org)); }); - var params = this.context.router.getCurrentParams(); + var params = this.props.params; return (
    {children}
    - + {this.props.children}
    diff --git a/src/sentry/static/sentry/app/views/organizationMembers.jsx b/src/sentry/static/sentry/app/views/organizationMembers.jsx deleted file mode 100644 index d523d23345296e..00000000000000 --- a/src/sentry/static/sentry/app/views/organizationMembers.jsx +++ /dev/null @@ -1,129 +0,0 @@ -import React from "react"; -import api from "../api"; -import Gravatar from "../components/gravatar"; -import LoadingError from "../components/loadingError"; -import LoadingIndicator from "../components/loadingIndicator"; -import OrganizationHomeContainer from "../components/organizations/homeContainer"; -import OrganizationState from "../mixins/organizationState"; -import RouteMixin from "../mixins/routeMixin"; - -var OrganizationMembers = React.createClass({ - mixins: [ - OrganizationState, - RouteMixin - ], - - contextTypes: { - router: React.PropTypes.func - }, - - getInitialState() { - return { - loading: false, - error: false, - memberList: null - }; - }, - - componentWillMount() { - this.fetchData(); - }, - - routeDidChange(nextPath, nextParams) { - var router = this.context.router; - if (nextParams.orgId != router.getCurrentParams().orgId) { - this.fetchData(); - } - }, - - fetchData() { - this.setState({ - loading: true, - error: false - }); - - api.request(this.getOrganizationMembersEndpoint(), { - success: (data) => { - this.setState({ - memberList: data, - loading: false - }); - }, - error: () => { - this.setState({ - error: true, - loading: false - }); - } - }); - }, - - getOrganizationMembersEndpoint() { - var params = this.getCurrentParams(); - return '/organizations/' + params.orgId + '/members/'; - }, - - render() { - if (this.state.loading) { - return ; - } else if (this.state.error) { - return ; - } - - return ( - -

    Members

    -

    Members of your organization gain slightly elevated permissions over individual team members. For example, organization administrators can create new teams as well as manage all organization settings (including the list of admins).

    -
    - - - - - - - - - - - - - - - - - - - {this.state.memberList.map((member) => { - return ( - - - - - - - - ); - })} - -
    Member RoleTeams 
    - - {member.name} -
    - {member.email} -
    -
    - Missing SSO Link - Resend invite - {member.roleName} - - - - Remove - -
    -
    - ); - } -}); - -export default OrganizationMembers; diff --git a/src/sentry/static/sentry/app/views/organizationStats/index.jsx b/src/sentry/static/sentry/app/views/organizationStats/index.jsx index 72ba90de50affe..edc5bc011e67a4 100644 --- a/src/sentry/static/sentry/app/views/organizationStats/index.jsx +++ b/src/sentry/static/sentry/app/views/organizationStats/index.jsx @@ -6,20 +6,14 @@ import LoadingError from "../../components/loadingError"; import LoadingIndicator from "../../components/loadingIndicator"; import OrganizationHomeContainer from "../../components/organizations/homeContainer"; import OrganizationState from "../../mixins/organizationState"; -import RouteMixin from "../../mixins/routeMixin"; import ProjectTable from "./projectTable"; var OrganizationStats = React.createClass({ mixins: [ - OrganizationState, - RouteMixin + OrganizationState ], - contextTypes: { - router: React.PropTypes.func - }, - getInitialState() { let until = Math.floor(new Date().getTime() / 1000); let since = until - 3600 * 24 * 7; @@ -46,7 +40,14 @@ var OrganizationStats = React.createClass({ this.fetchData(); }, - componentDidUpdate() { + componentDidUpdate(prevProps) { + let prevParams = prevProps.params, + currentParams = this.props.params; + + if (prevParams.orgId !== currentParams.orgId) { + this.fetchData(); + } + var state = this.state; if (state.statsLoading && !state.statsRequestsPending) { this.processOrgData(); @@ -56,13 +57,6 @@ var OrganizationStats = React.createClass({ } }, - routeDidChange(nextPath, nextParams) { - var router = this.context.router; - if (nextParams.orgId != router.getCurrentParams().orgId) { - this.fetchData(); - } - }, - fetchData() { this.setState({ statsError: false, @@ -145,14 +139,12 @@ var OrganizationStats = React.createClass({ }, getOrganizationStatsEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); + var params = this.props.params; return '/organizations/' + params.orgId + '/stats/'; }, getOrganizationProjectsEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); + var params = this.props.params; return '/organizations/' + params.orgId + '/projects/'; }, diff --git a/src/sentry/static/sentry/app/views/organizationTeams/expandedTeamList.jsx b/src/sentry/static/sentry/app/views/organizationTeams/expandedTeamList.jsx index 05fe67a57f69ab..4e3c7b58613038 100644 --- a/src/sentry/static/sentry/app/views/organizationTeams/expandedTeamList.jsx +++ b/src/sentry/static/sentry/app/views/organizationTeams/expandedTeamList.jsx @@ -1,5 +1,5 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import LazyLoad from "react-lazy-load"; import api from "../../api"; @@ -15,10 +15,6 @@ var ExpandedTeamList = React.createClass({ projectStats: React.PropTypes.object }, - contextTypes: { - router: React.PropTypes.func - }, - leaveTeam(team) { // TODO(dcramer): handle loading indicator api.leaveTeam({ @@ -104,10 +100,9 @@ var ExpandedTeamList = React.createClass({
    - + {project.name} - +
    diff --git a/src/sentry/static/sentry/app/views/organizationTeams/index.jsx b/src/sentry/static/sentry/app/views/organizationTeams/index.jsx index 7935b4dd49641a..c77a2dcab043ba 100644 --- a/src/sentry/static/sentry/app/views/organizationTeams/index.jsx +++ b/src/sentry/static/sentry/app/views/organizationTeams/index.jsx @@ -22,10 +22,6 @@ var OrganizationTeams = React.createClass({ }) ], - contextTypes: { - router: React.PropTypes.func - }, - getInitialState() { return { activeNav: 'your-teams', @@ -57,8 +53,7 @@ var OrganizationTeams = React.createClass({ }, getOrganizationStatsEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); + var params = this.props.params; return '/organizations/' + params.orgId + '/stats/'; }, @@ -81,6 +76,9 @@ var OrganizationTeams = React.createClass({ }, render() { + if (!this.context.organization) + return null; + var access = this.getAccess(); var features = this.getFeatures(); var org = this.getOrganization(); @@ -140,7 +138,7 @@ var OrganizationTeams = React.createClass({ } - + ); diff --git a/src/sentry/static/sentry/app/views/organizationTeams/organizationStatOverview.jsx b/src/sentry/static/sentry/app/views/organizationTeams/organizationStatOverview.jsx index 1dd591488f66b4..949d3c5503f529 100644 --- a/src/sentry/static/sentry/app/views/organizationTeams/organizationStatOverview.jsx +++ b/src/sentry/static/sentry/app/views/organizationTeams/organizationStatOverview.jsx @@ -1,5 +1,5 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import classNames from "classnames"; import api from "../../api"; @@ -12,8 +12,12 @@ var OrganizationStatOverview = React.createClass({ OrganizationState ], + propTypes: { + orgId: React.PropTypes.string + }, + contextTypes: { - router: React.PropTypes.func + location: React.PropTypes.object }, getInitialState() { @@ -28,9 +32,7 @@ var OrganizationStatOverview = React.createClass({ }, getOrganizationStatsEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); - return '/organizations/' + params.orgId + '/stats/'; + return '/organizations/' + this.props.orgId + '/stats/'; }, fetchData() { @@ -72,7 +74,6 @@ var OrganizationStatOverview = React.createClass({ if (!defined(this.state.epm) || !defined(this.state.totalRejected)) return null; - var router = this.context.router; var access = this.getAccess(); var rejectedClasses = ['count']; @@ -86,8 +87,9 @@ var OrganizationStatOverview = React.createClass({
    Rejected in last 24h

    {this.state.totalRejected}

    {access.has('org:read') && - View all stats + + View all stats + } ); diff --git a/src/sentry/static/sentry/app/views/projectDashboard.jsx b/src/sentry/static/sentry/app/views/projectDashboard.jsx index baece0b46e9b9b..22198a5ce49fd4 100644 --- a/src/sentry/static/sentry/app/views/projectDashboard.jsx +++ b/src/sentry/static/sentry/app/views/projectDashboard.jsx @@ -1,27 +1,17 @@ import jQuery from "jquery"; import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import EventList from "./projectDashboard/eventList"; import ProjectState from "../mixins/projectState"; import ProjectChart from "./projectDashboard/chart"; -import RouteMixin from "../mixins/routeMixin"; var ProjectDashboard = React.createClass({ mixins: [ - ProjectState, - RouteMixin + ProjectState ], - contextTypes: { - router: React.PropTypes.func - }, - - propTypes: { - setProjectNavSection: React.PropTypes.func.isRequired - }, - getDefaultProps() { return { defaultStatsPeriod: "24h" @@ -36,27 +26,19 @@ var ProjectDashboard = React.createClass({ componentWillMount() { this.props.setProjectNavSection('dashboard'); - this._path = this.context.router.getCurrentPath(); }, - routeDidChange() { - this.setState(this.getQueryStringState()); - }, - - shouldComponentUpdate(nextProps, nextState) { - if (this._path !== this.context.router.getCurrentPath()) { - this._path = this.context.router.getCurrentPath(); - return true; - } - return false; + componentWillReceiveProps(nextProps) { + this.setState(this.getQueryStringState(nextProps)); }, - getQueryStringState() { - var currentQuery = this.context.router.getCurrentQuery(); + getQueryStringState(props) { + props = props || this.props; + var currentQuery = props.location.query; var statsPeriod = currentQuery.statsPeriod; if (statsPeriod !== '1w' && statsPeriod !== '24h' && statsPeriod != '1h') { - statsPeriod = this.props.defaultStatsPeriod; + statsPeriod = props.defaultStatsPeriod; } return { @@ -90,8 +72,7 @@ var ProjectDashboard = React.createClass({ }, getTrendingEventsEndpoint(dateSince) { - let router = this.context.router; - let params = router.getCurrentParams(); + let params = this.props.params; let qs = jQuery.param({ sort: "priority", query: "is:unresolved", @@ -101,8 +82,7 @@ var ProjectDashboard = React.createClass({ }, getNewEventsEndpoint(dateSince) { - let router = this.context.router; - let params = router.getCurrentParams(); + let params = this.props.params; let qs = jQuery.param({ sort: "new", query: "is:unresolved", @@ -115,33 +95,29 @@ var ProjectDashboard = React.createClass({ let {statsPeriod} = this.state; let dateSince = this.getStatsPeriodBeginTimestamp(statsPeriod); let resolution = this.getStatsPeriodResolution(statsPeriod); - let router = this.context.router; - let routeName = "projectDashboard"; - let routeParams = router.getCurrentParams(); - let routeQuery = router.getCurrentQuery(); + let {orgId, projectId} = this.props.params; + let url = `/${orgId}/${projectId}/dashboard/`; + let routeQuery = this.props.location.query; return (
    - 1h - 1h + 24h - 24h + 1w + className={"btn btn-sm btn-default" + (statsPeriod === "1w" ? " active" : "")}>1w

    Overview

    diff --git a/src/sentry/static/sentry/app/views/projectDashboard/chart.jsx b/src/sentry/static/sentry/app/views/projectDashboard/chart.jsx index 9e2442de8d9576..74bb3f561bfe51 100644 --- a/src/sentry/static/sentry/app/views/projectDashboard/chart.jsx +++ b/src/sentry/static/sentry/app/views/projectDashboard/chart.jsx @@ -11,10 +11,6 @@ var ProjectChart = React.createClass({ ProjectState, ], - contextTypes: { - router: React.PropTypes.func - }, - getInitialState() { return { loading: true, diff --git a/src/sentry/static/sentry/app/views/projectDashboard/eventList.jsx b/src/sentry/static/sentry/app/views/projectDashboard/eventList.jsx index b0d417cdecd471..e55f5a3248a041 100644 --- a/src/sentry/static/sentry/app/views/projectDashboard/eventList.jsx +++ b/src/sentry/static/sentry/app/views/projectDashboard/eventList.jsx @@ -6,10 +6,6 @@ import LoadingIndicator from "../../components/loadingIndicator"; import EventNode from "./eventNode"; var EventList = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { title: React.PropTypes.string.isRequired, endpoint: React.PropTypes.string.isRequired diff --git a/src/sentry/static/sentry/app/views/projectDashboard/eventNode.jsx b/src/sentry/static/sentry/app/views/projectDashboard/eventNode.jsx index ff9c28cf40f51f..91e5b8eb56548b 100644 --- a/src/sentry/static/sentry/app/views/projectDashboard/eventNode.jsx +++ b/src/sentry/static/sentry/app/views/projectDashboard/eventNode.jsx @@ -1,5 +1,5 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import Count from "../../components/count"; import PropTypes from "../../proptypes"; import TimeSince from "../../components/timeSince"; @@ -16,16 +16,14 @@ var EventNode = React.createClass({ var group = this.props.group; var org = this.getOrganization(); - var params = { - orgId: org.slug, - projectId: group.project.slug, - groupId: group.id - }; + let orgId = org.slug; + let projectId = group.project.slug; + let groupId = group.id; return ( - + {title} - + ); }, diff --git a/src/sentry/static/sentry/app/views/projectDetails.jsx b/src/sentry/static/sentry/app/views/projectDetails.jsx index 1b975fbed0bc56..e628d28d9ad929 100644 --- a/src/sentry/static/sentry/app/views/projectDetails.jsx +++ b/src/sentry/static/sentry/app/views/projectDetails.jsx @@ -1,6 +1,5 @@ import React from "react"; import Reflux from "reflux"; -import Router from "react-router"; import api from "../api"; import DocumentTitle from "react-document-title"; import MemberListStore from "../stores/memberListStore"; @@ -9,7 +8,6 @@ import LoadingIndicator from "../components/loadingIndicator"; import MissingProjectMembership from "../components/missingProjectMembership"; import ProjectHeader from "../components/projectHeader"; import OrganizationState from "../mixins/organizationState"; -import RouteMixin from "../mixins/routeMixin"; import PropTypes from "../proptypes"; import TeamStore from "../stores/teamStore"; @@ -22,9 +20,7 @@ var ProjectDetails = React.createClass({ mixins: [ Reflux.connect(MemberListStore, "memberList"), Reflux.listenTo(TeamStore, "onTeamChange"), - OrganizationState, - Reflux.listenTo(TeamStore, "onTeamChange"), - RouteMixin + OrganizationState ], childContextTypes: { @@ -32,10 +28,6 @@ var ProjectDetails = React.createClass({ team: PropTypes.Team }, - contextTypes: { - router: React.PropTypes.func - }, - getChildContext() { return { project: this.state.project, @@ -63,11 +55,9 @@ var ProjectDetails = React.createClass({ this.setState(this.getInitialState(), this.fetchData); }, - routeDidChange(nextPath, nextParams) { - let router = this.context.router; - let params = router.getCurrentParams(); - if (nextParams.projectId != params.projectId || - nextParams.orgId != params.orgId) { + componentWillReceiveProps(nextProps) { + if (nextProps.params.projectId !== this.props.params.projectId || + nextProps.params.orgId != this.props.params.orgId) { this.remountComponent(); } }, @@ -77,13 +67,12 @@ var ProjectDetails = React.createClass({ }, identifyProject() { - let router = this.context.router; - let params = router.getCurrentParams(); - let projectSlug = params.projectId; - let activeProject = null; - let activeTeam = null; - let teams = TeamStore.getAll(); - teams.forEach((team) => { + var params = this.props.params; + var projectSlug = params.projectId; + var activeProject = null; + var activeTeam = null; + let org = this.context.organization; + org.teams.forEach((team) => { team.projects.forEach((project) => { if (project.slug == projectSlug) { activeProject = project; @@ -137,8 +126,7 @@ var ProjectDetails = React.createClass({ }, getMemberListEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); + var params = this.props.params; return '/projects/' + params.orgId + '/' + params.projectId + '/members/'; }, @@ -188,10 +176,10 @@ var ProjectDetails = React.createClass({ organization={this.getOrganization()} />
    - + {React.cloneElement(this.props.children, { + setProjectNavSection: this.setProjectNavSection, + memberList: this.state.memberList + })}
    diff --git a/src/sentry/static/sentry/app/views/projectEvents.jsx b/src/sentry/static/sentry/app/views/projectEvents.jsx index c6e235466d751a..e8d883ae96db18 100644 --- a/src/sentry/static/sentry/app/views/projectEvents.jsx +++ b/src/sentry/static/sentry/app/views/projectEvents.jsx @@ -1,4 +1,5 @@ import React from "react"; +import {History} from "react-router"; import Reflux from "reflux"; import jQuery from "jquery"; import Cookies from "js-cookie"; @@ -9,21 +10,16 @@ import EventStore from "../stores/eventStore"; import LoadingError from "../components/loadingError"; import LoadingIndicator from "../components/loadingIndicator"; import Pagination from "../components/pagination"; -import RouteMixin from "../mixins/routeMixin"; import utils from "../utils"; var ProjectEvents = React.createClass({ mixins: [ Reflux.listenTo(EventStore, "onEventChange"), - RouteMixin + History ], - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { - setProjectNavSection: React.PropTypes.func.isRequired + setProjectNavSection: React.PropTypes.func }, getInitialState() { @@ -68,12 +64,12 @@ var ProjectEvents = React.createClass({ EventStore.reset(); }, - routeDidChange() { - this._poller.disable(); - this.fetchData(); - }, - componentDidUpdate(prevProps, prevState) { + if (prevProps.params.projectId !== this.props.params.projectId) { + this._poller.disable(); + this.fetchData(); + } + this._poller.setEndpoint(this.getEventListEndpoint()); if (prevState.realtimeActive !== this.state.realtimeActive) { if (this.state.realtimeActive) { @@ -120,9 +116,8 @@ var ProjectEvents = React.createClass({ }, getEventListEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); - var queryParams = router.getCurrentQuery(); + var params = this.props.params; + var queryParams = this.props.location.query; queryParams.limit = 50; var querystring = jQuery.param(queryParams); @@ -155,17 +150,14 @@ var ProjectEvents = React.createClass({ }, onPage(cursor) { - var router = this.context.router; - var params = router.getCurrentParams(); - var queryParams = jQuery.extend({}, router.getCurrentQuery(), { + var queryParams = jQuery.extend({}, this.props.location.query, { cursor: cursor }); - router.transitionTo('events', params, queryParams); + this.history.pushState(null, this.props.location.pathname, queryParams); }, transitionTo() { - var router = this.context.router; var queryParams = {}; for (var prop in this.state.filter) { @@ -180,11 +172,12 @@ var ProjectEvents = React.createClass({ queryParams.statsPeriod = this.state.statsPeriod; } - router.transitionTo('stream', router.getCurrentParams(), queryParams); + let {orgId, projectId} = this.props.params; + this.history.pushState(null, `/${orgId}/${projectId}/`, queryParams); }, renderEventNodes(ids) { - var params = this.context.router.getCurrentParams(); + var params = this.props.params; var nodes = ids.map((id) => { return ( diff --git a/src/sentry/static/sentry/app/views/projectInstall/index.jsx b/src/sentry/static/sentry/app/views/projectInstall/index.jsx index 56165749ca682d..be1a722a2ac534 100644 --- a/src/sentry/static/sentry/app/views/projectInstall/index.jsx +++ b/src/sentry/static/sentry/app/views/projectInstall/index.jsx @@ -1,17 +1,12 @@ import React from "react"; -import Router from "react-router"; import api from "../../api"; import LoadingError from "../../components/loadingError"; import LoadingIndicator from "../../components/loadingIndicator"; const ProjectInstall = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { - setProjectNavSection: React.PropTypes.func.isRequired + setProjectNavSection: React.PropTypes.func }, componentWillMount() { @@ -30,7 +25,7 @@ const ProjectInstall = React.createClass({ }, fetchData() { - let {orgId, projectId} = this.context.router.getCurrentParams(); + let {orgId, projectId} = this.props.params; api.request(`/projects/${orgId}/${projectId}/docs/`, { success: (data) => { this.setState({ @@ -48,10 +43,9 @@ const ProjectInstall = React.createClass({ return ; let data = this.state.data; - return ( - - ); + return React.cloneElement(this.props.children, { + platformData: data // {...this.props} + }); } }); diff --git a/src/sentry/static/sentry/app/views/projectInstall/overview.jsx b/src/sentry/static/sentry/app/views/projectInstall/overview.jsx index 65a1ba5be9670d..6cf123152a9c1b 100644 --- a/src/sentry/static/sentry/app/views/projectInstall/overview.jsx +++ b/src/sentry/static/sentry/app/views/projectInstall/overview.jsx @@ -4,10 +4,6 @@ import {Link} from "react-router"; import AutoSelectText from "../../components/autoSelectText"; const ProjectInstallOverview = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - getInitialState() { return { data: this.props.platformData @@ -15,12 +11,11 @@ const ProjectInstallOverview = React.createClass({ }, getIntegrationLink(root, platform, display) { - let params = this.context.router.getCurrentParams(); + let {orgId, projectId} = this.props.params; return (
  • - + {display}
  • diff --git a/src/sentry/static/sentry/app/views/projectInstall/platform.jsx b/src/sentry/static/sentry/app/views/projectInstall/platform.jsx index 9ef6ad1929923c..7da94bc9f2d2f4 100644 --- a/src/sentry/static/sentry/app/views/projectInstall/platform.jsx +++ b/src/sentry/static/sentry/app/views/projectInstall/platform.jsx @@ -5,17 +5,11 @@ import api from "../../api"; import LanguageNav from "./languageNav"; import LoadingError from "../../components/loadingError"; import LoadingIndicator from "../../components/loadingIndicator"; -import RouteMixin from "../../mixins/routeMixin"; var ProjectInstallPlatform = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - - mixins: [RouteMixin], getInitialState() { - let params = this.context.router.getCurrentParams(); + let params = this.props.params; var key = params.platform; var integration; var platform; @@ -44,12 +38,14 @@ var ProjectInstallPlatform = React.createClass({ this.fetchData(); }, - routeDidChange() { - this.setState(this.getInitialState(), this.fetchData); + componentWillReceiveProps(nextProps) { + if (nextProps.params.platform !== this.props.params.platform) { + this.setState(this.getInitialState(), this.fetchData); + } }, fetchData() { - let {orgId, projectId, platform} = this.context.router.getCurrentParams(); + let {orgId, projectId, platform} = this.props.params; api.request(`/projects/${orgId}/${projectId}/docs/${platform}/`, { success: (data) => { this.setState({ @@ -68,12 +64,11 @@ var ProjectInstallPlatform = React.createClass({ }, getPlatformLink(platform, display) { - let params = this.context.router.getCurrentParams(); + let {orgId, projectId} = this.props.params; return ( + to={`/${orgId}/${projectId}/settings/install/${platform}/`} + className="list-group-item"> {display || platform} ); diff --git a/src/sentry/static/sentry/app/views/projectReleases/index.jsx b/src/sentry/static/sentry/app/views/projectReleases/index.jsx index 8dfe8a98c22888..6085eb1810f963 100644 --- a/src/sentry/static/sentry/app/views/projectReleases/index.jsx +++ b/src/sentry/static/sentry/app/views/projectReleases/index.jsx @@ -1,18 +1,16 @@ import jQuery from "jquery"; import React from "react"; +import {History} from "react-router"; import api from "../../api"; import LoadingError from "../../components/loadingError"; import LoadingIndicator from "../../components/loadingIndicator"; import Pagination from "../../components/pagination"; -import RouteMixin from "../../mixins/routeMixin"; import SearchBar from "../../components/searchBar.jsx"; import ReleaseList from "./releaseList"; var ProjectReleases = React.createClass({ - mixins: [ - RouteMixin - ], + mixins: [ History ], getDefaultProps() { return { @@ -20,16 +18,12 @@ var ProjectReleases = React.createClass({ }; }, - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { - setProjectNavSection: React.PropTypes.func.isRequired + setProjectNavSection: React.PropTypes.func }, getInitialState() { - var queryParams = this.context.router.getCurrentQuery(); + var queryParams = this.props.location.query; return { releaseList: [], @@ -41,13 +35,12 @@ var ProjectReleases = React.createClass({ }, onSearch(query) { - var router = this.context.router; - var targetQueryParams = {}; if (query !== '') targetQueryParams.query = query; - router.transitionTo("projectReleases", router.getCurrentParams(), targetQueryParams); + let {orgId, projectId} = this.props.params; + this.history.pushState(null, `/${orgId}/${projectId}/releases/`, targetQueryParams); }, componentWillMount() { @@ -55,11 +48,13 @@ var ProjectReleases = React.createClass({ this.fetchData(); }, - routeDidChange() { - var queryParams = this.context.router.getCurrentQuery(); - this.setState({ - query: queryParams.query - }, this.fetchData); + componentWillReceiveProps(nextProps) { + if (nextProps.location.search !== this.props.location.search) { + let queryParams = nextProps.location.query; + this.setState({ + query: queryParams.query + }, this.fetchData); + } }, fetchData() { @@ -87,9 +82,8 @@ var ProjectReleases = React.createClass({ }, getProjectReleasesEndpoint() { - var router = this.context.router; - var params = router.getCurrentParams(); - var queryParams = $.extend({}, router.getCurrentQuery()); + var params = this.props.params; + var queryParams = $.extend({}, this.props.location.query); queryParams.limit = 50; queryParams.query = this.state.query; @@ -97,17 +91,15 @@ var ProjectReleases = React.createClass({ }, onPage(cursor) { - var router = this.context.router; - var params = router.getCurrentParams(); - var queryParams = $.extend({}, router.getCurrentQuery()); + var queryParams = $.extend({}, this.props.location.query); queryParams.cursor = cursor; - router.transitionTo('projectReleases', params, queryParams); + let {orgId, projectId} = this.props.params; + this.history.pushState(null, `/${orgId}/${projectId}/releases/`, queryParams); }, getReleaseTrackingUrl() { - var router = this.context.router; - var params = router.getCurrentParams(); + var params = this.props.params; return '/' + params.orgId + '/' + params.projectId + '/settings/release-tracking/'; }, @@ -115,12 +107,14 @@ var ProjectReleases = React.createClass({ renderStreamBody() { var body; + let params = this.props.params; + if (this.state.loading) body = this.renderLoading(); else if (this.state.error) body = ; else if (this.state.releaseList.length > 0) - body = ; + body = ; else if (this.state.query && this.state.query !== this.props.defaultQuery) body = this.renderNoQueryResults(); else diff --git a/src/sentry/static/sentry/app/views/projectReleases/releaseList.jsx b/src/sentry/static/sentry/app/views/projectReleases/releaseList.jsx index 6363552bb98eaf..0a18b854d37537 100644 --- a/src/sentry/static/sentry/app/views/projectReleases/releaseList.jsx +++ b/src/sentry/static/sentry/app/views/projectReleases/releaseList.jsx @@ -4,11 +4,15 @@ import TimeSince from "../../components/timeSince"; import Version from "../../components/version"; var ReleaseList = React.createClass({ - contextTypes: { - router: React.PropTypes.func + + propTypes: { + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired, }, render() { + var {orgId, projectId} = this.props; + return (
      {this.props.releaseList.map((release) => { @@ -16,7 +20,7 @@ var ReleaseList = React.createClass({
    • -

      +

      diff --git a/src/sentry/static/sentry/app/views/projectSettings/index.jsx b/src/sentry/static/sentry/app/views/projectSettings/index.jsx index 12a4cf544173cf..a8364361084795 100644 --- a/src/sentry/static/sentry/app/views/projectSettings/index.jsx +++ b/src/sentry/static/sentry/app/views/projectSettings/index.jsx @@ -1,22 +1,19 @@ import React from "react"; -import Router from "react-router"; import api from "../../api"; import ConfigStore from "../../stores/configStore"; import ListLink from "../../components/listLink"; import LoadingError from "../../components/loadingError"; import LoadingIndicator from "../../components/loadingIndicator"; -import RouteMixin from "../../mixins/routeMixin"; const ProjectSettings = React.createClass({ - mixins: [RouteMixin], contextTypes: { - router: React.PropTypes.func + location: React.PropTypes.object }, propTypes: { - setProjectNavSection: React.PropTypes.func.isRequired + setProjectNavSection: React.PropTypes.func }, componentWillMount() { @@ -24,10 +21,10 @@ const ProjectSettings = React.createClass({ this.fetchData(); }, - routeDidChange(nextPath, nextParams) { - var params = this.context.router.getCurrentParams(); - if (nextParams.projectId != params.projectId || - nextParams.orgId != params.orgId) { + componentWillReceiveProps(nextProps) { + var params = this.props.params; + if (nextProps.params.projectId !== params.projectId || + nextProps.params.orgId !== params.orgId) { this.setState({ loading: true, error: false @@ -44,7 +41,7 @@ const ProjectSettings = React.createClass({ }, fetchData() { - var params = this.context.router.getCurrentParams(); + var params = this.props.params; api.request(`/projects/${params.orgId}/${params.projectId}/`, { success: (data) => { @@ -71,8 +68,8 @@ const ProjectSettings = React.createClass({ return ; let urlPrefix = ConfigStore.get('urlPrefix'); - let params = this.context.router.getCurrentParams(); - let settingsUrlRoot = `${urlPrefix}/${params.orgId}/${params.projectId}/settings`; + let {orgId, projectId} = this.props.params; + let settingsUrlRoot = `${urlPrefix}/${orgId}/${projectId}/settings`; let project = this.state.project; return ( @@ -89,10 +86,13 @@ const ProjectSettings = React.createClass({
    Setup
    Integrations
    @@ -104,10 +104,10 @@ const ProjectSettings = React.createClass({
    - + {React.cloneElement(this.props.children, { + setProjectNavSection: this.props.setProjectNavSection, + project: project + })}
    ); diff --git a/src/sentry/static/sentry/app/views/releaseAllEvents.jsx b/src/sentry/static/sentry/app/views/releaseAllEvents.jsx index ad015759ddbd2c..5e225faf6d08d7 100644 --- a/src/sentry/static/sentry/app/views/releaseAllEvents.jsx +++ b/src/sentry/static/sentry/app/views/releaseAllEvents.jsx @@ -1,29 +1,26 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import GroupList from "../components/groupList"; -import PropTypes from "../proptypes"; var ReleaseAllEvents = React.createClass({ contextTypes: { - router: React.PropTypes.func, - release: PropTypes.AnyModel + release: React.PropTypes.object }, render() { - var params = this.context.router.getCurrentParams(); + let {orgId, projectId} = this.props.params; return (
    - View all events seen in this release in the stream - +
    diff --git a/src/sentry/static/sentry/app/views/releaseArtifacts.jsx b/src/sentry/static/sentry/app/views/releaseArtifacts.jsx index 9ed5fa656bd636..73d64a3fdbe2a4 100644 --- a/src/sentry/static/sentry/app/views/releaseArtifacts.jsx +++ b/src/sentry/static/sentry/app/views/releaseArtifacts.jsx @@ -1,5 +1,5 @@ import React from "react"; -import PropTypes from "../proptypes"; +import {History} from "react-router"; import jQuery from "jquery"; import api from "../api"; @@ -9,9 +9,10 @@ import LoadingIndicator from "../components/loadingIndicator"; import Pagination from "../components/pagination"; var ReleaseArtifacts = React.createClass({ + mixins: [ History ], + contextTypes: { - router: React.PropTypes.func, - release: PropTypes.AnyModel + release: React.PropTypes.object }, getInitialState() { @@ -27,13 +28,14 @@ var ReleaseArtifacts = React.createClass({ this.fetchData(); }, - routeDidChange() { - this.fetchData(); + componentDidUpdate(prevProps) { + if (this.props.location.search !== prevProps.location.search) { + this.fetchData(); + } }, fetchData() { - var router = this.context.router; - var params = router.getCurrentParams(); + var params = this.props.params; var endpoint = '/projects/' + params.orgId + '/' + params.projectId + '/releases/' + params.version + '/files/'; this.setState({ @@ -60,12 +62,12 @@ var ReleaseArtifacts = React.createClass({ }, onPage(cursor) { - var router = this.context.router; - var queryParams = jQuery.extend({}, router.getCurrentQuery(), { + var queryParams = jQuery.extend({}, this.props.location.query, { cursor: cursor }); - router.transitionTo('releaseArtifacts', router.getCurrentParams(), queryParams); + let {orgId, projectId, version} = this.props.params; + this.history.pushState(null, `/${orgId}/${projectId}/releases/${version}/artifacts/`, queryParams); }, render() { @@ -85,6 +87,7 @@ var ReleaseArtifacts = React.createClass({ return (
    + {this.state.fileList.map((file) => { return ( @@ -93,6 +96,7 @@ var ReleaseArtifacts = React.createClass({ ); })} +
    diff --git a/src/sentry/static/sentry/app/views/releaseDetails.jsx b/src/sentry/static/sentry/app/views/releaseDetails.jsx index 429d34a54a8fd6..8f3027d866dc7a 100644 --- a/src/sentry/static/sentry/app/views/releaseDetails.jsx +++ b/src/sentry/static/sentry/app/views/releaseDetails.jsx @@ -1,5 +1,4 @@ import React from "react"; -import Router from "react-router"; import api from "../api"; import Count from "../components/count"; import DocumentTitle from "react-document-title"; @@ -11,16 +10,16 @@ import TimeSince from "../components/timeSince"; import Version from "../components/version"; var ReleaseDetails = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - mixins: [ ProjectState ], propTypes: { - setProjectNavSection: React.PropTypes.func.isRequired + setProjectNavSection: React.PropTypes.func + }, + + contextTypes: { + location: React.PropTypes.object }, childContextTypes: { @@ -49,7 +48,7 @@ var ReleaseDetails = React.createClass({ getTitle() { var project = this.getProject(); var team = this.getTeam(); - var params = this.context.router.getCurrentParams(); + var params = this.props.params; return 'Release ' + params.version + ' | ' + team.name + ' / ' + project.name; }, @@ -75,7 +74,7 @@ var ReleaseDetails = React.createClass({ }, getReleaseDetailsEndpoint() { - var params = this.context.router.getCurrentParams(); + var params = this.props.params; var orgId = params.orgId; var projectId = params.projectId; var version = params.version; @@ -90,15 +89,15 @@ var ReleaseDetails = React.createClass({ return ; var release = this.state.release; - var params = this.context.router.getCurrentParams(); + var {orgId, projectId} = this.props.params; return (
    -

    Release

    +

    Release

    @@ -131,12 +130,18 @@ var ReleaseDetails = React.createClass({
      - New Events - All Events - Artifacts + { + // react-router isActive will return true for any route that is part of the active route + // e.g. parent routes. To avoid matching on sub-routes, insist on strict path equality. + return to === this.context.location.pathname; + }}>New Events + All Events + Artifacts
    - + {React.cloneElement(this.props.children, { + release: release, + })}
    ); diff --git a/src/sentry/static/sentry/app/views/releaseNewEvents.jsx b/src/sentry/static/sentry/app/views/releaseNewEvents.jsx index 8701e1cc8b1141..6147d8eb01c069 100644 --- a/src/sentry/static/sentry/app/views/releaseNewEvents.jsx +++ b/src/sentry/static/sentry/app/views/releaseNewEvents.jsx @@ -1,28 +1,26 @@ import React from "react"; -import Router from "react-router"; +import {Link} from "react-router"; import GroupList from "../components/groupList"; var ReleaseNewEvents = React.createClass({ contextTypes: { - router: React.PropTypes.func, release: React.PropTypes.object }, render() { - var params = this.context.router.getCurrentParams(); + let {orgId, projectId} = this.props.params; return (
    - View new events seen in this release in the stream - +
    diff --git a/src/sentry/static/sentry/app/views/sharedGroupDetails/index.jsx b/src/sentry/static/sentry/app/views/sharedGroupDetails/index.jsx index 324b48648eb62f..50efe2963a3360 100644 --- a/src/sentry/static/sentry/app/views/sharedGroupDetails/index.jsx +++ b/src/sentry/static/sentry/app/views/sharedGroupDetails/index.jsx @@ -13,9 +13,6 @@ import PropTypes from "../../proptypes"; import SharedGroupHeader from "./sharedGroupHeader"; var SharedGroupDetails = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, childContextTypes: { group: PropTypes.Group, @@ -72,7 +69,7 @@ var SharedGroupDetails = React.createClass({ }, getGroupDetailsEndpoint() { - var id = this.context.router.getCurrentParams().shareId; + var id = this.props.params.shareId; return '/shared/groups/' + id + '/'; }, diff --git a/src/sentry/static/sentry/app/views/stream.jsx b/src/sentry/static/sentry/app/views/stream.jsx index 0ad4b1f7258e0e..2cee38a3b7dc76 100644 --- a/src/sentry/static/sentry/app/views/stream.jsx +++ b/src/sentry/static/sentry/app/views/stream.jsx @@ -1,5 +1,6 @@ import React from "react"; import Reflux from "reflux"; +import {History} from "react-router"; import $ from "jquery"; import Cookies from "js-cookie"; import Sticky from 'react-sticky'; @@ -12,7 +13,6 @@ import GroupStore from "../stores/groupStore"; import LoadingError from "../components/loadingError"; import LoadingIndicator from "../components/loadingIndicator"; import Pagination from "../components/pagination"; -import RouteMixin from "../mixins/routeMixin"; import StreamGroup from '../components/stream/group'; import StreamActions from './stream/actions'; import StreamTagActions from "../actions/streamTagActions"; @@ -26,15 +26,11 @@ var Stream = React.createClass({ mixins: [ Reflux.listenTo(GroupStore, "onGroupChange"), Reflux.listenTo(StreamTagStore, "onStreamTagChange"), - RouteMixin + History ], - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { - setProjectNavSection: React.PropTypes.func.isRequired + setProjectNavSection: React.PropTypes.func }, getDefaultProps() { @@ -74,8 +70,19 @@ var Stream = React.createClass({ }, componentWillReceiveProps(nextProps) { - if (nextProps.params.projectId !== this.props.params.projectId) { - this.fetchTags(); + if (nextProps.location.search !== this.props.location.search) { + this.setState(this.getQueryStringState(nextProps), this.fetchData); + this._poller.disable(); + } + }, + + componentDidUpdate(prevProps, prevState) { + if (prevState.realtimeActive !== this.state.realtimeActive) { + if (this.state.realtimeActive) { + this._poller.enable(); + } else { + this._poller.disable(); + } } }, @@ -88,8 +95,6 @@ var Stream = React.createClass({ endpoint: this.getGroupListEndpoint() }); - this.fetchTags(); - var realtime = Cookies.get("realtimeActive"); if (realtime) { var realtimeActive = realtime === "true"; @@ -101,6 +106,7 @@ var Stream = React.createClass({ } } + this.fetchTags(); this.fetchData(); }, @@ -117,7 +123,7 @@ var Stream = React.createClass({ tagsLoading: true }); - var params = this.context.router.getCurrentParams(); + let params = this.props.params; api.request(`/projects/${params.orgId}/${params.projectId}/tags/`, { success: (tags) => { this.setState({tagsLoading: false}); @@ -130,8 +136,9 @@ var Stream = React.createClass({ }); }, - getQueryStringState() { - var currentQuery = this.context.router.getCurrentQuery(); + getQueryStringState(props) { + props = props || this.props; + var currentQuery = props.location.query; var filter = {}; if (currentQuery.bookmarks) { @@ -167,22 +174,6 @@ var Stream = React.createClass({ }; }, - routeDidChange() { - this.setState(this.getQueryStringState()); - this._poller.disable(); - this.fetchData(); - }, - - componentDidUpdate(prevProps, prevState) { - if (prevState.realtimeActive !== this.state.realtimeActive) { - if (this.state.realtimeActive) { - this._poller.enable(); - } else { - this._poller.disable(); - } - } - }, - fetchData() { GroupStore.loadInitialData([]); @@ -193,8 +184,7 @@ var Stream = React.createClass({ var url = this.getGroupListEndpoint(); - var router = this.context.router; - var requestParams = $.extend({}, router.getCurrentQuery(), { + var requestParams = $.extend({}, this.props.location.query, { limit: this.props.maxItems, statsPeriod: this.state.statsPeriod }); @@ -214,10 +204,10 @@ var Stream = React.createClass({ // Was this the result of an event SHA search? If so, redirect // to corresponding group details if (data.length === 1 && /^[a-zA-Z0-9]{32}$/.test(requestParams.query.trim())) { - const params = $.extend({}, router.getCurrentParams(), { - groupId: data[0].id - }); - return void this.context.router.transitionTo('groupDetails', params); + let params = this.props.params; + let groupId = data[0].id; + + return void this.history.pushState(null, `/${params.orgId}/${params.projectId}/${groupId}/`); } this._streamManager.push(data); @@ -250,8 +240,7 @@ var Stream = React.createClass({ }, getGroupListEndpoint() { - var router = this.context.router, - params = router.getCurrentParams(); + var params = this.props.params; return '/projects/' + params.orgId + '/' + params.projectId + '/groups/'; }, @@ -300,12 +289,11 @@ var Stream = React.createClass({ }, onPage(cursor) { - var router = this.context.router; - var params = router.getCurrentParams(); - var queryParams = $.extend({}, router.getCurrentQuery()); + var params = this.props.params; + var queryParams = $.extend({}, this.props.location.query); queryParams.cursor = cursor; - router.transitionTo('stream', params, queryParams); + this.history.pushState(null, `/${params.orgId}/${params.projectId}/`, queryParams); }, onSearch(query) { @@ -339,7 +327,6 @@ var Stream = React.createClass({ }, transitionTo() { - var router = this.context.router; var queryParams = {}; for (var prop in this.state.filter) { @@ -358,12 +345,21 @@ var Stream = React.createClass({ queryParams.statsPeriod = this.state.statsPeriod; } - router.transitionTo('stream', router.getCurrentParams(), queryParams); + let params = this.props.params; + this.history.pushState(null, `/${params.orgId}/${params.projectId}/`, queryParams); }, renderGroupNodes(ids, statsPeriod) { + var {orgId, projectId} = this.props.params; var groupNodes = ids.map((id) => { - return ; + return ( + + ); }); return (
      {groupNodes}
    ); @@ -403,17 +399,19 @@ var Stream = React.createClass({ }, render() { - let router = this.context.router; - let params = router.getCurrentParams(); + let params = this.props.params; let classes = ['stream-row']; if (this.state.isSidebarVisible) classes.push('show-sidebar'); + let {orgId, projectId} = this.props.params; return (
    + onQueryChange={this.onSearch} + orgId={params.orgId} + projectId={params.projectId} + />
    ); } diff --git a/src/sentry/static/sentry/app/views/stream/filterSelectLink.jsx b/src/sentry/static/sentry/app/views/stream/filterSelectLink.jsx index d93d5cb5a7210d..357e7fdfac5e0c 100644 --- a/src/sentry/static/sentry/app/views/stream/filterSelectLink.jsx +++ b/src/sentry/static/sentry/app/views/stream/filterSelectLink.jsx @@ -1,10 +1,6 @@ import React from "react"; var FilterSelectLink = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { label: React.PropTypes.string, onSelect: React.PropTypes.func, diff --git a/src/sentry/static/sentry/app/views/stream/filters.jsx b/src/sentry/static/sentry/app/views/stream/filters.jsx index 64061061b3bbbf..14dfc3369120b1 100644 --- a/src/sentry/static/sentry/app/views/stream/filters.jsx +++ b/src/sentry/static/sentry/app/views/stream/filters.jsx @@ -5,8 +5,9 @@ import SearchBar from "./searchBar"; import SortOptions from "./sortOptions"; var StreamFilters = React.createClass({ - contextTypes: { - router: React.PropTypes.func + propTypes: { + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired }, getDefaultProps() { @@ -22,24 +23,12 @@ var StreamFilters = React.createClass({ }; }, - componentWillMount() { - this.setState({ - activeButton: this.getActiveButton() - }); - }, - - componentWillReceiveProps(nextProps) { - var activeButton = this.getActiveButton(); - if (activeButton != this.state.activeButton) { - this.setState({ - activeButton: activeButton - }); - } + contextTypes: { + location: React.PropTypes.object }, getActiveButton() { - var router = this.context.router; - var queryParams = router.getCurrentQuery(); + var queryParams = this.context.location.query; var activeButton; if (queryParams.bookmarks) { activeButton = 'bookmarks'; @@ -56,7 +45,7 @@ var StreamFilters = React.createClass({ }, render() { - var activeButton = this.state.activeButton; + let activeButton = this.getActiveButton(); return (
    @@ -85,6 +74,8 @@ var StreamFilters = React.createClass({
    ); })} diff --git a/src/sentry/static/sentry/app/views/stream/tagFilter.jsx b/src/sentry/static/sentry/app/views/stream/tagFilter.jsx index 253a048af161ec..0d72667a92e472 100644 --- a/src/sentry/static/sentry/app/views/stream/tagFilter.jsx +++ b/src/sentry/static/sentry/app/views/stream/tagFilter.jsx @@ -3,12 +3,10 @@ import ReactDOM from "react-dom"; import _ from "underscore"; var StreamTagFilter = React.createClass({ - contextTypes: { - router: React.PropTypes.func - }, - propTypes: { - tag: React.PropTypes.object.isRequired + tag: React.PropTypes.object.isRequired, + orgId: React.PropTypes.string.isRequired, + projectId: React.PropTypes.string.isRequired }, getDefaultProps() { @@ -90,8 +88,7 @@ var StreamTagFilter = React.createClass({ }, getTagValuesAPIEndpoint() { - let params = this.context.router.getCurrentParams(); - return `/api/0/projects/${params.orgId}/${params.projectId}/tags/${this.props.tag.key}/values/`; + return `/api/0/projects/${this.props.orgId}/${this.props.projectId}/tags/${this.props.tag.key}/values/`; }, onSelectValue(evt) { diff --git a/src/sentry/static/sentry/app/views/teamDetails.jsx b/src/sentry/static/sentry/app/views/teamDetails.jsx deleted file mode 100644 index 97c4dbb16ad9a1..00000000000000 --- a/src/sentry/static/sentry/app/views/teamDetails.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from "react"; -import Router from "react-router"; -import LoadingIndicator from "../components/loadingIndicator"; -import OrganizationState from "../mixins/organizationState"; -import PropTypes from "../proptypes"; -import RouteMixin from "../mixins/routeMixin"; - -var TeamDetails = React.createClass({ - mixins: [ - OrganizationState, - RouteMixin - ], - - childContextTypes: { - team: PropTypes.Team - }, - - contextTypes: { - router: React.PropTypes.func - }, - - getChildContext() { - return { - team: this.state.team - }; - }, - - getInitialState() { - return { - team: null - }; - }, - - componentWillMount() { - this.fetchData(); - }, - - routeDidChange(nextPath, nextParams) { - var router = this.context.router; - var params = router.getCurrentParams(); - if (nextParams.teamId != params.teamId) { - this.fetchData(); - } - }, - - fetchData() { - var org = this.getOrganization(); - if (!org) { - return; - } - - var router = this.context.router; - var params = router.getCurrentParams(); - var teamSlug = params.teamId; - var team = org.teams.filter((t) => { - return t.slug === teamSlug; - })[0]; - - this.setState({ - team: team, - loading: false, - error: typeof team !== "undefined" - }); - }, - - render() { - if (!this.state.team) { - return ; - } - - return ( -
    -
    -
    - -
    -
    -
    - ); - } -}); - -export default TeamDetails; diff --git a/src/sentry/templates/sentry/bases/react.html b/src/sentry/templates/sentry/bases/react.html index 1ef5c6d87c6b5d..607cb64052430b 100644 --- a/src/sentry/templates/sentry/bases/react.html +++ b/src/sentry/templates/sentry/bases/react.html @@ -16,10 +16,10 @@
    {% endblock %} diff --git a/tests/js/helpers/stubReactComponent.jsx b/tests/js/helpers/stubReactComponent.jsx index 730cc6bf1d7e6b..f8b29817a8ae19 100644 --- a/tests/js/helpers/stubReactComponent.jsx +++ b/tests/js/helpers/stubReactComponent.jsx @@ -7,6 +7,7 @@ var originalCreateElement = React.createElement; export default function(stubber, stubbedComponents) { stubber.stub(React, "createElement", function(component, props) { + props = props || {}; if (stubbedComponents.indexOf(component) === -1) { return originalCreateElement.apply(React, arguments); } else { diff --git a/tests/js/spec/components/group/tagDistributionMeter.spec.jsx b/tests/js/spec/components/group/tagDistributionMeter.spec.jsx index 8b4644a8571c12..6e2f74cba2986e 100644 --- a/tests/js/spec/components/group/tagDistributionMeter.spec.jsx +++ b/tests/js/spec/components/group/tagDistributionMeter.spec.jsx @@ -5,8 +5,6 @@ import TestUtils from "react-addons-test-utils"; import api from "app/api"; import TagDistributionMeter from "app/components/group/tagDistributionMeter"; -import stubRouter from "../../../helpers/stubRouter"; -import stubContext from "../../../helpers/stubContext"; describe("TagDistributionMeter", function() { @@ -15,19 +13,9 @@ describe("TagDistributionMeter", function() { this.stubbedApiRequest = this.sandbox.stub(api, "request"); - let ContextStubbedTagDistributionMeter = stubContext(TagDistributionMeter, { - organization: { id: 1337 }, - router: stubRouter({ - getCurrentParams() { - return { orgId: "123" }; - }, - getCurrentQuery() { - return { limit: 0 }; - } - }) - }); - - this.element = TestUtils.renderIntoDocument().refs.wrapped; + this.element = TestUtils.renderIntoDocument( + + ); }); afterEach(function() { diff --git a/tests/js/spec/views/organizationTeams.spec.jsx b/tests/js/spec/views/organizationTeams.spec.jsx index 63ea34867bf000..b77a9844c4f611 100644 --- a/tests/js/spec/views/organizationTeams.spec.jsx +++ b/tests/js/spec/views/organizationTeams.spec.jsx @@ -2,7 +2,11 @@ import React from "react"; import TestUtils from "react-addons-test-utils"; import api from "app/api"; import OrganizationTeams from "app/views/organizationTeams"; -import stubRouter from "../../helpers/stubRouter"; +import ExpandedTeamList from "app/views/organizationTeams/expandedTeamList"; +import AllTeamsList from "app/views/organizationTeams/allTeamsList"; +import OrganizationHomeContainer from "app/components/organizations/homeContainer"; + +import stubReactComponent from "../../helpers/stubReactComponent"; import stubContext from "../../helpers/stubContext"; describe("OrganizationTeams", function() { @@ -11,20 +15,13 @@ describe("OrganizationTeams", function() { this.sandbox = sinon.sandbox.create(); this.stubbedApiRequest = this.sandbox.stub(api, "request"); + stubReactComponent(this.sandbox, [ExpandedTeamList, AllTeamsList, OrganizationHomeContainer]); var ContextStubbedOrganizationTeams = stubContext(OrganizationTeams, { - organization: { id: "1337" }, - router: stubRouter({ - getCurrentParams() { - return { orgId: "123" }; - }, - getCurrentQuery() { - return { limit: 0 }; - } - }) + organization: { id: "1337" } }); - this.Element = ; + this.Element = ; }); afterEach(function() { diff --git a/tests/js/spec/views/projectReleases.spec.jsx b/tests/js/spec/views/projectReleases.spec.jsx index 565af03c3950a4..e5897bd674dc05 100644 --- a/tests/js/spec/views/projectReleases.spec.jsx +++ b/tests/js/spec/views/projectReleases.spec.jsx @@ -2,8 +2,7 @@ import React from "react"; import TestUtils from "react-addons-test-utils"; import stubReactComponents from "../../helpers/stubReactComponent"; -import stubContext from "../../helpers/stubContext"; -import stubRouter from "../../helpers/stubRouter"; + import api from "app/api"; import ProjectReleases from "app/views/projectReleases"; import SearchBar from "app/views/stream/searchBar"; @@ -16,22 +15,14 @@ describe("ProjectReleases", function () { this.sandbox.stub(api, "request"); stubReactComponents(this.sandbox, [SearchBar, Pagination]); - this.ContextStubbedProjectReleases = stubContext(ProjectReleases, { - router: stubRouter({ - getCurrentParams() { - return { - orgId: "123", - projectId: "456" - }; - }, - getCurrentQuery() { - return { - limit: 0, - query: "derp" - }; - } - }) - }); + this.props = { + setProjectNavSection: function () {}, + params: { orgId: "123", projectId: "456"}, + location: {query: {limit: 0, query: "derp"}} + }; + this.projectReleases = TestUtils.renderIntoDocument( + + ); }); afterEach(function () { @@ -40,60 +31,57 @@ describe("ProjectReleases", function () { describe("fetchData()", function () { it("should call releases endpoint", function () { - TestUtils.renderIntoDocument( - - ); - expect(api.request.args[0][0]).to.equal('/projects/123/456/releases/?limit=50&query=derp'); }); }); describe("getInitialState()", function () { it("should take query state from query string", function () { - TestUtils.renderIntoDocument( - - ); - - var projectReleases = TestUtils.renderIntoDocument( - - ).refs.wrapped; - - expect(projectReleases.state.query).to.equal("derp"); + expect(this.projectReleases.state.query).to.equal("derp"); }); }); describe("onSearch", function () { it("should change query string with new search parameter", function () { - var projectReleases = TestUtils.renderIntoDocument( - - ).refs.wrapped; + var projectReleases = this.projectReleases; - var router = this.sandbox.stub(projectReleases.context.router, 'transitionTo'); + let pushState = this.sandbox.stub(); + projectReleases.history = { + pushState: pushState + }; projectReleases.onSearch("searchquery"); - expect(router.calledOnce).to.be.ok; - expect(router.args[0]).to.eql([ - "projectReleases", - { orgId: "123", projectId: "456" }, + expect(pushState.calledOnce).to.be.ok; + expect(pushState.args[0]).to.eql([ + null, + '/123/456/releases/', { query: "searchquery" } ]); }); }); - describe("routeDidChange()", function () { + // TODO: figure how to trigger componentWillReceiveProps + + describe("componentWillReceiveProps()", function () { it("should update state with latest query pulled from query string", function () { - var projectReleases = TestUtils.renderIntoDocument( - - ).refs.wrapped; + var projectReleases = this.projectReleases; - this.sandbox.stub(projectReleases.context.router, 'getCurrentQuery').returns({ - query: "newquery" - }); + let setState = this.sandbox.stub(projectReleases, 'setState'); - projectReleases.routeDidChange(); + let newProps = { + ...this.props, + location: { + search: "?query=newquery", + query: { query: "newquery" } + } + }; + projectReleases.componentWillReceiveProps(newProps); - expect(projectReleases.state.query).to.eql("newquery"); + expect(setState.calledOnce).to.be.ok; + expect(setState.getCall(0).args[0]).to.eql({ + query: "newquery" + }); }); }); }); diff --git a/tests/js/spec/views/stream.spec.jsx b/tests/js/spec/views/stream.spec.jsx index 4a970dcb00d8b1..1b1820573a9722 100644 --- a/tests/js/spec/views/stream.spec.jsx +++ b/tests/js/spec/views/stream.spec.jsx @@ -1,6 +1,7 @@ import React from "react"; import TestUtils from "react-addons-test-utils"; import Cookies from "js-cookie"; +import Sticky from "react-sticky"; import Api from "app/api"; import CursorPoller from "app/utils/cursorPoller"; import LoadingError from "app/components/loadingError"; @@ -9,9 +10,8 @@ import Stream from "app/views/stream"; import StreamGroup from "app/components/stream/group"; import StreamFilters from "app/views/stream/filters"; import StreamSidebar from "app/views/stream/sidebar"; +import StreamActions from "app/views/stream/actions"; import stubReactComponents from "../../helpers/stubReactComponent"; -import stubContext from "../../helpers/stubContext"; -import stubRouter from "../../helpers/stubRouter"; var findWithClass = TestUtils.findRenderedDOMComponentWithClass; var findWithType = TestUtils.findRenderedComponentWithType; @@ -26,24 +26,14 @@ describe("Stream", function() { this.sandbox = sinon.sandbox.create(); this.stubbedApiRequest = this.sandbox.stub(Api, "request"); - stubReactComponents(this.sandbox, [StreamGroup, StreamFilters, StreamSidebar]); - - var ContextStubbedStream = stubContext(Stream, { - router: stubRouter({ - getCurrentParams() { - return { - orgId: "123", - projectId: "456" - }; - }, - getCurrentQuery() { - return { limit: 0 }; - } - }) - }); - - - this.Element = ; + stubReactComponents(this.sandbox, [StreamGroup, StreamFilters, StreamSidebar, StreamActions, Sticky]); + + this.Element = ( + + ); }); afterEach(function() { @@ -67,8 +57,8 @@ describe("Stream", function() { it("should reset the poller endpoint and sets cursor URL", function() { this.linkHeader = DEFAULT_LINKS_HEADER; - var wrapper = TestUtils.renderIntoDocument(this.Element); - wrapper.refs.wrapped.fetchData(); + var stream = TestUtils.renderIntoDocument(this.Element); + stream.fetchData(); expect(CursorPoller.prototype.setEndpoint .calledWith('http://127.0.0.1:8000/api/0/projects/sentry/ludic-science/groups/?cursor=1443575731:0:1')) @@ -79,8 +69,8 @@ describe("Stream", function() { this.linkHeader = '; rel="next"; results="true"; cursor="1443575731:0:0'; - var wrapper = TestUtils.renderIntoDocument(this.Element); - wrapper.refs.wrapped.fetchData(); + var stream = TestUtils.renderIntoDocument(this.Element); + stream.fetchData(); expect(CursorPoller.prototype.setEndpoint.notCalled).to.be.ok; }); @@ -99,7 +89,7 @@ describe("Stream", function() { }); // NOTE: fetchData called once after render automatically - var stream = TestUtils.renderIntoDocument(this.Element).refs.wrapped; + var stream = TestUtils.renderIntoDocument(this.Element); // 2nd fetch should call cancel stream.fetchData(); @@ -119,41 +109,42 @@ describe("Stream", function() { describe("render()", function() { it("displays a loading indicator when component is loading", function() { - var wrapper = TestUtils.renderIntoDocument(this.Element); - wrapper.refs.wrapped.setState({ loading: true }); - var expected = findWithType(wrapper, LoadingIndicator); + var stream = TestUtils.renderIntoDocument(this.Element); + stream.setState({ loading: true }); + var expected = findWithType(stream, LoadingIndicator); + expect(expected).to.be.ok; }); it("displays an error when component has errored", function() { - var wrapper = TestUtils.renderIntoDocument(this.Element); - wrapper.refs.wrapped.setState({ + var stream = TestUtils.renderIntoDocument(this.Element); + stream.setState({ error: true, loading: false }); - var expected = findWithType(wrapper, LoadingError); + var expected = findWithType(stream, LoadingError); expect(expected).to.be.ok; }); it("displays the group list", function() { - var wrapper = TestUtils.renderIntoDocument(this.Element); - wrapper.refs.wrapped.setState({ + var stream = TestUtils.renderIntoDocument(this.Element); + stream.setState({ error: false, groupIds: ["1"], loading: false }); - var expected = findWithClass(wrapper, "group-list"); + var expected = findWithClass(stream, "group-list"); expect(expected).to.be.ok; }); it("displays empty with no ids", function() { - var wrapper = TestUtils.renderIntoDocument(this.Element); - wrapper.refs.wrapped.setState({ + var stream = TestUtils.renderIntoDocument(this.Element); + stream.setState({ error: false, groupIds: [], loading: false }); - var expected = findWithClass(wrapper, "empty-stream"); + var expected = findWithClass(stream, "empty-stream"); expect(expected).to.be.ok; }); @@ -165,18 +156,24 @@ describe("Stream", function() { Cookies.remove("realtimeActive"); }); - it("reads the realtimeActive state from a cookie", function() { + it("reads the realtimeActive state from a cookie", function(done) { Cookies.set("realtimeActive", "false"); - var wrapper = TestUtils.renderIntoDocument(this.Element); - var expected = findWithClass(wrapper, "icon-play"); - expect(expected).to.be.ok; + + var stream = TestUtils.renderIntoDocument(this.Element); + setTimeout(() => { + expect(stream.state.realtimeActive).to.not.be.ok; + done(); + }); }); - it("reads the true realtimeActive state from a cookie", function() { + it("reads the true realtimeActive state from a cookie", function(done) { Cookies.set("realtimeActive", "true"); - var wrapper = TestUtils.renderIntoDocument(this.Element); - var expected = findWithClass(wrapper, "icon-pause"); - expect(expected).to.be.ok; + var stream = TestUtils.renderIntoDocument(this.Element); + + setTimeout(() => { + expect(stream.state.realtimeActive).to.be.ok; + done(); + }); }); }); @@ -184,14 +181,14 @@ describe("Stream", function() { describe("onRealtimeChange", function() { it("sets the realtimeActive state", function() { - var wrapper = TestUtils.renderIntoDocument(this.Element); - wrapper.refs.wrapped.state.realtimeActive = false; - wrapper.refs.wrapped.onRealtimeChange(true); - expect(wrapper.refs.wrapped.state.realtimeActive).to.eql(true); + var stream = TestUtils.renderIntoDocument(this.Element); + stream.state.realtimeActive = false; + stream.onRealtimeChange(true); + expect(stream.state.realtimeActive).to.eql(true); expect(Cookies.get("realtimeActive")).to.eql("true"); - wrapper.refs.wrapped.onRealtimeChange(false); - expect(wrapper.refs.wrapped.state.realtimeActive).to.eql(false); + stream.onRealtimeChange(false); + expect(stream.state.realtimeActive).to.eql(false); expect(Cookies.get("realtimeActive")).to.eql("false"); }); @@ -211,8 +208,8 @@ describe("Stream", function() { loading: true, error: false }; - var wrapper = TestUtils.renderIntoDocument(this.Element); - var actual = wrapper.refs.wrapped.getInitialState(); + var stream = TestUtils.renderIntoDocument(this.Element); + var actual = stream.getInitialState(); for (var property in expected) { expect(actual[property]).to.eql(expected[property]); diff --git a/tests/js/spec/views/stream/searchBar.spec.jsx b/tests/js/spec/views/stream/searchBar.spec.jsx index 2e1decb7ccb6f9..89c1929788b7e3 100644 --- a/tests/js/spec/views/stream/searchBar.spec.jsx +++ b/tests/js/spec/views/stream/searchBar.spec.jsx @@ -6,7 +6,7 @@ import SearchBar from "app/views/stream/searchBar"; import SearchDropdown from "app/views/stream/searchDropdown"; import StreamTagStore from "app/stores/streamTagStore"; import stubReactComponents from "../../../helpers/stubReactComponent"; -import stubRouter from "../../../helpers/stubRouter"; + import stubContext from "../../../helpers/stubContext"; var findWithClass = TestUtils.findRenderedDOMComponentWithClass; @@ -21,19 +21,7 @@ describe("SearchBar", function() { this.sandbox.stub(api, "request"); stubReactComponents(this.sandbox, [SearchDropdown]); - this.ContextStubbedSearchBar = stubContext(SearchBar, { - router: stubRouter({ - getCurrentParams() { - return { - orgId: "123", - projectId: "456" - }; - }, - getCurrentQuery() { - return { limit: 0 }; - } - }) - }); + this.ContextStubbedSearchBar = stubContext(SearchBar); }); afterEach(function() { @@ -70,6 +58,8 @@ describe("SearchBar", function() { it("clears the query", function() { var props = { + orgId: "123", + projectId: "456", query: "is:unresolved ruby", defaultQuery: "is:unresolved" }; @@ -82,6 +72,8 @@ describe("SearchBar", function() { it("calls onSearch()", function(done) { var props = { + orgId: "123", + projectId: "456", query: "is:unresolved ruby", defaultQuery: "is:unresolved", onSearch: this.sandbox.spy() @@ -101,7 +93,7 @@ describe("SearchBar", function() { describe("onQueryFocus()", function() { it("displays the drop down", function() { - var wrapper = TestUtils.renderIntoDocument().refs.wrapped; + var wrapper = TestUtils.renderIntoDocument().refs.wrapped; expect(wrapper.state.dropdownVisible).to.be.false; wrapper.onQueryFocus(); @@ -114,7 +106,7 @@ describe("SearchBar", function() { describe("onQueryBlur()", function() { it("hides the drop down", function() { - var wrapper = TestUtils.renderIntoDocument().refs.wrapped; + var wrapper = TestUtils.renderIntoDocument().refs.wrapped; wrapper.state.dropdownVisible = true; var clock = this.sandbox.useFakeTimers(); @@ -130,7 +122,7 @@ describe("SearchBar", function() { describe("escape", function () { it("blurs the input", function () { // needs to be rendered into document.body or cannot query document.activeElement - var wrapper = ReactDOM.render(, document.body).refs.wrapped; + var wrapper = ReactDOM.render(, document.body).refs.wrapped; wrapper.state.dropdownVisible = true; var input = ReactDOM.findDOMNode(wrapper.refs.searchInput); @@ -150,7 +142,7 @@ describe("SearchBar", function() { it("invokes onSearch() when submitting the form", function() { var stubbedOnSearch = this.sandbox.spy(); - var wrapper = TestUtils.renderIntoDocument().refs.wrapped; + var wrapper = TestUtils.renderIntoDocument().refs.wrapped; TestUtils.Simulate.submit(wrapper.refs.searchForm, { preventDefault() {} }); @@ -159,6 +151,8 @@ describe("SearchBar", function() { it("invokes onSearch() when search is cleared", function(done) { var props = { + orgId: "123", + projectId: "456", query: "is:unresolved", onSearch: this.sandbox.spy() }; @@ -167,7 +161,7 @@ describe("SearchBar", function() { var cancelButton = findWithClass(wrapper, "search-clear-form"); TestUtils.Simulate.click(cancelButton); - setTimeout(() => { + setTimeout(function () { expect(props.onSearch.calledWith("")).to.be.true; done(); }); @@ -175,6 +169,8 @@ describe("SearchBar", function() { it("handles an empty query", function () { let props = { + orgId: "123", + projectId: "456", query: "", defaultQuery: "is:unresolved" }; @@ -187,6 +183,8 @@ describe("SearchBar", function() { describe("updateAutoCompleteItems()", function() { it("sets state when empty", function() { var props = { + orgId: "123", + projectId: "456", query: "", }; var wrapper = TestUtils.renderIntoDocument().refs.wrapped; @@ -198,6 +196,8 @@ describe("SearchBar", function() { it("sets state when incomplete tag", function() { var props = { + orgId: "123", + projectId: "456", query: "fu", }; var wrapper = TestUtils.renderIntoDocument().refs.wrapped; @@ -209,6 +209,8 @@ describe("SearchBar", function() { it("sets state with complete tag", function() { var props = { + orgId: "123", + projectId: "456", query: "url:\"fu\"", }; var wrapper = TestUtils.renderIntoDocument().refs.wrapped; @@ -220,6 +222,8 @@ describe("SearchBar", function() { it("sets state when incomplete tag as second input", function() { var props = { + orgId: "123", + projectId: "456", query: "is:unresolved fu", }; var wrapper = TestUtils.renderIntoDocument().refs.wrapped; @@ -233,6 +237,8 @@ describe("SearchBar", function() { it("sets state when value has colon", function() { var props = { + orgId: "123", + projectId: "456", query: "url:\"http://example.com\"", }; var wrapper = TestUtils.renderIntoDocument().refs.wrapped; diff --git a/tests/karma.conf.js b/tests/karma.conf.js index d734f3c03c69db..68d68dd56094a4 100644 --- a/tests/karma.conf.js +++ b/tests/karma.conf.js @@ -25,8 +25,7 @@ module.exports = function(config) { cache: true, resolve: { alias: { - "app": appPrefix, - "react-router": path.join(__dirname, "..", "node_modules", "react-router", "build", "lib") + "app": appPrefix }, modulesDirectories: ["node_modules"], extensions: ["", ".jsx", ".js", ".json"] diff --git a/webpack.config.js b/webpack.config.js index 570ee74dbad947..17a1a3451fba09 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -79,8 +79,7 @@ var config = { resolve: { alias: { "flot": path.join(__dirname, staticPrefix, "vendor", "jquery-flot"), - "flot-tooltip": path.join(__dirname, staticPrefix, "vendor", "jquery-flot-tooltip"), - "react-router": path.join(__dirname, "node_modules", "react-router", "build", "lib") + "flot-tooltip": path.join(__dirname, staticPrefix, "vendor", "jquery-flot-tooltip") }, modulesDirectories: [path.join(__dirname, staticPrefix), "node_modules"], extensions: ["", ".jsx", ".js", ".json"]