Skip to content

Commit 5454cbc

Browse files
authored
feat(ui): Semi-fix loading initial data on issue details (#18419)
This basically re-implements #13689 in a more simple way. This is possible due to other changes that we have done to issue details (#13875, #14731). This also (semi) fixes an issue with loading Issue Details with an environment in the URL. Previously, it would fetch details API serially: 1) without env and 2) with env. There would be a slight flicker between loading -> finished req #1 -> loading -> finished req #2. Now this seems to fire both at near the same time and cancels the initial request almost immediately. This is still not ideal but is an interim-fix as the ideal solution is a bit more involved, but will be on its way. The tests introduced in #18452 should cover the changes in this PR.
1 parent d20e51d commit 5454cbc

File tree

8 files changed

+26
-121
lines changed

8 files changed

+26
-121
lines changed

src/sentry/static/sentry/app/components/organizations/globalSelectionHeader/globalSelectionHeader.tsx

+1-42
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,6 @@ type Props = {
111111
*/
112112
forceProject?: MinimalProject | null;
113113

114-
/**
115-
* GlobalSelectionStore is not always initialized (e.g. Group Details) before this is rendered
116-
*
117-
* This component intentionally attempts to sync store --> URL Parameter
118-
* only when mounted, except when this prop changes.
119-
*
120-
* XXX: This comes from GlobalSelectionStore and currently does not reset,
121-
* so it happens at most once. Can add a reset as needed.
122-
*/
123-
forceUrlSync: boolean;
124-
125114
/// Props passed to child components ///
126115

127116
/**
@@ -198,7 +187,6 @@ class GlobalSelectionHeader extends React.Component<Props, State> {
198187
showDateSelector: PropTypes.bool,
199188
hasCustomRouting: PropTypes.bool,
200189
resetParamsOnChange: PropTypes.arrayOf(PropTypes.string),
201-
forceUrlSync: PropTypes.bool,
202190
showAbsolute: PropTypes.bool,
203191
showRelative: PropTypes.bool,
204192
allowClearTimeRange: PropTypes.bool,
@@ -337,11 +325,6 @@ class GlobalSelectionHeader extends React.Component<Props, State> {
337325
return true;
338326
}
339327

340-
// Update if `forceUrlSync` changes
341-
if (!this.props.forceUrlSync && nextProps.forceUrlSync) {
342-
return true;
343-
}
344-
345328
if (this.props.organization !== nextProps.organization) {
346329
return true;
347330
}
@@ -350,14 +333,7 @@ class GlobalSelectionHeader extends React.Component<Props, State> {
350333
}
351334

352335
componentDidUpdate(prevProps) {
353-
const {
354-
hasCustomRouting,
355-
location,
356-
selection,
357-
forceUrlSync,
358-
forceProject,
359-
shouldForceProject,
360-
} = this.props;
336+
const {hasCustomRouting, location, forceProject, shouldForceProject} = this.props;
361337

362338
if (hasCustomRouting) {
363339
return;
@@ -393,23 +369,6 @@ class GlobalSelectionHeader extends React.Component<Props, State> {
393369
}
394370
}
395371

396-
if (forceUrlSync && !prevProps.forceUrlSync) {
397-
const {project, environment} = getStateFromQuery(location.query);
398-
399-
if (
400-
!isEqual(project, selection.projects) ||
401-
!isEqual(environment, selection.environments)
402-
) {
403-
updateParamsWithoutHistory(
404-
{
405-
project: selection.projects,
406-
environment: selection.environments,
407-
},
408-
this.getRouter()
409-
);
410-
}
411-
}
412-
413372
// If component has updated (e.g. due to re-render from a router action),
414373
// update store values with values from router. Router should be source of truth
415374
this.updateStoreIfChange(prevProps, this.props);

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

+5-17
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,7 @@ const GlobalSelectionStore = Reflux.createStore({
8484
* Initializes the global selection store
8585
* If there are query params apply these, otherwise check local storage
8686
*/
87-
loadInitialData(
88-
organization,
89-
queryParams,
90-
{api, forceUrlSync, onlyIfNeverLoaded} = {}
91-
) {
92-
// If this option is true, only load if it has never been loaded before
93-
if (onlyIfNeverLoaded && this._hasLoaded) {
94-
return;
95-
}
96-
87+
loadInitialData(organization, queryParams, {api, skipLastUsed} = {}) {
9788
this._hasLoaded = true;
9889
this.organization = organization;
9990
const query = pick(queryParams, Object.values(URL_PARAM));
@@ -113,7 +104,7 @@ const GlobalSelectionStore = Reflux.createStore({
113104
[DATE_TIME.UTC]: parsed.utc || null,
114105
},
115106
};
116-
} else {
107+
} else if (!skipLastUsed) {
117108
try {
118109
const localStorageKey = `${LOCAL_STORAGE_KEY}:${organization.slug}`;
119110

@@ -129,15 +120,12 @@ const GlobalSelectionStore = Reflux.createStore({
129120
// use default if invalid
130121
}
131122
}
132-
this.loadSelectionIfValid(globalSelection, organization, forceUrlSync, api);
123+
this.loadSelectionIfValid(globalSelection, organization, api);
133124
},
134125

135-
async loadSelectionIfValid(globalSelection, organization, forceUrlSync, api) {
126+
async loadSelectionIfValid(globalSelection, organization, api) {
136127
if (await isValidSelection(globalSelection, organization, api)) {
137-
this.selection = {
138-
...globalSelection,
139-
...(forceUrlSync ? {forceUrlSync: true} : {}),
140-
};
128+
this.selection = globalSelection;
141129
this.trigger(this.selection);
142130
}
143131
},

src/sentry/static/sentry/app/types/index.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,6 @@ export type DocumentIntegration = {
448448
export type GlobalSelection = {
449449
projects: number[];
450450
environments: string[];
451-
forceUrlSync?: boolean;
452451
datetime: {
453452
start: Date | null;
454453
end: Date | null;

src/sentry/static/sentry/app/utils/withGlobalSelection.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import getDisplayName from 'app/utils/getDisplayName';
77
import {GlobalSelection} from 'app/types';
88

99
type InjectedGlobalSelectionProps = {
10-
forceUrlSync?: boolean;
1110
selection: GlobalSelection;
1211
};
1312

@@ -52,10 +51,9 @@ const withGlobalSelection = <P extends InjectedGlobalSelectionProps>(
5251
},
5352

5453
render() {
55-
const {forceUrlSync, ...selection} = this.state.selection;
54+
const {selection} = this.state;
5655
return (
5756
<WrappedComponent
58-
forceUrlSync={!!forceUrlSync}
5957
selection={selection as GlobalSelection}
6058
{...(this.props as P)}
6159
/>

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

+7-9
Original file line numberDiff line numberDiff line change
@@ -205,15 +205,13 @@ const OrganizationContext = createReactClass({
205205
// Make an exception for issue details in the case where it is accessed directly (e.g. from email)
206206
// We do not want to load the user's last used env/project in this case, otherwise will
207207
// lead to very confusing behavior.
208-
if (
209-
!this.props.routes.find(
210-
({path}) => path && path.includes('/organizations/:orgId/issues/:groupId/')
211-
)
212-
) {
213-
GlobalSelectionStore.loadInitialData(organization, this.props.location.query, {
214-
api: this.props.api,
215-
});
216-
}
208+
const skipLastUsed = !!this.props.routes.find(
209+
({path}) => path && path.includes('/organizations/:orgId/issues/:groupId/')
210+
);
211+
GlobalSelectionStore.loadInitialData(organization, this.props.location.query, {
212+
skipLastUsed,
213+
api: this.props.api,
214+
});
217215
} else if (error) {
218216
// If user is superuser, open sudo window
219217
const user = ConfigStore.get('user');

src/sentry/static/sentry/app/views/organizationGroupDetails/groupEventDetails/groupEventDetails.tsx

+1-21
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {Client} from 'app/api';
99
import {fetchSentryAppComponents} from 'app/actionCreators/sentryAppComponents';
1010
import {withMeta} from 'app/components/events/meta/metaProxy';
1111
import EventEntries from 'app/components/events/eventEntries';
12-
import GlobalSelectionStore from 'app/stores/globalSelectionStore';
1312
import GroupEventDetailsLoadingError from 'app/components/errors/groupEventDetailsLoadingError';
1413
import GroupSidebar from 'app/components/group/sidebar';
1514
import LoadingIndicator from 'app/components/loadingIndicator';
@@ -98,26 +97,7 @@ class GroupEventDetails extends React.Component<Props, State> {
9897
}
9998

10099
componentWillUnmount() {
101-
const {api, organization} = this.props;
102-
103-
// Note: We do not load global selection store with any data when this component is used
104-
// This is handled in `<OrganizationContext>` by examining the routes.
105-
//
106-
// When this view gets unmounted, attempt to load initial data so that projects/envs
107-
// gets loaded with the last used one (via local storage). `forceUrlSync` will make
108-
// `<GlobalSelectionHeader>` sync values from store to the URL (if they are different),
109-
// otherwise they can out of sync because the component only syncs in `DidMount`, and
110-
// the timing for that is not guaranteed.
111-
//
112-
// TBD: if this behavior is actually desired
113-
if (organization.projects) {
114-
GlobalSelectionStore.loadInitialData(organization, this.props.location.query, {
115-
api: this.props.api,
116-
onlyIfNeverLoaded: true,
117-
forceUrlSync: true,
118-
});
119-
}
120-
100+
const {api} = this.props;
121101
api.clear();
122102
}
123103

tests/js/spec/components/organizations/globalSelectionHeader.spec.jsx

-25
Original file line numberDiff line numberDiff line change
@@ -301,31 +301,6 @@ describe('GlobalSelectionHeader', function() {
301301
expect(checkboxes.text()).toBe('staging');
302302
});
303303

304-
it('updates URL to match GlobalSelection store when re-rendered with `forceUrlSync` prop', async function() {
305-
const wrapper = mountWithTheme(
306-
<GlobalSelectionHeader router={router} organization={organization} />,
307-
routerContext
308-
);
309-
310-
await tick();
311-
wrapper.update();
312-
313-
// Force load, will load from mocked localStorage
314-
GlobalSelectionStore.loadInitialData(organization, {}, {forceUrlSync: true});
315-
316-
await tick();
317-
wrapper.update();
318-
319-
expect(router.replace).toHaveBeenCalledWith(
320-
expect.objectContaining({
321-
query: {
322-
environment: ['staging'],
323-
project: [3],
324-
},
325-
})
326-
);
327-
});
328-
329304
it('updates GlobalSelection store with default period', async function() {
330305
mountWithTheme(
331306
<GlobalSelectionHeader organization={organization} />,

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,11 @@ describe('OrganizationContext', function() {
8989
true,
9090
true
9191
);
92-
expect(GlobalSelectionStore.loadInitialData).toHaveBeenCalledWith(org, {}, {api});
92+
expect(GlobalSelectionStore.loadInitialData).toHaveBeenCalledWith(
93+
org,
94+
{},
95+
{api, skipLastUsed: false}
96+
);
9397
});
9498

9599
it('fetches new org when router params change', async function() {
@@ -259,7 +263,7 @@ describe('OrganizationContext', function() {
259263
expect(getOrgMock).toHaveBeenCalledTimes(1);
260264
});
261265

262-
it('does not call `GlobalSelectionStore.loadInitialData` on group details route', async function() {
266+
it('calls `GlobalSelectionStore.loadInitialData` with `skipLastUsed` option when loadigno group details route', async function() {
263267
expect(GlobalSelectionStore.loadInitialData).not.toHaveBeenCalled();
264268
wrapper = createWrapper({
265269
routes: [{path: '/organizations/:orgId/issues/:groupId/'}],
@@ -273,6 +277,10 @@ describe('OrganizationContext', function() {
273277
expect(wrapper.state('loading')).toBe(false);
274278
expect(wrapper.state('error')).toBe(null);
275279

276-
expect(GlobalSelectionStore.loadInitialData).not.toHaveBeenCalled();
280+
expect(GlobalSelectionStore.loadInitialData).toHaveBeenCalledWith(
281+
org,
282+
{},
283+
{api, skipLastUsed: true}
284+
);
277285
});
278286
});

0 commit comments

Comments
 (0)