Skip to content

Commit 57271c2

Browse files
committed
fix(app-platform): Render unpublished integrations
A bug was causing Issue pages in Orgs that had an unpublished Integration installed to error out. This was only case for one or two Orgs, as far as I can tell (our test Orgs). This fixes that bug and refactors some related things: - Retrieve SentryAppInstallations from Store instead of passing down through components - Handle when the "give me all SentryApps" and "give me all MY SentryApps" endpoints return the same records
1 parent bbe343e commit 57271c2

File tree

6 files changed

+137
-62
lines changed

6 files changed

+137
-62
lines changed

src/sentry/static/sentry/app/actionCreators/sentryAppComponents.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import SentryAppComponentsActions from 'app/actions/sentryAppComponentActions';
33
export function fetchSentryAppComponents(api, orgSlug, projectId) {
44
const componentsUri = `/organizations/${orgSlug}/sentry-app-components/?projectId=${projectId}`;
55

6-
const promise = api.requestPromise(componentsUri);
7-
promise.then(res => SentryAppComponentsActions.loadComponents(res));
8-
return promise;
6+
return api.requestPromise(componentsUri).then(res => {
7+
SentryAppComponentsActions.loadComponents(res);
8+
});
99
}

src/sentry/static/sentry/app/components/group/externalIssuesList.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ class ExternalIssueList extends AsyncComponent {
123123

124124
return components.map(component => {
125125
const {sentryApp} = component;
126-
127126
const installation = sentryAppInstallations.find(
128127
i => i.sentryApp.uuid === sentryApp.uuid
129128
);

src/sentry/static/sentry/app/components/group/sidebar.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const GroupSidebar = createReactClass({
2727
group: SentryTypes.Group,
2828
event: SentryTypes.Event,
2929
environments: PropTypes.arrayOf(SentryTypes.Environment),
30-
sentryAppInstallations: PropTypes.array,
3130
},
3231

3332
contextTypes: {
@@ -235,7 +234,7 @@ const GroupSidebar = createReactClass({
235234
},
236235

237236
render() {
238-
const {group, project, sentryAppInstallations} = this.props;
237+
const {group, project} = this.props;
239238
const projectId = project.slug;
240239
const organization = this.getOrganization();
241240

src/sentry/static/sentry/app/stores/sentryAppStore.jsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Reflux from 'reflux';
2+
import {uniqBy} from 'lodash';
23

34
const SentryAppStore = Reflux.createStore({
45
init() {
@@ -11,7 +12,14 @@ const SentryAppStore = Reflux.createStore({
1112

1213
load(items) {
1314
this.items = items;
14-
this.trigger(items);
15+
this.deDup();
16+
this.trigger(this.items);
17+
},
18+
19+
add(...apps) {
20+
apps.forEach(app => this.items.push(app));
21+
this.deDup();
22+
this.trigger(this.items);
1523
},
1624

1725
get(slug) {
@@ -21,6 +29,10 @@ const SentryAppStore = Reflux.createStore({
2129
getAll() {
2230
return this.items;
2331
},
32+
33+
deDup() {
34+
this.items = uniqBy(this.items, i => i.uuid);
35+
},
2436
});
2537

2638
export default SentryAppStore;

src/sentry/static/sentry/app/utils/fetchSentryAppInstallations.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ import SentryAppStore from 'app/stores/sentryAppStore';
33

44
const fetchSentryAppInstallations = (api, orgSlug) => {
55
const sentryAppsUri = '/sentry-apps/';
6+
const ownedSentryAppsUri = `/organizations/${orgSlug}/sentry-apps/`;
67
const installsUri = `/organizations/${orgSlug}/sentry-app-installations/`;
78

89
function updateSentryAppStore(sentryApps) {
910
SentryAppStore.load(sentryApps);
1011
}
1112

13+
function fetchOwnedSentryApps() {
14+
api.requestPromise(ownedSentryAppsUri).then(apps => SentryAppStore.add(...apps));
15+
}
16+
1217
function fetchInstalls() {
1318
api
1419
.requestPromise(installsUri)
@@ -28,6 +33,7 @@ const fetchSentryAppInstallations = (api, orgSlug) => {
2833
api
2934
.requestPromise(sentryAppsUri)
3035
.then(updateSentryAppStore)
36+
.then(fetchOwnedSentryApps)
3137
.then(fetchInstalls);
3238
};
3339

tests/js/spec/views/groupDetails/groupEventDetails.spec.jsx

Lines changed: 114 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,7 @@ describe('groupEventDetails', () => {
1212
let group;
1313
let event;
1414

15-
beforeEach(() => {
16-
const props = initializeOrg();
17-
org = props.organization;
18-
project = props.project;
19-
project.organization = org;
20-
routerContext = props.routerContext;
21-
22-
group = TestStubs.Group();
23-
event = TestStubs.Event({
24-
size: 1,
25-
dateCreated: '2019-03-20T00:00:00.000Z',
26-
errors: [],
27-
entries: [],
28-
tags: [{key: 'environment', value: 'dev'}],
29-
});
30-
15+
const mockGroupApis = () => {
3116
MockApiClient.addMockResponse({
3217
url: `/issues/${group.id}/`,
3318
body: group,
@@ -67,12 +52,36 @@ describe('groupEventDetails', () => {
6752
url: `/groups/${group.id}/integrations/`,
6853
body: [],
6954
});
55+
};
56+
57+
beforeEach(() => {
58+
const props = initializeOrg();
59+
org = props.organization;
60+
project = props.project;
61+
project.organization = org;
62+
routerContext = props.routerContext;
63+
64+
group = TestStubs.Group();
65+
event = TestStubs.Event({
66+
size: 1,
67+
dateCreated: '2019-03-20T00:00:00.000Z',
68+
errors: [],
69+
entries: [],
70+
tags: [{key: 'environment', value: 'dev'}],
71+
});
72+
73+
mockGroupApis();
7074

7175
MockApiClient.addMockResponse({
7276
url: '/sentry-apps/',
7377
body: [],
7478
});
7579

80+
MockApiClient.addMockResponse({
81+
url: `/organizations/${org.slug}/sentry-apps/`,
82+
body: [],
83+
});
84+
7685
MockApiClient.addMockResponse({
7786
url: `/organizations/${org.slug}/sentry-app-installations/`,
7887
body: [],
@@ -192,49 +201,99 @@ describe('groupEventDetails', () => {
192201
});
193202
});
194203

195-
it('loads Sentry Apps', () => {
196-
const request = MockApiClient.addMockResponse({
197-
url: '/sentry-apps/',
198-
body: [],
204+
describe('Platform Integrations', () => {
205+
let wrapper; // eslint-disable-line
206+
let integrationsRequest;
207+
let orgIntegrationsRequest;
208+
let componentsRequest;
209+
210+
const mountWrapper = () => {
211+
return mount(
212+
<GroupEventDetails
213+
group={group}
214+
project={project}
215+
organization={org}
216+
environments={[{id: '1', name: 'dev', displayName: 'Dev'}]}
217+
params={{orgId: org.slug, groupId: group.id, eventId: '1'}}
218+
location={{query: {environment: 'dev'}}}
219+
/>,
220+
routerContext
221+
);
222+
};
223+
224+
beforeEach(() => {
225+
const integration = TestStubs.SentryApp();
226+
const unpublishedIntegration = TestStubs.SentryApp({status: 'unpublished'});
227+
const internalIntegration = TestStubs.SentryApp({status: 'internal'});
228+
229+
const unpublishedInstall = TestStubs.SentryAppInstallation({
230+
app: {
231+
slug: unpublishedIntegration.slug,
232+
uuid: unpublishedIntegration.uuid,
233+
},
234+
});
235+
236+
const internalInstall = TestStubs.SentryAppInstallation({
237+
app: {
238+
slug: internalIntegration.slug,
239+
uuid: internalIntegration.uuid,
240+
},
241+
});
242+
243+
const component = TestStubs.SentryAppComponent({
244+
sentryApp: {
245+
uuid: unpublishedIntegration.uuid,
246+
slug: unpublishedIntegration.slug,
247+
name: unpublishedIntegration.name,
248+
},
249+
});
250+
251+
MockApiClient.clearMockResponses();
252+
mockGroupApis();
253+
254+
MockApiClient.addMockResponse({
255+
url: `/projects/${org.slug}/${project.slug}/events/1/`,
256+
body: event,
257+
});
258+
259+
componentsRequest = MockApiClient.addMockResponse({
260+
url: `/organizations/${org.slug}/sentry-app-components/?projectId=${project.id}`,
261+
body: [component],
262+
});
263+
264+
MockApiClient.addMockResponse({
265+
url: `/projects/${org.slug}/${project.slug}/events/1/`,
266+
body: event,
267+
});
268+
269+
integrationsRequest = MockApiClient.addMockResponse({
270+
url: '/sentry-apps/',
271+
body: [integration],
272+
});
273+
274+
MockApiClient.addMockResponse({
275+
url: `/organizations/${org.slug}/sentry-app-installations/`,
276+
body: [unpublishedInstall, internalInstall],
277+
});
278+
279+
orgIntegrationsRequest = MockApiClient.addMockResponse({
280+
url: `/organizations/${org.slug}/sentry-apps/`,
281+
body: [unpublishedIntegration, internalIntegration],
282+
});
283+
284+
wrapper = mountWrapper();
199285
});
200286

201-
project.organization = org;
202-
203-
mount(
204-
<GroupEventDetails
205-
group={group}
206-
project={project}
207-
organization={org}
208-
environments={[{id: '1', name: 'dev', displayName: 'Dev'}]}
209-
params={{}}
210-
location={{}}
211-
/>,
212-
routerContext
213-
);
214-
215-
expect(request).toHaveBeenCalledTimes(1);
216-
});
217-
218-
it('loads sentry app components when flagged in', () => {
219-
const request = MockApiClient.addMockResponse({
220-
url: `/organizations/${org.slug}/sentry-app-components/?projectId=${project.id}`,
221-
body: [],
287+
it('loads Integrations', () => {
288+
expect(integrationsRequest).toHaveBeenCalled();
222289
});
223290

224-
project.organization = org;
225-
226-
mount(
227-
<GroupEventDetails
228-
group={group}
229-
project={project}
230-
organization={org}
231-
environments={[{id: '1', name: 'dev', displayName: 'Dev'}]}
232-
params={{}}
233-
location={{}}
234-
/>,
235-
routerContext
236-
);
291+
it('loads unpublished and internal Integrations', () => {
292+
expect(orgIntegrationsRequest).toHaveBeenCalled();
293+
});
237294

238-
expect(request).toHaveBeenCalledTimes(1);
295+
it('loads Integration UI components', () => {
296+
expect(componentsRequest).toHaveBeenCalled();
297+
});
239298
});
240299
});

0 commit comments

Comments
 (0)