Skip to content

Commit

Permalink
Revert "ref(issue-views): Fold navigate behavior into issueViews cont…
Browse files Browse the repository at this point in the history
…ext (#82540)"

This reverts commit 518b63d.

Co-authored-by: malwilley <[email protected]>
  • Loading branch information
getsentry-bot and malwilley committed Jan 3, 2025
1 parent c563acb commit 520e316
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 114 deletions.
17 changes: 11 additions & 6 deletions static/app/views/issueList/customViewsHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,17 @@ function CustomViewsIssueListHeaderTabsContent({
}
if (query) {
if (!tabListState?.selectionManager.isSelected(TEMPORARY_TAB_KEY)) {
dispatch({
type: 'SET_TEMP_VIEW',
query,
sort,
updateQueryParams: {newQueryParams: {viewId: undefined}, replace: true},
});
dispatch({type: 'SET_TEMP_VIEW', query, sort});
navigate(
normalizeUrl({
...location,
query: {
...queryParamsWithPageFilters,
viewId: undefined,
},
}),
{replace: true}
);
tabListState?.setSelectedKey(TEMPORARY_TAB_KEY);
return;
}
Expand Down
116 changes: 72 additions & 44 deletions static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {InjectedRouter} from 'sentry/types/legacyReactRouter';
import normalizeUrl from 'sentry/utils/url/normalizeUrl';
import {useHotkeys} from 'sentry/utils/useHotkeys';
import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import {DraggableTabMenuButton} from 'sentry/views/issueList/groupSearchViewTabs/draggableTabMenuButton';
import EditableTabTitle from 'sentry/views/issueList/groupSearchViewTabs/editableTabTitle';
Expand All @@ -39,6 +40,7 @@ export function DraggableTabBar({initialTabKey, router}: DraggableTabBarProps) {
const [editingTabKey, setEditingTabKey] = useState<string | null>(null);

const organization = useOrganization();
const navigate = useNavigate();
const location = useLocation();

const {cursor: _cursor, page: _page, ...queryParams} = router?.location?.query ?? {};
Expand All @@ -64,6 +66,43 @@ export function DraggableTabBar({initialTabKey, router}: DraggableTabBarProps) {
[dispatch, tabListState?.selectedKey, tabs]
);

const handleDuplicateView = () => {
const newViewId = generateTempViewId();
const duplicatedTab = state.views.find(
view => view.key === tabListState?.selectedKey
);
if (!duplicatedTab) {
return;
}
dispatch({type: 'DUPLICATE_VIEW', newViewId, syncViews: true});
navigate({
...location,
query: {
...queryParams,
query: duplicatedTab.query,
sort: duplicatedTab.querySort,
viewId: newViewId,
},
});
};

const handleDiscardChanges = () => {
dispatch({type: 'DISCARD_CHANGES'});
const originalTab = state.views.find(view => view.key === tabListState?.selectedKey);
if (originalTab) {
// TODO(msun): Move navigate logic to IssueViewsContext
navigate({
...location,
query: {
...queryParams,
query: originalTab.query,
sort: originalTab.querySort,
viewId: originalTab.id,
},
});
}
};

const handleNewViewsSaved: NewTabContext['onNewViewsSaved'] = useCallback<
NewTabContext['onNewViewsSaved']
>(
Expand Down Expand Up @@ -106,23 +145,37 @@ export function DraggableTabBar({initialTabKey, router}: DraggableTabBarProps) {
updatedTabs = [...updatedTabs, ...remainingNewViews];
}

dispatch({
type: 'SET_VIEWS',
views: updatedTabs,
syncViews: true,
updateQueryParams: {
newQueryParams: {query, sort: IssueSortOptions.DATE},
replace: true,
dispatch({type: 'SET_VIEWS', views: updatedTabs, syncViews: true});
navigate(
{
...location,
query: {
...queryParams,
query,
sort: IssueSortOptions.DATE,
},
},
});
{replace: true}
);
},

// eslint-disable-next-line react-hooks/exhaustive-deps
[location, setNewViewActive, tabs, viewId]
[location, navigate, setNewViewActive, tabs, viewId]
);

useEffect(() => {
setOnNewViewsSaved(handleNewViewsSaved);
}, [setOnNewViewsSaved, handleNewViewsSaved]);
const handleCreateNewView = () => {
const tempId = generateTempViewId();
dispatch({type: 'CREATE_NEW_VIEW', tempId});
tabListState?.setSelectedKey(tempId);
navigate({
...location,
query: {
...queryParams,
query: '',
viewId: tempId,
},
});
};

const handleDeleteView = (tab: IssueView) => {
dispatch({type: 'DELETE_VIEW', syncViews: true});
Expand All @@ -131,28 +184,9 @@ export function DraggableTabBar({initialTabKey, router}: DraggableTabBarProps) {
tabListState?.setSelectedKey(tabs.filter(tb => tb.key !== tab.key)[0]!.key);
};

const handleDuplicateView = (tab: IssueView) => {
const newViewId = generateTempViewId();
dispatch({
type: 'DUPLICATE_VIEW',
newViewId,
syncViews: true,
updateQueryParams: {
newQueryParams: {viewId: newViewId, query: tab.query, sort: tab.querySort},
},
});
};

const handleAddView = () => {
const tempId = generateTempViewId();
dispatch({
type: 'CREATE_NEW_VIEW',
tempId,
updateQueryParams: {
newQueryParams: {viewId: tempId, query: ''},
},
});
};
useEffect(() => {
setOnNewViewsSaved(handleNewViewsSaved);
}, [setOnNewViewsSaved, handleNewViewsSaved]);

const makeMenuOptions = (tab: IssueView): MenuItemProps[] => {
if (tab.key === TEMPORARY_TAB_KEY) {
Expand All @@ -164,21 +198,15 @@ export function DraggableTabBar({initialTabKey, router}: DraggableTabBarProps) {
if (tab.unsavedChanges) {
return makeUnsavedChangesMenuOptions({
onRename: () => setEditingTabKey(tab.key),
onDuplicate: () => handleDuplicateView(tab),
onDuplicate: handleDuplicateView,
onDelete: tabs.length > 1 ? () => handleDeleteView(tab) : undefined,
onSave: () => dispatch({type: 'SAVE_CHANGES', syncViews: true}),
onDiscard: () =>
dispatch({
type: 'DISCARD_CHANGES',
updateQueryParams: {
newQueryParams: {viewId: tab.id, query: tab.query, sort: tab.querySort},
},
}),
onDiscard: handleDiscardChanges,
});
}
return makeDefaultMenuOptions({
onRename: () => setEditingTabKey(tab.key),
onDuplicate: () => handleDuplicateView(tab),
onDuplicate: handleDuplicateView,
onDelete: tabs.length > 1 ? () => handleDeleteView(tab) : undefined,
});
};
Expand All @@ -195,7 +223,7 @@ export function DraggableTabBar({initialTabKey, router}: DraggableTabBarProps) {
}
onReorderComplete={() => dispatch({type: 'SYNC_VIEWS_TO_BACKEND'})}
defaultSelectedKey={initialTabKey}
onAddView={handleAddView}
onAddView={handleCreateNewView}
orientation="horizontal"
editingTabKey={editingTabKey ?? undefined}
hideBorder
Expand Down
91 changes: 27 additions & 64 deletions static/app/views/issueList/groupSearchViewTabs/issueViews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,9 @@ export interface IssueView {
unsavedChanges?: [string, IssueSortOptions];
}

export type IssueViewsQueryUpdateParams = {
// TODO(msun): Once project/env/datetime are added as view configs, add them here
newQueryParams: {
query?: string;
sort?: IssueSortOptions;
viewId?: string;
};
replace?: boolean;
};

type BaseIssueViewsAction = {
/** If true, the new views state created by the action will be synced to the backend */
syncViews?: boolean;
updateQueryParams?: IssueViewsQueryUpdateParams;
};

type ReorderTabsAction = {
Expand Down Expand Up @@ -123,7 +112,7 @@ type SetViewsAction = {
type SyncViewsToBackendAction = {
/** Syncs the current views state to the backend. Does not make any changes to the views state. */
type: 'SYNC_VIEWS_TO_BACKEND';
} & Exclude<BaseIssueViewsAction, 'syncViews'>;
};

export type IssueViewsActions =
| ReorderTabsAction
Expand Down Expand Up @@ -258,11 +247,7 @@ function deleteView(state: IssueViewsState, tabListState: TabListState<any>) {
return {...state, views: newViews};
}

function createNewView(
state: IssueViewsState,
action: CreateNewViewAction,
tabListState: TabListState<any>
) {
function createNewView(state: IssueViewsState, action: CreateNewViewAction) {
const newTabs: IssueView[] = [
...state.views,
{
Expand All @@ -274,7 +259,6 @@ function createNewView(
isCommitted: false,
},
];
tabListState?.setSelectedKey(action.tempId);
return {...state, views: newTabs};
}

Expand Down Expand Up @@ -433,6 +417,30 @@ export function IssueViewsStateProvider({
onSuccess: replaceWithPersistantViewIds,
});

const debounceUpdateViews = useMemo(
() =>
debounce((newTabs: IssueView[]) => {
if (newTabs) {
updateViews({
orgSlug: organization.slug,
groupSearchViews: newTabs
.filter(tab => tab.isCommitted)
.map(tab => ({
// Do not send over an ID if it's a temporary or default tab so that
// the backend will save these and generate permanent Ids for them
...(tab.id[0] !== '_' && !tab.id.startsWith('default')
? {id: tab.id}
: {}),
name: tab.label,
query: tab.query,
querySort: tab.querySort,
})),
});
}
}, 500),
[organization.slug, updateViews]
);

const reducer: Reducer<IssueViewsState, IssueViewsActions> = useCallback(
(state, action): IssueViewsState => {
if (!tabListState) {
Expand All @@ -452,7 +460,7 @@ export function IssueViewsStateProvider({
case 'DELETE_VIEW':
return deleteView(state, tabListState);
case 'CREATE_NEW_VIEW':
return createNewView(state, action, tabListState);
return createNewView(state, action);
case 'SET_TEMP_VIEW':
return setTempView(state, action);
case 'DISCARD_TEMP_VIEW':
Expand Down Expand Up @@ -496,59 +504,14 @@ export function IssueViewsStateProvider({
tempView: initialTempView,
});

const debounceUpdateViews = useMemo(
() =>
debounce((newTabs: IssueView[]) => {
if (newTabs) {
updateViews({
orgSlug: organization.slug,
groupSearchViews: newTabs
.filter(tab => tab.isCommitted)
.map(tab => ({
// Do not send over an ID if it's a temporary or default tab so that
// the backend will save these and generate permanent Ids for them
...(tab.id[0] !== '_' && !tab.id.startsWith('default')
? {id: tab.id}
: {}),
name: tab.label,
query: tab.query,
querySort: tab.querySort,
})),
});
}
}, 500),
[organization.slug, updateViews]
);

const updateQueryParams = (params: IssueViewsQueryUpdateParams) => {
const {newQueryParams, replace = false} = params;
navigate(
normalizeUrl({
...location,
query: {
...queryParams,
...newQueryParams,
},
}),
{replace}
);
};

const dispatchWrapper = (action: IssueViewsActions) => {
const newState = reducer(state, action);
dispatch(action);

// These actions are outside of the dispatch to avoid introducing side effects in the reducer
if (action.type === 'SYNC_VIEWS_TO_BACKEND' || action.syncViews) {
debounceUpdateViews(newState.views);
}

if (action.updateQueryParams) {
queueMicrotask(() => {
updateQueryParams(action.updateQueryParams!);
});
}

const actionAnalyticsKey = ACTION_ANALYTICS_MAP[action.type];
if (actionAnalyticsKey) {
trackAnalytics(actionAnalyticsKey, {
Expand Down

0 comments on commit 520e316

Please sign in to comment.