Skip to content

Commit 5aa04e5

Browse files
authored
feat(ui): Fix OrganizationContext sometimes double fetching for org details (#13655)
Fixes the race condition where `OrganizationContext` gets loaded twice if `OrganizationStore` finishes loading *after* `OrganizationDetails.fetchData` finishes.
1 parent 9e42cc3 commit 5aa04e5

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,16 @@ const OrganizationContext = createReactClass({
7171
this.props.params.orgId &&
7272
prevProps.params.orgId !== this.props.params.orgId;
7373

74+
// protect against the case where we finish fetching org details
75+
// and then `OrganizationsStore` finishes loading:
76+
// only fetch in the case where we don't have an orgId
7477
const organizationLoadingChanged =
7578
prevProps.organizationsLoading !== this.props.organizationsLoading &&
7679
this.props.organizationsLoading === false;
7780

7881
if (
7982
hasOrgIdAndChanged ||
80-
organizationLoadingChanged ||
83+
(!this.props.params.orgId && organizationLoadingChanged) ||
8184
(this.props.location.state === 'refresh' && prevProps.location.state !== 'refresh')
8285
) {
8386
this.remountComponent();
@@ -146,6 +149,7 @@ const OrganizationContext = createReactClass({
146149
})
147150
.catch(err => {
148151
let errorType = null;
152+
149153
switch (err.statusText) {
150154
case 'NOT FOUND':
151155
errorType = ERROR_TYPES.ORG_NOT_FOUND;
@@ -161,6 +165,10 @@ const OrganizationContext = createReactClass({
161165
// If user is superuser, open sudo window
162166
const user = ConfigStore.get('user');
163167
if (!user || !user.isSuperuser || err.status !== 403) {
168+
// This `catch` can swallow up errors in development (and tests)
169+
// So let's log them. This may create some noise, especially the test case where
170+
// we specifically test this branch
171+
console.error(err); // eslint-disable-line no-console
164172
return;
165173
}
166174
openSudo({

tests/js/spec/views/organizationContext.spec.jsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ describe('OrganizationContext', function() {
8787

8888
it('fetches new org when router params change', function() {
8989
wrapper = createWrapper();
90+
MockApiClient.addMockResponse({
91+
url: '/organizations/new-slug/environments/',
92+
body: TestStubs.Environments(),
93+
});
9094
const mock = MockApiClient.addMockResponse({
9195
url: '/organizations/new-slug/',
9296
body: org,
@@ -140,8 +144,13 @@ describe('OrganizationContext', function() {
140144
});
141145

142146
it('uses last organization from ConfigStore', function() {
147+
MockApiClient.addMockResponse({
148+
url: '/organizations/lastOrganization/environments/',
149+
body: TestStubs.Environments(),
150+
});
143151
getOrgMock = MockApiClient.addMockResponse({
144152
url: '/organizations/lastOrganization/',
153+
body: org,
145154
});
146155
// mocking `.get('lastOrganization')`
147156
ConfigStore.get.mockImplementation(() => 'lastOrganization');
@@ -153,8 +162,13 @@ describe('OrganizationContext', function() {
153162
});
154163

155164
it('uses last organization from `organizations` prop', async function() {
165+
MockApiClient.addMockResponse({
166+
url: '/organizations/foo/environments/',
167+
body: TestStubs.Environments(),
168+
});
156169
getOrgMock = MockApiClient.addMockResponse({
157170
url: '/organizations/foo/',
171+
body: org,
158172
});
159173
ConfigStore.get.mockImplementation(() => '');
160174

@@ -182,4 +196,28 @@ describe('OrganizationContext', function() {
182196

183197
expect(getOrgMock).toHaveBeenLastCalledWith('/organizations/foo/', expect.anything());
184198
});
199+
200+
it('fetches org details only once if organizations loading store changes', async function() {
201+
wrapper = createWrapper({
202+
params: {orgId: 'org-slug'},
203+
organizationsLoading: true,
204+
organizations: [],
205+
});
206+
await tick();
207+
wrapper.update();
208+
expect(wrapper.find('LoadingIndicator')).toHaveLength(0);
209+
expect(getOrgMock).toHaveBeenCalledTimes(1);
210+
211+
// Simulate OrganizationsStore being loaded *after* `OrganizationContext` finishes
212+
// org details fetch
213+
wrapper.setProps({
214+
organizationsLoading: false,
215+
organizations: [
216+
TestStubs.Organization({slug: 'foo'}),
217+
TestStubs.Organization({slug: 'bar'}),
218+
],
219+
});
220+
221+
expect(getOrgMock).toHaveBeenCalledTimes(1);
222+
});
185223
});

0 commit comments

Comments
 (0)