mirror of
https://github.com/langgenius/dify.git
synced 2026-06-07 16:32:01 +08:00
chore: not store search tag condition in url (#36814)
This commit is contained in:
parent
30270b5c30
commit
a392a72960
@ -45,6 +45,7 @@ vi.mock('@/next/navigation', () => ({
|
||||
push: mockRouterPush,
|
||||
replace: mockRouterReplace,
|
||||
}),
|
||||
usePathname: () => '/apps',
|
||||
useSearchParams: () => new URLSearchParams(),
|
||||
}))
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ vi.mock('@/next/navigation', () => ({
|
||||
push: mockRouterPush,
|
||||
replace: mockRouterReplace,
|
||||
}),
|
||||
usePathname: () => '/apps',
|
||||
useSearchParams: () => new URLSearchParams(),
|
||||
}))
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { act, fireEvent, screen } from '@testing-library/react'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features'
|
||||
import { renderWithNuqs } from '@/test/nuqs-testing'
|
||||
@ -13,9 +13,11 @@ const mockUseWorkflowOnlineUsers = vi.hoisted(() => vi.fn((_options: unknown) =>
|
||||
|
||||
const mockReplace = vi.fn()
|
||||
const mockRouter = { replace: mockReplace }
|
||||
let mockSearchParams = new URLSearchParams('')
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
useRouter: () => mockRouter,
|
||||
useSearchParams: () => new URLSearchParams(''),
|
||||
usePathname: () => '/apps',
|
||||
useSearchParams: () => mockSearchParams,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
@ -49,12 +51,10 @@ vi.mock('@/context/app-context', () => ({
|
||||
}))
|
||||
|
||||
const mockSetKeywords = vi.fn()
|
||||
const mockSetTagIDs = vi.fn()
|
||||
const mockSetIsCreatedByMe = vi.fn()
|
||||
const mockSetCategory = vi.fn()
|
||||
const mockQueryState = {
|
||||
category: 'all',
|
||||
tagIDs: [] as string[],
|
||||
keywords: '',
|
||||
isCreatedByMe: false,
|
||||
}
|
||||
@ -64,11 +64,20 @@ vi.mock('../hooks/use-apps-query-state', () => ({
|
||||
query: mockQueryState,
|
||||
setCategory: mockSetCategory,
|
||||
setKeywords: mockSetKeywords,
|
||||
setTagIDs: mockSetTagIDs,
|
||||
setIsCreatedByMe: mockSetIsCreatedByMe,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/features/tag-management/components/tag-filter', () => ({
|
||||
TagFilter: ({ value, onChange, onOpenTagManagement }: { value: string[], onChange: (value: string[]) => void, onOpenTagManagement: () => void }) => (
|
||||
<div>
|
||||
<button type="button" onClick={() => onChange(['tag-1'])}>common.tag.placeholder</button>
|
||||
<span data-testid="tag-filter-value">{value.join(',')}</span>
|
||||
<button type="button" onClick={onOpenTagManagement}>Manage tags</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
let mockOnDSLFileDropped: ((file: File) => void) | null = null
|
||||
let mockDragging = false
|
||||
vi.mock('../hooks/use-dsl-drag-drop', () => ({
|
||||
@ -230,6 +239,7 @@ beforeAll(() => {
|
||||
// Render helper wrapping with shared nuqs testing helper plus a seeded
|
||||
// systemFeatures cache so List can resolve its useSuspenseQuery.
|
||||
const renderList = (searchParams = '') => {
|
||||
mockSearchParams = new URLSearchParams(searchParams)
|
||||
const { wrapper: SystemFeaturesWrapper } = createSystemFeaturesWrapper({
|
||||
systemFeatures: { branding: { enabled: false } },
|
||||
})
|
||||
@ -253,7 +263,6 @@ describe('List', () => {
|
||||
mockServiceState.isLoading = false
|
||||
mockServiceState.isFetchingNextPage = false
|
||||
mockQueryState.category = 'all'
|
||||
mockQueryState.tagIDs = []
|
||||
mockQueryState.keywords = ''
|
||||
mockQueryState.isCreatedByMe = false
|
||||
mockUseWorkflowOnlineUsers.mockClear()
|
||||
@ -375,12 +384,12 @@ describe('List', () => {
|
||||
|
||||
describe('App List Query', () => {
|
||||
it('should build paged query input from active filters', () => {
|
||||
mockQueryState.tagIDs = ['tag-1']
|
||||
mockQueryState.keywords = 'sales'
|
||||
mockQueryState.isCreatedByMe = true
|
||||
mockQueryState.category = AppModeEnum.WORKFLOW
|
||||
|
||||
renderList()
|
||||
fireEvent.click(screen.getByText('common.tag.placeholder'))
|
||||
|
||||
const options = mockAppListInfiniteOptions.mock.calls.at(-1)?.[0] as AppListInfiniteOptions
|
||||
|
||||
@ -397,6 +406,17 @@ describe('List', () => {
|
||||
expect(options.getNextPageParam({ has_more: true, page: 2 })).toBe(3)
|
||||
expect(options.getNextPageParam({ has_more: false, page: 2 })).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should remove legacy tagIDs from URL while preserving other filters', async () => {
|
||||
renderList('?category=workflow&tagIDs=tag-1;tag-2&keywords=sales&isCreatedByMe=true')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockReplace).toHaveBeenCalledWith(
|
||||
'/apps?category=workflow&keywords=sales&isCreatedByMe=true',
|
||||
{ scroll: false },
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Tag Filter', () => {
|
||||
|
||||
@ -5,6 +5,7 @@ import { APP_LIST_SEARCH_DEBOUNCE_MS } from '../../constants'
|
||||
import { useAppsQueryState } from '../use-apps-query-state'
|
||||
|
||||
const renderWithAdapter = (searchParams = '') => {
|
||||
// eslint-disable-next-line react/use-state -- renderHook executes a custom hook, not React.useState
|
||||
return renderHookWithNuqs(() => useAppsQueryState(), { searchParams })
|
||||
}
|
||||
|
||||
@ -18,13 +19,11 @@ describe('useAppsQueryState', () => {
|
||||
|
||||
expect(result.current.query).toEqual({
|
||||
category: 'all',
|
||||
tagIDs: [],
|
||||
keywords: '',
|
||||
isCreatedByMe: false,
|
||||
})
|
||||
expect(typeof result.current.setCategory).toBe('function')
|
||||
expect(typeof result.current.setKeywords).toBe('function')
|
||||
expect(typeof result.current.setTagIDs).toBe('function')
|
||||
expect(typeof result.current.setIsCreatedByMe).toBe('function')
|
||||
})
|
||||
|
||||
@ -35,7 +34,6 @@ describe('useAppsQueryState', () => {
|
||||
|
||||
expect(result.current.query).toEqual({
|
||||
category: AppModeEnum.WORKFLOW,
|
||||
tagIDs: ['tag1', 'tag2'],
|
||||
keywords: 'search term',
|
||||
isCreatedByMe: true,
|
||||
})
|
||||
@ -117,33 +115,6 @@ describe('useAppsQueryState', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should update tag filter URL state', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter()
|
||||
|
||||
act(() => {
|
||||
result.current.setTagIDs(['tag1', 'tag2'])
|
||||
})
|
||||
|
||||
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
|
||||
const update = onUrlUpdate.mock.calls.at(-1)![0]
|
||||
expect(result.current.query.tagIDs).toEqual(['tag1', 'tag2'])
|
||||
expect(update.searchParams.get('tagIDs')).toBe('tag1;tag2')
|
||||
expect(update.options.history).toBe('push')
|
||||
})
|
||||
|
||||
it('should remove tagIDs from URL when empty', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter('?tagIDs=tag1;tag2')
|
||||
|
||||
act(() => {
|
||||
result.current.setTagIDs([])
|
||||
})
|
||||
|
||||
await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
|
||||
const update = onUrlUpdate.mock.calls.at(-1)![0]
|
||||
expect(result.current.query.tagIDs).toEqual([])
|
||||
expect(update.searchParams.has('tagIDs')).toBe(false)
|
||||
})
|
||||
|
||||
it('should update created-by-me URL state', async () => {
|
||||
const { result, onUrlUpdate } = renderWithAdapter()
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { debounce, parseAsArrayOf, parseAsBoolean, parseAsString, parseAsStringLiteral, useQueryStates } from 'nuqs'
|
||||
import { debounce, parseAsBoolean, parseAsString, parseAsStringLiteral, useQueryStates } from 'nuqs'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { AppModes } from '@/types/app'
|
||||
import { APP_LIST_SEARCH_DEBOUNCE_MS } from '../constants'
|
||||
@ -16,9 +16,6 @@ const appListQueryParsers = {
|
||||
category: parseAsStringLiteral(APP_LIST_CATEGORY_VALUES)
|
||||
.withDefault('all')
|
||||
.withOptions({ history: 'push' }),
|
||||
tagIDs: parseAsArrayOf(parseAsString, ';')
|
||||
.withDefault([])
|
||||
.withOptions({ history: 'push' }),
|
||||
keywords: parseAsString.withDefault('').withOptions({
|
||||
limitUrlUpdates: debounce(APP_LIST_SEARCH_DEBOUNCE_MS),
|
||||
}),
|
||||
@ -38,10 +35,6 @@ export function useAppsQueryState() {
|
||||
setQuery({ keywords })
|
||||
}, [setQuery])
|
||||
|
||||
const setTagIDs = useCallback((tagIDs: string[]) => {
|
||||
setQuery({ tagIDs })
|
||||
}, [setQuery])
|
||||
|
||||
const setIsCreatedByMe = useCallback((isCreatedByMe: boolean) => {
|
||||
setQuery({ isCreatedByMe })
|
||||
}, [setQuery])
|
||||
@ -50,7 +43,6 @@ export function useAppsQueryState() {
|
||||
query,
|
||||
setCategory,
|
||||
setKeywords,
|
||||
setTagIDs,
|
||||
setIsCreatedByMe,
|
||||
}), [query, setCategory, setKeywords, setTagIDs, setIsCreatedByMe])
|
||||
}), [query, setCategory, setKeywords, setIsCreatedByMe])
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import { useAppContext } from '@/context/app-context'
|
||||
import { TagFilter } from '@/features/tag-management/components/tag-filter'
|
||||
import { CheckModal } from '@/hooks/use-pay'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import { usePathname, useRouter, useSearchParams } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
@ -44,15 +45,18 @@ const List: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace } = useAppContext()
|
||||
const searchParams = useSearchParams()
|
||||
const pathname = usePathname()
|
||||
const { replace } = useRouter()
|
||||
|
||||
// eslint-disable-next-line react/use-state -- custom URL query hook, not React.useState
|
||||
const {
|
||||
query: { category, tagIDs, keywords, isCreatedByMe },
|
||||
query: { category, keywords, isCreatedByMe },
|
||||
setCategory,
|
||||
setKeywords,
|
||||
setTagIDs,
|
||||
setIsCreatedByMe,
|
||||
} = useAppsQueryState()
|
||||
const [tagIDs, setTagIDs] = useState<string[]>([])
|
||||
const debouncedKeywords = useDebounce(keywords, { wait: APP_LIST_SEARCH_DEBOUNCE_MS })
|
||||
const newAppCardRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
@ -71,6 +75,16 @@ const List: FC<Props> = ({
|
||||
enabled: isCurrentWorkspaceEditor,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchParams.has('tagIDs'))
|
||||
return
|
||||
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
params.delete('tagIDs')
|
||||
const query = params.toString()
|
||||
replace(query ? `${pathname}?${query}` : pathname, { scroll: false })
|
||||
}, [pathname, replace, searchParams])
|
||||
|
||||
const appListQuery = useMemo<AppListQuery>(() => ({
|
||||
page: 1,
|
||||
limit: 30,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user