Skip to content

Commit 810f042

Browse files
committed
Some initial work on an organization admin
1 parent b513d6b commit 810f042

File tree

8 files changed

+230
-11
lines changed

8 files changed

+230
-11
lines changed

src/sentry/api/endpoints/organization_index.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
from __future__ import absolute_import
22

3+
from django.db.models import Q
34
from rest_framework import serializers, status
45
from rest_framework.response import Response
56

67
from sentry import roles
78
from sentry.api.base import DocSection, Endpoint
89
from sentry.api.bases.organization import OrganizationPermission
10+
from sentry.api.paginator import OffsetPaginator
911
from sentry.api.serializers import serialize
1012
from sentry.models import (
11-
AuditLogEntryEvent, Organization, OrganizationMember
13+
AuditLogEntryEvent, Organization, OrganizationMember, OrganizationStatus
1214
)
1315
from sentry.utils.apidocs import scenario, attach_scenarios
1416

@@ -41,18 +43,46 @@ def get(self, request):
4143
user bound context. For API key based requests this will
4244
only return the organization that belongs to the key.
4345
46+
:qparam bool member: restrict results to organizations which you have
47+
membership
48+
4449
:auth: required
4550
"""
51+
member_only = request.GET.get('member') in ('1', 'true')
52+
53+
queryset = Organization.objects.filter(
54+
status=OrganizationStatus.VISIBLE,
55+
)
56+
4657
if request.auth:
4758
if hasattr(request.auth, 'project'):
48-
organizations = [request.auth.project.organization]
59+
queryset = queryset.filter(
60+
id=request.auth.project.organization_id
61+
)
4962
else:
50-
organizations = [request.auth.organization]
51-
else:
52-
organizations = Organization.objects.get_for_user(
53-
user=request.user,
63+
queryset = queryset.filter(
64+
id=request.auth.organization.id
65+
)
66+
elif member_only or not request.user.is_active_superuser():
67+
queryset = queryset.filter(
68+
id__in=OrganizationMember.objects.filter(
69+
user=request.user,
70+
).values('organization'),
5471
)
55-
return Response(serialize(organizations, request.user))
72+
73+
query = request.GET.get('query')
74+
if query:
75+
queryset = queryset.filter(
76+
Q(name__icontains=query) | Q(slug__icontains=query),
77+
)
78+
79+
return self.paginate(
80+
request=request,
81+
queryset=queryset,
82+
order_by='name',
83+
on_results=lambda x: serialize(x, request.user),
84+
paginator_cls=OffsetPaginator,
85+
)
5686

5787
# XXX: endpoint useless for end-users as it needs user context.
5888
def post(self, request):
File renamed without changes.

src/sentry/static/sentry/app/routes.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var Route = Router.Route;
44
var DefaultRoute = Router.DefaultRoute;
55

66
import Admin from "./views/admin";
7+
import AdminOrganizations from "./views/adminOrganizations";
78
import AdminOverview from "./views/adminOverview";
89
import App from "./views/app";
910
import GroupActivity from "./views/groupActivity";
@@ -35,6 +36,7 @@ import Stream from "./views/stream";
3536
var routes = (
3637
<Route name="app" path="/" handler={App}>
3738
<Route name="admin" path="/manage/" handler={Admin}>
39+
<Route name="adminOrganizations" path="organizations/" handler={AdminOrganizations} />
3840
<DefaultRoute name="adminOverview" handler={AdminOverview} />
3941
</Route>
4042
<Route path="/organizations/:orgId/" handler={OrganizationDetails}>

src/sentry/static/sentry/app/views/admin/index.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const Admin = React.createClass({
3838

3939
<h6 className="nav-header">Manage</h6>
4040
<ul className="nav nav-stacked">
41+
<ListLink to="adminOrganizations">Organizations</ListLink>
4142
<li><a href={`${urlPrefix}/manage/teams/`}>Teams</a></li>
4243
<li><a href={`${urlPrefix}/manage/projects/`}>Projects</a></li>
4344
<li><a href={`${urlPrefix}/manage/users/`}>Users</a></li>
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import jQuery from "jquery";
2+
import React from "react";
3+
import Router from "react-router";
4+
5+
import api from "../api";
6+
import LoadingError from "../components/loadingError";
7+
import LoadingIndicator from "../components/loadingIndicator";
8+
import Pagination from "../components/pagination";
9+
import RouteMixin from "../mixins/routeMixin";
10+
import SearchBar from "../components/searchBar.jsx";
11+
12+
const AdminOrganizations = React.createClass({
13+
mixins: [
14+
RouteMixin
15+
],
16+
17+
contextTypes: {
18+
router: React.PropTypes.func
19+
},
20+
21+
getInitialState() {
22+
let queryParams = this.context.router.getCurrentQuery();
23+
24+
return {
25+
data: [],
26+
loading: true,
27+
error: false,
28+
query: queryParams.query || '',
29+
pageLinks: '',
30+
};
31+
},
32+
33+
componentWillMount() {
34+
this.fetchData();
35+
},
36+
37+
remountComponent() {
38+
this.setState(this.getInitialState(), this.fetchData);
39+
},
40+
41+
fetchData() {
42+
let queryParams = this.context.router.getCurrentQuery();
43+
44+
api.request(`/organizations/`, {
45+
method: 'GET',
46+
data: queryParams,
47+
success: (data, _, jqXHR) => {
48+
this.setState({
49+
data: data,
50+
error: false,
51+
loading: false,
52+
pageLinks: jqXHR.getResponseHeader('Link')
53+
});
54+
},
55+
error: (error) => {
56+
this.setState({
57+
error: true,
58+
loading: false
59+
});
60+
}
61+
});
62+
},
63+
64+
routeDidChange() {
65+
let queryParams = this.context.router.getCurrentQuery();
66+
this.setState({
67+
query: queryParams.query,
68+
loading: true,
69+
error: false
70+
}, this.fetchData);
71+
},
72+
73+
onPage(cursor) {
74+
let router = this.context.router;
75+
let params = router.getCurrentParams();
76+
let queryParams = jQuery.extend({}, router.getCurrentQuery(), {
77+
cursor: cursor
78+
});
79+
router.transitionTo('adminOrganizations', params, queryParams);
80+
},
81+
82+
onSearch(query) {
83+
let router = this.context.router;
84+
let params = router.getCurrentParams();
85+
86+
let targetQueryParams = {};
87+
if (query !== '')
88+
targetQueryParams.query = query;
89+
90+
router.transitionTo("adminOrganizations", params, targetQueryParams);
91+
},
92+
93+
renderLoading() {
94+
return (
95+
<tr>
96+
<td colSpan="3">
97+
<LoadingIndicator />
98+
</td>
99+
</tr>
100+
);
101+
},
102+
103+
renderError() {
104+
return (
105+
<tr>
106+
<td colSpan="3">
107+
<LoadingError onRetry={this.remountComponent} />
108+
</td>
109+
</tr>
110+
);
111+
},
112+
113+
renderNoResults() {
114+
return (
115+
<tr>
116+
<td colSpan="3">
117+
<span className="icon icon-exclamation" />
118+
<p>Sorry, no results match your filters.</p>
119+
</td>
120+
</tr>
121+
);
122+
},
123+
124+
renderResults() {
125+
return this.state.data.map((item) => {
126+
return (
127+
<tr>
128+
<td>
129+
<Router.Link to="organizationDetails" params={{orgId: item.slug}}>
130+
{item.name}
131+
</Router.Link><br />
132+
<small>{item.slug}</small>
133+
</td>
134+
<td>&mdash;</td>
135+
<td>&mdash;</td>
136+
</tr>
137+
);
138+
});
139+
},
140+
141+
render() {
142+
return (
143+
<div>
144+
<div className="row">
145+
<div className="col-sm-7">
146+
<h3>Organizations</h3>
147+
</div>
148+
<div className="col-sm-5">
149+
<SearchBar defaultQuery=""
150+
placeholder="Search for an organization."
151+
query={this.state.query}
152+
onSearch={this.onSearch}
153+
/>
154+
</div>
155+
</div>
156+
157+
<table className="table">
158+
<thead>
159+
<tr>
160+
<th>Organization</th>
161+
<th style={{width: 100}}>Members</th>
162+
<th style={{width: 100}}>Projects</th>
163+
</tr>
164+
</thead>
165+
<tbody>
166+
{this.state.loading ?
167+
this.renderLoading()
168+
: (this.state.error ?
169+
this.renderError()
170+
: (this.state.data.length === 0 ?
171+
this.renderNoResults()
172+
:
173+
this.renderResults()
174+
))}
175+
</tbody>
176+
</table>
177+
<Pagination pageLinks={this.state.pageLinks} onPage={this.onPage} />
178+
</div>
179+
);
180+
}
181+
});
182+
183+
export default AdminOrganizations;

src/sentry/static/sentry/app/views/app.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ var App = React.createClass({
1717

1818
componentWillMount() {
1919
api.request('/organizations/', {
20+
query: {
21+
'member': '1'
22+
},
2023
success: (data) => {
2124
OrganizationStore.load(data);
2225
this.setState({

src/sentry/static/sentry/app/views/projectReleases/index.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import LoadingError from "../../components/loadingError";
55
import LoadingIndicator from "../../components/loadingIndicator";
66
import Pagination from "../../components/pagination";
77
import RouteMixin from "../../mixins/routeMixin";
8-
import SearchBar from "./searchBar.jsx";
8+
import SearchBar from "../../components/searchBar.jsx";
99

1010
import ReleaseList from "./releaseList";
1111

@@ -168,7 +168,6 @@ var ProjectReleases = React.createClass({
168168
<SearchBar defaultQuery=""
169169
placeholder="Search for a release."
170170
query={this.state.query}
171-
onQueryChange={this.onQueryChange}
172171
onSearch={this.onSearch}
173172
/>
174173
</div>

src/sentry/web/urls.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,6 @@ def init_all_applications():
175175
url(r'^account/settings/social/', include('social_auth.urls')),
176176

177177
# Admin
178-
url(r'^manage/$', react_page_view,
179-
name='sentry-admin-overview'),
180178
url(r'^manage/queue/$', AdminQueueView.as_view(),
181179
name='sentry-admin-queue'),
182180
url(r'^manage/status/environment/$', admin.status_env,
@@ -210,6 +208,9 @@ def init_all_applications():
210208
url(r'^manage/plugins/(?P<slug>[\w_-]+)/$', admin.configure_plugin,
211209
name='sentry-admin-configure-plugin'),
212210

211+
url(r'^manage/', react_page_view,
212+
name='sentry-admin-overview'),
213+
213214
# Legacy Redirects
214215
url(r'^docs/?$',
215216
RedirectView.as_view(url='https://docs.getsentry.com/hosted/', permanent=False),

0 commit comments

Comments
 (0)