Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"require": false,
"expect": false,
"sinon": false,
"MockApiClient": true,
"TestStubs": true,
"Raven": true,
"jest": true
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,8 @@
"sinon": "1.17.2",
"sinon-chai": "2.8.0",
"webpack-livereload-plugin": "^0.11.0"
},
"optionalDependencies": {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a dupe of @billyvg's work, i can back it out, but it looks like he hadn't landed it yet and is OOO for a bit

"fsevents": "^1.1.2"
}
}
51 changes: 51 additions & 0 deletions src/sentry/static/sentry/app/__mocks__/api.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export class Request {}

export class Client {
static mockResponses = [];

static clearMockResponses() {
Client.mockResponses = [];
}

static addMockResponse(response) {
Client.mockResponses.push({
statusCode: 200,
body: '',
method: 'GET',
...response
});
}

static findMockResponse(url, options) {
return Client.mockResponses.find(response => {
return url === response.url && (options.method || 'GET') === response.method;
});
}

request(url, options) {
let response = Client.findMockResponse(url, options);
if (!response) {
console.error(
'No mocked response found for request.',
url,
options.method || 'GET'
);
options.error &&
options.error({
status: 404,
responseText: 'HTTP 404',
responseJSON: null
});
} else if (response.statusCode !== 200) {
options.error &&
options.error({
status: response.statusCode,
responseText: JSON.stringify(response.body),
responseJSON: response.body
});
} else {
options.success && options.success(response.body);
}
options.complete && options.complete();
}
}
3 changes: 1 addition & 2 deletions src/sentry/static/sentry/app/components/forms/rangeField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export default class RangeField extends InputField {

static defaultProps = {
...InputField.defaultProps,
onChange: value => {},
formatLabel: value => value,
min: 0,
max: 100,
Expand Down Expand Up @@ -55,7 +54,7 @@ export default class RangeField extends InputField {
.on('slider:changed', (e, data) => {
let value = parseInt(data.value, 10);
$value.html(this.props.formatLabel(value));
this.props.onChange(value);
this.setValue(value);
})
.simpleSlider({
value: this.props.defaultValue || this.props.value,
Expand Down
74 changes: 58 additions & 16 deletions src/sentry/static/sentry/app/views/asyncView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,18 @@ class AsyncView extends React.Component {

// XXX: cant call this getInitialState as React whines
getDefaultState() {
return {
data: null,
let endpoints = this.getEndpoints();
let state = {
// has all data finished requesting?
loading: true,
error: false
// is there an error loading ANY data?
error: false,
errors: {}
};
endpoints.forEach(([stateKey, endpoint]) => {
state[stateKey] = null;
});
return state;
}

remountComponent() {
Expand All @@ -46,41 +53,74 @@ class AsyncView extends React.Component {

// TODO(dcramer): we'd like to support multiple initial api requests
fetchData() {
let endpoint = this.getEndpoint();
if (!endpoint) {
let endpoints = this.getEndpoints();
if (!endpoints.length) {
this.setState({
loading: false,
error: false
});
} else {
return;
}
// TODO(dcramer): this should cancel any existing API requests
this.setState({
loading: true,
error: false,
remainingRequests: endpoints.length
});
endpoints.forEach(([stateKey, endpoint, params]) => {
this.api.request(endpoint, {
method: 'GET',
params: this.getEndpointParams(),
params: params,
success: (data, _, jqXHR) => {
this.setState({
loading: false,
error: false,
data: data
this.setState(prevState => {
return {
[stateKey]: data,
remainingRequests: prevState.remainingRequests - 1,
loading: prevState.remainingRequests > 1
};
});
},
error: error => {
this.setState({
loading: false,
error: error
this.setState(prevState => {
return {
[stateKey]: null,
errors: {
...prevState.errors,
[stateKey]: error
},
remainingRequests: prevState.remainingRequests - 1,
loading: prevState.remainingRequests > 1,
error: true
};
});
}
});
}
});
}

// DEPRECATED: use getEndpoints()
getEndpointParams() {
return {};
}

// DEPRECATED: use getEndpoints()
getEndpoint() {
return null;
}

/**
* Return a list of endpoint queries to make.
*
* return [
* ['stateKeyName', '/endpoint/', {optional: 'query params'}]
* ]
*/
getEndpoints() {
let endpoint = this.getEndpoint();
if (!endpoint) return [];
return [['data', endpoint, this.getEndpointParams()]];
}

getTitle() {
return 'Sentry';
}
Expand All @@ -98,7 +138,9 @@ class AsyncView extends React.Component {
<DocumentTitle title={this.getTitle()}>
{this.state.loading
? this.renderLoading()
: this.state.error ? this.renderError(this.state.error) : this.renderBody()}
: this.state.error
? this.renderError(new Error('Unable to load all required endpoints'))
: this.renderBody()}
</DocumentTitle>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/static/sentry/app/views/organizationCreate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export default class OrganizationCreate extends AsyncView {
submitLabel={t('Create Organization')}
apiEndpoint="/organizations/"
apiMethod="POST"
onSubmitSuccess={this.onSubmitSuccess}>
onSubmitSuccess={this.onSubmitSuccess}
requireChanges={true}>
<TextField
name="name"
label={t('Organization Name')}
Expand Down
Loading