mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 18:58:35 +08:00
refactor(web): remove remaining app context consumers (#38565)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
1340bef096
commit
d67123e5fd
@ -2023,11 +2023,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/base/permission-selector/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/permission-selector/member-item.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -3230,14 +3225,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/develop/secret-key/secret-key-modal.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/explore/banner/__tests__/indicator-button.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
@ -4134,11 +4121,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/rag-pipeline-main.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/hooks/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 9
|
||||
@ -4427,11 +4409,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow-app/hooks/use-workflow-init.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow-app/hooks/use-workflow-refresh-draft.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -4457,11 +4434,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow-app/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow-app/store/workflow/workflow-slice.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -6853,11 +6825,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/features/tag-management/components/tag-management-modal.tsx": {
|
||||
"jsx-a11y/no-autofocus": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/hooks/use-async-window-open.spec.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
|
||||
@ -10,7 +10,6 @@ import type { App } from '@/models/explore'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { createTestQueryClient, renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features'
|
||||
import AppList from '@/app/components/explore/app-list'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { fetchAppDetail, fetchAppList, fetchBanners } from '@/service/explore'
|
||||
import { useMembers } from '@/service/use-common'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
@ -124,6 +123,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
useSelector: <T,>(selector: (state: MockAppContext) => T): T => selector(mockUseAppContext()),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockUseAppContext())
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useMembers: vi.fn(),
|
||||
}))
|
||||
@ -198,15 +209,15 @@ const createApp = (overrides: Partial<App> = {}): App => ({
|
||||
})
|
||||
|
||||
const mockMemberRole = (hasEditPermission: boolean) => {
|
||||
;(useAppContext as Mock).mockReturnValue({
|
||||
mockUseAppContext.mockReturnValue({
|
||||
userProfile: { id: 'user-1' },
|
||||
workspacePermissionKeys: hasEditPermission ? ['app.create_and_management'] : [],
|
||||
})
|
||||
;(useMembers as Mock).mockReturnValue({
|
||||
vi.mocked(useMembers).mockReturnValue({
|
||||
data: {
|
||||
accounts: [{ id: 'user-1', role: hasEditPermission ? 'admin' : 'normal' }],
|
||||
},
|
||||
})
|
||||
} as unknown as ReturnType<typeof useMembers>)
|
||||
}
|
||||
|
||||
const localeInput = { query: { language: 'en' } }
|
||||
|
||||
@ -4,7 +4,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { renderWithSystemFeatures as render } from '@/__tests__/utils/mock-system-features'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/app/components/apps/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { createApp } from '@/service/apps'
|
||||
@ -21,6 +20,7 @@ const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: { id: 'user-1' },
|
||||
workspacePermissionKeys: ['app.create_and_management'] as string[],
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('ahooks', () => ({
|
||||
useDebounceFn: <T extends (...args: unknown[]) => unknown>(fn: T) => {
|
||||
@ -75,7 +75,7 @@ vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
@ -100,7 +100,6 @@ const mockCreateApp = vi.mocked(createApp)
|
||||
const mockTrackCreateApp = vi.mocked(trackCreateApp)
|
||||
const mockGetRedirection = vi.mocked(getRedirection)
|
||||
const mockUseProviderContext = vi.mocked(useProviderContext)
|
||||
const mockUseAppContext = vi.mocked(useAppContext)
|
||||
const { mockToastSuccess, mockToastError } = toastMocks
|
||||
|
||||
const defaultPlanUsage = {
|
||||
@ -148,7 +147,7 @@ describe('CreateAppModal', () => {
|
||||
mockUseAppContext.mockReturnValue({
|
||||
userProfile: { id: 'user-1' },
|
||||
workspacePermissionKeys: ['app.create_and_management'],
|
||||
} as unknown as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
mockAppContextState.userProfile = { id: 'user-1' }
|
||||
mockAppContextState.workspacePermissionKeys = ['app.create_and_management']
|
||||
mockSetItem.mockClear()
|
||||
|
||||
@ -1,30 +1,29 @@
|
||||
import type { AppContextValue } from '@/context/app-context'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { useSelector } from '@/context/app-context'
|
||||
import Tips from '../tips'
|
||||
|
||||
// Mock AppContext's useSelector to control user profile data
|
||||
vi.mock('@/context/app-context', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/context/app-context')>()
|
||||
return {
|
||||
...actual,
|
||||
useSelector: vi.fn(),
|
||||
}
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
email: 'test@example.com',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
describe('Tips', () => {
|
||||
const mockEmail = 'test@example.com'
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(useSelector).mockImplementation((selector: (value: AppContextValue) => unknown) => {
|
||||
return selector({
|
||||
userProfile: {
|
||||
email: mockEmail,
|
||||
},
|
||||
} as AppContextValue)
|
||||
})
|
||||
mockAppContextState.userProfile.email = 'test@example.com'
|
||||
})
|
||||
|
||||
it('should render email tip in normal mode', () => {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { useSelector as useAppSelector } from '@/context/app-context'
|
||||
import { userProfileEmailAtom } from '@/context/app-context-state'
|
||||
|
||||
type TipsProps = {
|
||||
showEmailTip: boolean
|
||||
@ -15,7 +16,7 @@ const Tips = ({
|
||||
showDebugModeTip,
|
||||
}: TipsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const email = useAppSelector(s => s.userProfile.email)
|
||||
const email = useAtomValue(userProfileEmailAtom)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -8,11 +8,13 @@ import {
|
||||
} from '@langgenius/dify-ui/popover'
|
||||
import { RiArrowDownSLine, RiGroup2Line, RiLock2Line } from '@remixicon/react'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
// eslint-disable-next-line no-restricted-imports -- This legacy selector still relies on showLeftIcon/showClearIcon props from the old input.
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { PermissionLevel } from '@/models/permission'
|
||||
import MemberItem from './member-item'
|
||||
import Item from './permission-item'
|
||||
@ -45,7 +47,7 @@ const PermissionSelector = ({
|
||||
hidePartialMembers = false,
|
||||
}: PermissionSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const userProfile = useAppContextWithSelector(state => state.userProfile)
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const [keywords, setKeywords] = useState('')
|
||||
@ -72,7 +74,7 @@ const PermissionSelector = ({
|
||||
}, [userProfile, value, memberList])
|
||||
|
||||
const showMe = useMemo(() => {
|
||||
return userProfile.name.includes(searchKeywords) || userProfile.email.includes(searchKeywords)
|
||||
return (userProfile.name ?? '').includes(searchKeywords) || (userProfile.email ?? '').includes(searchKeywords)
|
||||
}, [searchKeywords, userProfile])
|
||||
|
||||
const filteredMemberList = useMemo(() => {
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import type { AvatarSize } from '@langgenius/dify-ui/avatar'
|
||||
import type { FC } from 'react'
|
||||
import { AvatarFallback, AvatarImage, AvatarRoot } from '@langgenius/dify-ui/avatar'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo } from 'react'
|
||||
import { getUserColor } from '@/app/components/workflow/collaboration/utils/user-color'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileIdAtom } from '@/context/app-context-state'
|
||||
|
||||
type User = {
|
||||
id: string
|
||||
@ -37,7 +38,7 @@ export const UserAvatarList: FC<UserAvatarListProps> = memo(({
|
||||
className = '',
|
||||
showCount = true,
|
||||
}) => {
|
||||
const { userProfile } = useAppContext()
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
if (!users.length)
|
||||
return null
|
||||
|
||||
@ -46,8 +47,6 @@ export const UserAvatarList: FC<UserAvatarListProps> = memo(({
|
||||
const visibleUsers = users.slice(0, actualMaxVisible)
|
||||
const remainingCount = users.length - actualMaxVisible
|
||||
|
||||
const currentUserId = userProfile?.id
|
||||
|
||||
return (
|
||||
<div className={`flex items-center -space-x-1 ${className}`}>
|
||||
{visibleUsers.map((user, index) => {
|
||||
|
||||
@ -7,11 +7,11 @@ import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/comm
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { mailToSupport } from '@/app/components/header/utils/util'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { baseProviderContextValue, useProviderContext } from '@/context/provider-context'
|
||||
import AppsFull from '../index'
|
||||
|
||||
let mockAppContextState: AppContextValue
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
@ -22,7 +22,7 @@ vi.mock('@/config', async (importOriginal) => {
|
||||
})
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
@ -136,7 +136,7 @@ describe('AppsFull', () => {
|
||||
vi.clearAllMocks()
|
||||
;(useProviderContext as Mock).mockReturnValue(buildProviderContext())
|
||||
mockAppContextState = buildAppContext()
|
||||
;(useAppContext as Mock).mockReturnValue(mockAppContextState)
|
||||
mockUseAppContext.mockReturnValue(mockAppContextState)
|
||||
;(mailToSupport as Mock).mockReturnValue('mailto:support@example.com')
|
||||
})
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ import type { Mock } from 'vitest'
|
||||
import { toast, ToastHost } from '@langgenius/dify-ui/toast'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||
import { fetchSubscriptionUrls } from '@/service/billing'
|
||||
@ -13,9 +12,10 @@ import { PlanRange } from '../../../plan-switcher/plan-range-switcher'
|
||||
import CloudPlanItem from '../index'
|
||||
|
||||
let mockAppCtx: Record<string, unknown> = {}
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
@ -56,7 +56,6 @@ vi.mock('../../../assets', () => ({
|
||||
Team: () => <div>Team Icon</div>,
|
||||
}))
|
||||
|
||||
const mockUseAppContext = useAppContext as Mock
|
||||
const mockUseProviderContext = useProviderContext as Mock
|
||||
const mockUseAsyncWindowOpen = useAsyncWindowOpen as Mock
|
||||
const mockBillingInvoices = consoleClient.billing.invoices.get as Mock
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import { toast, ToastHost } from '@langgenius/dify-ui/toast'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { contactSalesUrl, getStartedWithCommunityUrl, getWithPremiumUrl } from '../../../../config'
|
||||
import { SelfHostedPlan } from '../../../../type'
|
||||
import SelfHostedPlanItem from '../index'
|
||||
|
||||
let mockAppCtx: Record<string, unknown> = {}
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('../list', () => ({
|
||||
default: ({ plan }: { plan: string }) => (
|
||||
@ -19,7 +18,7 @@ vi.mock('../list', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
@ -40,8 +39,6 @@ vi.mock('../../../assets', () => ({
|
||||
EnterpriseNoise: () => <div>EnterpriseNoise</div>,
|
||||
}))
|
||||
|
||||
const mockUseAppContext = useAppContext as Mock
|
||||
|
||||
let assignedHref = ''
|
||||
const originalLocation = window.location
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ import { Plan } from '@/app/components/billing/type'
|
||||
import {
|
||||
initialLangGeniusVersionInfo,
|
||||
initialWorkspaceInfo,
|
||||
useAppContext,
|
||||
userProfilePlaceholder,
|
||||
} from '@/context/app-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
@ -46,6 +45,7 @@ const { mockToast } = vi.hoisted(() => {
|
||||
})
|
||||
return { mockToast }
|
||||
})
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
@ -57,7 +57,7 @@ vi.mock('@/context/app-context', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/context/app-context')>()
|
||||
return {
|
||||
...actual,
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}
|
||||
})
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
@ -66,7 +66,6 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
|
||||
const mockUseProviderContext = vi.mocked(useProviderContext)
|
||||
const mockUseModalContext = vi.mocked(useModalContext)
|
||||
const mockUseAppContext = vi.mocked(useAppContext)
|
||||
|
||||
const createProviderContext = ({
|
||||
enableBilling = false,
|
||||
|
||||
@ -11,7 +11,6 @@ import { Plan } from '@/app/components/billing/type'
|
||||
import {
|
||||
initialLangGeniusVersionInfo,
|
||||
initialWorkspaceInfo,
|
||||
useAppContext,
|
||||
userProfilePlaceholder,
|
||||
} from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
@ -43,6 +42,10 @@ const { mockNotify, mockToast } = vi.hoisted(() => {
|
||||
})
|
||||
return { mockNotify, mockToast }
|
||||
})
|
||||
const appContextStateRef = vi.hoisted(() => ({
|
||||
value: undefined as AppContextValue | undefined,
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: mockToast,
|
||||
@ -54,9 +57,22 @@ vi.mock('@/context/app-context', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/context/app-context')>()
|
||||
return {
|
||||
...actual,
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}
|
||||
})
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
...appContextStateRef.value,
|
||||
refreshCurrentWorkspace: appContextStateRef.value?.mutateCurrentWorkspace,
|
||||
}))
|
||||
})
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
@ -66,7 +82,6 @@ vi.mock('@/app/components/base/image-uploader/utils', () => ({
|
||||
}))
|
||||
|
||||
const mockUpdateCurrentWorkspace = vi.mocked(updateCurrentWorkspace)
|
||||
const mockUseAppContext = vi.mocked(useAppContext)
|
||||
const mockUseProviderContext = vi.mocked(useProviderContext)
|
||||
const mockImageUpload = vi.mocked(imageUpload)
|
||||
const mockGetImageUploadErrorMessage = vi.mocked(getImageUploadErrorMessage)
|
||||
@ -120,11 +135,15 @@ const createAppContextValue = (overrides: Partial<AppContextValue> = {}): AppCon
|
||||
|
||||
describe('useWebAppBrand', () => {
|
||||
let appContextValue: AppContextValue
|
||||
const setAppContextValue = (nextValue: AppContextValue) => {
|
||||
appContextValue = nextValue
|
||||
appContextStateRef.value = nextValue
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
appContextValue = createAppContextValue()
|
||||
setAppContextValue(createAppContextValue())
|
||||
currentBrandingOverrides = {}
|
||||
|
||||
mockUpdateCurrentWorkspace.mockResolvedValue(appContextValue.currentWorkspace)
|
||||
@ -146,10 +165,10 @@ describe('useWebAppBrand', () => {
|
||||
})
|
||||
|
||||
it('should disable uploads when customization management permission is missing', () => {
|
||||
appContextValue = createAppContextValue({
|
||||
setAppContextValue(createAppContextValue({
|
||||
workspacePermissionKeys: [],
|
||||
isCurrentWorkspaceManager: true,
|
||||
})
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useWebAppBrand())
|
||||
|
||||
@ -158,10 +177,10 @@ describe('useWebAppBrand', () => {
|
||||
})
|
||||
|
||||
it('should allow uploads for non-manager users with customization management permission', () => {
|
||||
appContextValue = createAppContextValue({
|
||||
setAppContextValue(createAppContextValue({
|
||||
workspacePermissionKeys: ['customization.manage'],
|
||||
isCurrentWorkspaceManager: false,
|
||||
})
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useWebAppBrand())
|
||||
|
||||
@ -174,7 +193,7 @@ describe('useWebAppBrand', () => {
|
||||
enableBilling: true,
|
||||
planType: Plan.sandbox,
|
||||
}))
|
||||
appContextValue = createAppContextValue({
|
||||
setAppContextValue(createAppContextValue({
|
||||
currentWorkspace: {
|
||||
...initialWorkspaceInfo,
|
||||
custom_config: {
|
||||
@ -182,7 +201,7 @@ describe('useWebAppBrand', () => {
|
||||
remove_webapp_brand: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useWebAppBrand())
|
||||
|
||||
@ -200,12 +219,12 @@ describe('useWebAppBrand', () => {
|
||||
})
|
||||
|
||||
it('should fall back to an empty custom logo when custom config is missing', () => {
|
||||
appContextValue = {
|
||||
setAppContextValue({
|
||||
...createAppContextValue(),
|
||||
currentWorkspace: {
|
||||
...initialWorkspaceInfo,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const { result } = renderHook(() => useWebAppBrand())
|
||||
|
||||
@ -308,9 +327,9 @@ describe('useWebAppBrand', () => {
|
||||
|
||||
it('should persist the selected logo and reset transient state on apply', async () => {
|
||||
const mutateCurrentWorkspace = vi.fn()
|
||||
appContextValue = createAppContextValue({
|
||||
setAppContextValue(createAppContextValue({
|
||||
mutateCurrentWorkspace,
|
||||
})
|
||||
}))
|
||||
mockImageUpload.mockImplementation(({ onSuccessCallback }) => {
|
||||
onSuccessCallback({ id: 'new-logo' })
|
||||
})
|
||||
@ -345,9 +364,9 @@ describe('useWebAppBrand', () => {
|
||||
|
||||
it('should restore the default branding configuration', async () => {
|
||||
const mutateCurrentWorkspace = vi.fn()
|
||||
appContextValue = createAppContextValue({
|
||||
setAppContextValue(createAppContextValue({
|
||||
mutateCurrentWorkspace,
|
||||
})
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useWebAppBrand())
|
||||
|
||||
@ -367,9 +386,9 @@ describe('useWebAppBrand', () => {
|
||||
|
||||
it('should persist brand removal changes', async () => {
|
||||
const mutateCurrentWorkspace = vi.fn()
|
||||
appContextValue = createAppContextValue({
|
||||
setAppContextValue(createAppContextValue({
|
||||
mutateCurrentWorkspace,
|
||||
})
|
||||
}))
|
||||
|
||||
const { result } = renderHook(() => useWebAppBrand())
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/image-uploader/utils'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceAtom, refreshCurrentWorkspaceAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { updateCurrentWorkspace } from '@/service/common'
|
||||
@ -17,7 +18,9 @@ const WEB_APP_LOGO_UPLOAD_URL = '/workspaces/custom-config/webapp-logo/upload'
|
||||
const useWebAppBrand = () => {
|
||||
const { t } = useTranslation()
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const { currentWorkspace, mutateCurrentWorkspace, workspacePermissionKeys } = useAppContext()
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const mutateCurrentWorkspace = useSetAtom(refreshCurrentWorkspaceAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const [fileId, setFileId] = useState('')
|
||||
const [imgKey, setImgKey] = useState(() => Date.now())
|
||||
const [uploadProgress, setUploadProgress] = useState(0)
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import ApiServer from '@/app/components/develop/ApiServer'
|
||||
import Doc from '@/app/components/develop/doc'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { getAppACLCapabilities } from '@/utils/permission'
|
||||
|
||||
type IDevelopMainProps = {
|
||||
@ -12,8 +13,8 @@ type IDevelopMainProps = {
|
||||
|
||||
const DevelopMain = ({ appId }: IDevelopMainProps) => {
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const appACLCapabilities = getAppACLCapabilities(appDetail?.permission_keys, {
|
||||
currentUserId,
|
||||
resourceMaintainer: appDetail?.maintainer,
|
||||
|
||||
@ -35,6 +35,22 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
currentWorkspace: mockCurrentWorkspace(),
|
||||
isCurrentWorkspaceManager: mockIsCurrentWorkspaceManager(),
|
||||
isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(),
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/hooks/use-timestamp', () => ({
|
||||
default: () => ({
|
||||
formatTime: vi.fn((value: number, _format: string) => `Formatted: ${value}`),
|
||||
@ -357,7 +373,7 @@ describe('SecretKeyModal', () => {
|
||||
})
|
||||
|
||||
it('should disable create button when no workspace', async () => {
|
||||
mockCurrentWorkspace.mockReturnValue(null)
|
||||
mockCurrentWorkspace.mockReturnValue({ id: '', name: '' })
|
||||
await renderModal(<SecretKeyModal {...defaultProps} />)
|
||||
|
||||
const createButton = screen.getByText('appApi.apiKeyModal.createNewSecretKey').closest('button')
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useState,
|
||||
} from 'react'
|
||||
@ -19,7 +20,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceAtom } from '@/context/app-context-state'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import {
|
||||
createApikey as createAppApikey,
|
||||
@ -49,7 +50,7 @@ const SecretKeyModal = ({
|
||||
}: ISecretKeyModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { formatTime } = useTimestamp()
|
||||
const { currentWorkspace } = useAppContext()
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [newKey, setNewKey] = useState<CreateApiKeyResponse | undefined>(undefined)
|
||||
@ -81,7 +82,7 @@ const SecretKeyModal = ({
|
||||
}
|
||||
|
||||
const onCreate = async () => {
|
||||
if (!currentWorkspace || !canManage)
|
||||
if (!currentWorkspace.id || !canManage)
|
||||
return
|
||||
|
||||
const params = appId
|
||||
@ -129,7 +130,14 @@ const SecretKeyModal = ({
|
||||
</DialogTitle>
|
||||
|
||||
<div className="-mt-6 -mr-2 mb-4 flex justify-end">
|
||||
<span className="i-heroicons-x-mark-20-solid size-6 cursor-pointer text-text-tertiary" onClick={handleClose} />
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('operation.close', { ns: 'common' })}
|
||||
className="flex size-6 cursor-pointer items-center justify-center text-text-tertiary"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<span className="i-heroicons-x-mark-20-solid size-6 cursor-pointer" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-1 shrink-0 text-[13px] leading-5 font-normal text-text-tertiary">{t('apiKeyModal.apiSecretKeyTips', { ns: 'appApi' })}</p>
|
||||
{isApiKeysLoading && <div className="mt-4"><Loading /></div>}
|
||||
@ -168,7 +176,7 @@ const SecretKeyModal = ({
|
||||
)
|
||||
}
|
||||
<div className="flex">
|
||||
<Button className={`mt-4 flex shrink-0 ${s.autoWidth}`} onClick={onCreate} disabled={!currentWorkspace || !canManage}>
|
||||
<Button className={`mt-4 flex shrink-0 ${s.autoWidth}`} onClick={onCreate} disabled={!currentWorkspace.id || !canManage}>
|
||||
<span className="mr-1 i-heroicons-plus-20-solid flex size-4 shrink-0" />
|
||||
<div className="text-xs font-medium text-text-secondary">{t('apiKeyModal.createNewSecretKey', { ns: 'appApi' })}</div>
|
||||
</Button>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { MediaType } from '@/hooks/use-breakpoints'
|
||||
import Explore from '../index'
|
||||
|
||||
@ -42,17 +40,10 @@ vi.mock('@/service/use-explore', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
}))
|
||||
|
||||
describe('Explore', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockMediaType = MediaType.pc
|
||||
;(useAppContext as Mock).mockReturnValue({
|
||||
isCurrentWorkspaceDatasetOperator: false,
|
||||
})
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@ -90,23 +81,19 @@ describe('Explore', () => {
|
||||
})
|
||||
|
||||
describe('Effects', () => {
|
||||
it('should not redirect dataset operators at component level', async () => {
|
||||
;(useAppContext as Mock).mockReturnValue({
|
||||
isCurrentWorkspaceDatasetOperator: true,
|
||||
})
|
||||
|
||||
it('should not redirect at component level', () => {
|
||||
render((
|
||||
<Explore>
|
||||
<div>child</div>
|
||||
</Explore>
|
||||
))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
expect(mockReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not redirect non dataset operators', () => {
|
||||
it('should not redirect on mobile', () => {
|
||||
mockMediaType = MediaType.mobile
|
||||
|
||||
render((
|
||||
<Explore>
|
||||
<div>child</div>
|
||||
|
||||
@ -7,7 +7,6 @@ import type { App as WorkspaceApp } from '@/types/app'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { createStore, Provider as JotaiProvider } from 'jotai'
|
||||
import { createSystemFeaturesWrapper } from '@/__tests__/utils/mock-system-features'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { fetchAppDetail, fetchAppList, fetchBanners } from '@/service/explore'
|
||||
import { renderWithNuqs } from '@/test/nuqs-testing'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
@ -15,12 +14,10 @@ import { AppACLPermission } from '@/utils/permission'
|
||||
import { LEARN_DIFY_HIDDEN_STORAGE_KEY } from '../../learn-dify/storage'
|
||||
import AppList from '../index'
|
||||
|
||||
type MockAppContext = {
|
||||
userProfile: { id: string }
|
||||
workspacePermissionKeys: string[]
|
||||
}
|
||||
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn<() => MockAppContext>())
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: { id: 'user-1' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
}))
|
||||
|
||||
let mockExploreData: { categories: string[], allList: App[] } | undefined = { categories: [], allList: [] }
|
||||
let mockLearnDifyApps: App[] = []
|
||||
@ -127,10 +124,17 @@ vi.mock('@/service/client', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: mockUseAppContext,
|
||||
useSelector: <T,>(selector: (state: MockAppContext) => T): T => selector(mockUseAppContext()),
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/hooks/use-import-dsl', () => ({
|
||||
useImportDSL: () => ({
|
||||
@ -282,10 +286,7 @@ const createBanner = (overrides: Partial<BannerType> = {}): BannerType => ({
|
||||
})
|
||||
|
||||
const mockAppCreatePermission = (hasEditPermission: boolean) => {
|
||||
;(useAppContext as Mock).mockReturnValue({
|
||||
userProfile: { id: 'user-1' },
|
||||
workspacePermissionKeys: hasEditPermission ? ['app.create_and_management'] : [],
|
||||
})
|
||||
mockAppContextState.workspacePermissionKeys = hasEditPermission ? ['app.create_and_management'] : []
|
||||
}
|
||||
|
||||
type RenderOptions = {
|
||||
|
||||
@ -9,6 +9,7 @@ import type { TrackCreateAppParams } from '@/utils/create-app-tracking'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { queryOptions, useQueries, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useQueryState } from 'nuqs'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
@ -17,7 +18,7 @@ import DSLConfirmModal from '@/app/components/app/create-from-dsl-modal/dsl-conf
|
||||
import AppCard from '@/app/components/explore/app-card'
|
||||
import Banner from '@/app/components/explore/banner/banner'
|
||||
import CreateAppModal from '@/app/components/explore/create-app-modal'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useImportDSL } from '@/hooks/use-import-dsl'
|
||||
@ -101,7 +102,7 @@ function getDisabledBannersQueryOptions() {
|
||||
const Apps = ({ onSuccess }: { onSuccess?: () => void }) => {
|
||||
const { t } = useTranslation()
|
||||
const locale = useLocale()
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { data: systemFeatures } = useSuspenseQuery(
|
||||
systemFeaturesQueryOptions(),
|
||||
)
|
||||
|
||||
@ -6,10 +6,15 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Banner from '../banner'
|
||||
|
||||
const mockUseGetBanners = vi.fn()
|
||||
const mockUseSelector = vi.fn()
|
||||
const mockTrackEvent = vi.fn()
|
||||
let mockSelectedIndex = 0
|
||||
const mockCarouselListeners = new Set<() => void>()
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'account-123',
|
||||
name: 'Evan',
|
||||
},
|
||||
}))
|
||||
|
||||
const setMockSelectedIndex = (index: number) => {
|
||||
mockSelectedIndex = index
|
||||
@ -20,9 +25,17 @@ vi.mock('@/context/i18n', () => ({
|
||||
useLocale: () => 'en-US',
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (...args: unknown[]) => mockUseSelector(...args),
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/base/amplitude', () => ({
|
||||
trackEvent: (...args: unknown[]) => mockTrackEvent(...args),
|
||||
@ -133,12 +146,10 @@ describe('Banner', () => {
|
||||
vi.useFakeTimers()
|
||||
mockSelectedIndex = 0
|
||||
mockCarouselListeners.clear()
|
||||
mockUseSelector.mockImplementation(selector => selector({
|
||||
userProfile: {
|
||||
id: 'account-123',
|
||||
name: 'Evan',
|
||||
},
|
||||
}))
|
||||
mockAppContextState.userProfile = {
|
||||
id: 'account-123',
|
||||
name: 'Evan',
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@ -357,12 +368,10 @@ describe('Banner', () => {
|
||||
})
|
||||
|
||||
it('does not track impressions when account id is unavailable', () => {
|
||||
mockUseSelector.mockImplementation(selector => selector({
|
||||
userProfile: {
|
||||
id: '',
|
||||
name: '',
|
||||
},
|
||||
}))
|
||||
mockAppContextState.userProfile = {
|
||||
id: '',
|
||||
name: '',
|
||||
}
|
||||
mockUseGetBanners.mockReturnValue({
|
||||
data: [createMockBanner('1', 'enabled', 'Enabled Banner 1')],
|
||||
isLoading: false,
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import type { Banner as BannerType } from '@/models/app'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import { Carousel, useCarousel } from '@/app/components/base/carousel'
|
||||
import { useSelector } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { BannerItem } from './banner-item'
|
||||
|
||||
@ -59,8 +60,9 @@ function Banner({
|
||||
}: BannerProps) {
|
||||
const { t } = useTranslation()
|
||||
const locale = useLocale()
|
||||
const accountId = useSelector(s => s.userProfile.id)
|
||||
const userName = useSelector(s => s.userProfile.name)
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const accountId = userProfile.id
|
||||
const userName = userProfile.name
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
const resizeTimerRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
@ -7,7 +7,7 @@ import { AppModeEnum } from '@/types/app'
|
||||
import { AppACLPermission } from '@/utils/permission'
|
||||
import ContinueWorkItem from '../item'
|
||||
|
||||
const mockAppContext = vi.hoisted(() => ({
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: { id: 'user-1' },
|
||||
workspacePermissionKeys: ['app.create_and_management'],
|
||||
}))
|
||||
@ -18,9 +18,17 @@ const toastMocks = vi.hoisted(() => ({
|
||||
warning: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: typeof mockAppContext) => unknown) => selector(mockAppContext),
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/hooks/use-format-time-from-now', () => ({
|
||||
useFormatTimeFromNow: () => ({
|
||||
@ -82,8 +90,8 @@ const renderItem = (
|
||||
describe('ContinueWorkItem', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockAppContext.userProfile = { id: 'user-1' }
|
||||
mockAppContext.workspacePermissionKeys = ['app.create_and_management']
|
||||
mockAppContextState.userProfile = { id: 'user-1' }
|
||||
mockAppContextState.workspacePermissionKeys = ['app.create_and_management']
|
||||
mockFormatTimeFromNow.mockReturnValue('5 minutes ago')
|
||||
})
|
||||
|
||||
|
||||
@ -4,11 +4,12 @@ import type { App } from '@/types/app'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AppTypeIcon } from '@/app/components/app/type-selector'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import Link from '@/next/link'
|
||||
@ -24,8 +25,8 @@ const ContinueWorkItem = ({
|
||||
}: ContinueWorkItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const currentUserId = useAppContextSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||
const updatedAt = (app.updated_at || app.created_at) * 1000
|
||||
|
||||
@ -6,7 +6,6 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
@ -46,6 +45,7 @@ const { mockSetTheme } = vi.hoisted(() => ({
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: undefined as AppContextValue | undefined,
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('next-themes', () => ({
|
||||
useTheme: () => ({
|
||||
@ -55,7 +55,7 @@ vi.mock('next-themes', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
@ -166,7 +166,7 @@ const baseAppContextValue: AppContextValue = {
|
||||
|
||||
const setAppContextValue = (value: AppContextValue) => {
|
||||
mockAppContextState.current = value
|
||||
vi.mocked(useAppContext).mockReturnValue(value)
|
||||
mockUseAppContext.mockReturnValue(value)
|
||||
}
|
||||
|
||||
describe('AccountDropdown', () => {
|
||||
|
||||
@ -3,7 +3,6 @@ import type { AppContextValue } from '@/context/app-context'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { useState } from 'react'
|
||||
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { baseProviderContextValue, useProviderContext } from '@/context/provider-context'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { ACCOUNT_SETTING_TAB } from '../constants'
|
||||
@ -13,6 +12,7 @@ const mockResetModelProviderListExpanded = vi.fn()
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: null as unknown,
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/context/provider-context', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/context/provider-context')>()
|
||||
@ -26,7 +26,7 @@ vi.mock('@/context/app-context', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/context/app-context')>()
|
||||
return {
|
||||
...actual,
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
useSelector: vi.fn((selector: (state: unknown) => unknown) => selector(mockAppContextState.current)),
|
||||
}
|
||||
})
|
||||
@ -239,7 +239,7 @@ describe('AccountSetting', () => {
|
||||
enableBilling: true,
|
||||
enableReplaceWebAppLogo: true,
|
||||
})
|
||||
vi.mocked(useAppContext).mockReturnValue(baseAppContextValue)
|
||||
mockUseAppContext.mockReturnValue(baseAppContextValue)
|
||||
mockAppContextState.current = baseAppContextValue
|
||||
vi.mocked(useBreakpoints).mockReturnValue(MediaType.pc)
|
||||
})
|
||||
@ -362,7 +362,7 @@ describe('AccountSetting', () => {
|
||||
...baseAppContextValue,
|
||||
isCurrentWorkspaceDatasetOperator: true,
|
||||
}
|
||||
vi.mocked(useAppContext).mockReturnValue(datasetOperatorContext)
|
||||
mockUseAppContext.mockReturnValue(datasetOperatorContext)
|
||||
mockAppContextState.current = datasetOperatorContext
|
||||
|
||||
// Act
|
||||
@ -383,7 +383,7 @@ describe('AccountSetting', () => {
|
||||
...baseAppContextValue,
|
||||
workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys.filter(key => key !== 'api_extension.manage'),
|
||||
}
|
||||
vi.mocked(useAppContext).mockReturnValue(contextWithoutApiExtensionPermission)
|
||||
mockUseAppContext.mockReturnValue(contextWithoutApiExtensionPermission)
|
||||
mockAppContextState.current = contextWithoutApiExtensionPermission
|
||||
|
||||
// Act
|
||||
@ -401,7 +401,7 @@ describe('AccountSetting', () => {
|
||||
...baseAppContextValue,
|
||||
workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys.filter(key => key !== 'customization.manage'),
|
||||
}
|
||||
vi.mocked(useAppContext).mockReturnValue(contextWithoutCustomizationPermission)
|
||||
mockUseAppContext.mockReturnValue(contextWithoutCustomizationPermission)
|
||||
mockAppContextState.current = contextWithoutCustomizationPermission
|
||||
|
||||
// Act
|
||||
@ -420,7 +420,7 @@ describe('AccountSetting', () => {
|
||||
...baseAppContextValue,
|
||||
workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys.filter(key => key !== 'workspace.role.manage'),
|
||||
}
|
||||
vi.mocked(useAppContext).mockReturnValue(contextWithoutRoleManagePermission)
|
||||
mockUseAppContext.mockReturnValue(contextWithoutRoleManagePermission)
|
||||
mockAppContextState.current = contextWithoutRoleManagePermission
|
||||
|
||||
// Act
|
||||
@ -505,7 +505,7 @@ describe('AccountSetting', () => {
|
||||
...baseAppContextValue,
|
||||
workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys.filter(key => key !== 'billing.view'),
|
||||
}
|
||||
vi.mocked(useAppContext).mockReturnValue(contextWithoutBillingViewPermission)
|
||||
mockUseAppContext.mockReturnValue(contextWithoutBillingViewPermission)
|
||||
mockAppContextState.current = contextWithoutBillingViewPermission
|
||||
|
||||
// Act
|
||||
@ -521,7 +521,7 @@ describe('AccountSetting', () => {
|
||||
...baseAppContextValue,
|
||||
workspacePermissionKeys: baseAppContextValue.workspacePermissionKeys.filter(key => key !== 'billing.view'),
|
||||
}
|
||||
vi.mocked(useAppContext).mockReturnValue(contextWithoutBillingViewPermission)
|
||||
mockUseAppContext.mockReturnValue(contextWithoutBillingViewPermission)
|
||||
mockAppContextState.current = contextWithoutBillingViewPermission
|
||||
|
||||
// Act
|
||||
|
||||
@ -7,7 +7,6 @@ import { vi } from 'vitest'
|
||||
import { createMockProviderContextValue } from '@/__mocks__/provider-context'
|
||||
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { useUpdateRolesOfMember } from '@/service/access-control/use-member-roles'
|
||||
@ -17,8 +16,11 @@ import MembersPage from '../index'
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/context/app-context')
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current)
|
||||
@ -55,7 +57,7 @@ const createRole = (overrides: Partial<Role>): Role => ({
|
||||
|
||||
const setAppContextValue = (value: AppContextValue) => {
|
||||
mockAppContextState.current = value
|
||||
vi.mocked(useAppContext).mockReturnValue(value)
|
||||
mockUseAppContext.mockReturnValue(value)
|
||||
}
|
||||
|
||||
vi.mock('../edit-workspace-modal', () => ({
|
||||
|
||||
@ -3,11 +3,14 @@ import type { ICurrentWorkspace } from '@/models/common'
|
||||
import { screen } from '@testing-library/react'
|
||||
import { vi } from 'vitest'
|
||||
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useWorkspacePermissions } from '@/service/use-workspace'
|
||||
import InviteButton from '../invite-button'
|
||||
|
||||
vi.mock('@/context/app-context')
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
@ -41,7 +44,7 @@ describe('InviteButton', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
mockUseAppContext.mockReturnValue({
|
||||
currentWorkspace: { id: 'workspace-id' } as ICurrentWorkspace,
|
||||
} as unknown as AppContextValue)
|
||||
})
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { AppContextValue } from '@/context/app-context'
|
||||
import { render } from '@testing-library/react'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import EditWorkspaceModal from '../index'
|
||||
|
||||
type DialogProps = {
|
||||
@ -14,6 +13,7 @@ let latestOnOpenChange: DialogProps['onOpenChange']
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, onOpenChange }: DialogProps) => {
|
||||
@ -30,7 +30,7 @@ vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
@ -52,7 +52,7 @@ describe('EditWorkspaceModal dialog lifecycle', () => {
|
||||
isCurrentWorkspaceOwner: true,
|
||||
} as never
|
||||
mockAppContextState.current = appContextValue
|
||||
vi.mocked(useAppContext).mockReturnValue(appContextValue)
|
||||
mockUseAppContext.mockReturnValue(appContextValue)
|
||||
})
|
||||
|
||||
it('should only call onCancel when the dialog requests closing', () => {
|
||||
|
||||
@ -3,7 +3,6 @@ import type { ICurrentWorkspace } from '@/models/common'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { vi } from 'vitest'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { updateWorkspaceInfo } from '@/service/common'
|
||||
import EditWorkspaceModal from '../index'
|
||||
|
||||
@ -13,10 +12,13 @@ const toastMocks = vi.hoisted(() => ({
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
const getSaveButton = () => screen.getByRole('button', { name: /operation\.(save|saving)/i })
|
||||
|
||||
vi.mock('@/context/app-context')
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current)
|
||||
@ -50,7 +52,7 @@ describe('EditWorkspaceModal', () => {
|
||||
isCurrentWorkspaceOwner: true,
|
||||
} as unknown as AppContextValue
|
||||
mockAppContextState.current = appContextValue
|
||||
vi.mocked(useAppContext).mockReturnValue(appContextValue)
|
||||
mockUseAppContext.mockReturnValue(appContextValue)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@ -165,7 +167,7 @@ describe('EditWorkspaceModal', () => {
|
||||
})
|
||||
|
||||
it('should disable confirm button for non-owners', async () => {
|
||||
vi.mocked(useAppContext).mockReturnValue({
|
||||
mockUseAppContext.mockReturnValue({
|
||||
currentWorkspace: { name: 'Test Workspace' } as ICurrentWorkspace,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
} as unknown as AppContextValue)
|
||||
|
||||
@ -3,7 +3,6 @@ import type { ICurrentWorkspace } from '@/models/common'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { vi } from 'vitest'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { ownershipTransfer, sendOwnerEmail, verifyOwnerEmail } from '@/service/common'
|
||||
import { useMembers } from '@/service/use-common'
|
||||
import TransferOwnershipModal from '../index'
|
||||
@ -14,8 +13,11 @@ const toastMocks = vi.hoisted(() => ({
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/context/app-context')
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState.current)
|
||||
@ -56,7 +58,7 @@ describe('TransferOwnershipModal', () => {
|
||||
userProfile: { email: 'owner@example.com', id: 'owner-id' },
|
||||
} as unknown as AppContextValue
|
||||
mockAppContextState.current = appContextValue
|
||||
vi.mocked(useAppContext).mockReturnValue(appContextValue)
|
||||
mockUseAppContext.mockReturnValue(appContextValue)
|
||||
|
||||
vi.mocked(useMembers).mockReturnValue({
|
||||
data: { accounts: [] },
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import type { AppContextValue } from '@/context/app-context'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { vi } from 'vitest'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import EnvNav from '../index'
|
||||
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
current: {} as Partial<AppContextValue>,
|
||||
}))
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
@ -23,8 +23,6 @@ vi.mock('jotai', async (importOriginal) => {
|
||||
})
|
||||
|
||||
describe('EnvNav', () => {
|
||||
const mockUseAppContext = vi.mocked(useAppContext)
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
@ -9,10 +9,11 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { useParams } from '@/next/navigation'
|
||||
import { datasetDetailQueryKeyPrefix } from '@/service/knowledge/use-dataset'
|
||||
@ -25,8 +26,8 @@ const Conversion = () => {
|
||||
const { t } = useTranslation()
|
||||
const { datasetId } = useParams()
|
||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
||||
const { mutateAsync: convert, isPending } = useConvertDatasetToPipeline()
|
||||
const invalidDatasetDetail = useInvalid([...datasetDetailQueryKeyPrefix, datasetId])
|
||||
|
||||
@ -157,6 +157,24 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
userProfile: {
|
||||
id: mockCurrentUserId,
|
||||
},
|
||||
isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys,
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
const mockSetShowPricingModal = vi.fn()
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContextSelector: () => mockSetShowPricingModal,
|
||||
|
||||
@ -149,6 +149,24 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
userProfile: {
|
||||
id: mockCurrentUserId,
|
||||
},
|
||||
isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys,
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
const mockSetShowPricingModal = vi.fn()
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContextSelector: <T,>(selector: (state: { setShowPricingModal: typeof mockSetShowPricingModal }) => T): T =>
|
||||
|
||||
@ -137,6 +137,7 @@ vi.mock('@/app/components/base/premium-badge', () => ({
|
||||
|
||||
vi.mock('@/config', () => ({
|
||||
IS_CLOUD_EDITION: true,
|
||||
MARKETPLACE_API_PREFIX: '/marketplace/api',
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks', () => ({
|
||||
@ -165,6 +166,24 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
userProfile: {
|
||||
id: mockCurrentUserId,
|
||||
},
|
||||
isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys,
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => () => 'https://docs.dify.ai',
|
||||
}))
|
||||
|
||||
@ -15,6 +15,7 @@ import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiArrowRightUpLine, RiPlayCircleLine, RiTerminalBoxLine } from '@remixicon/react'
|
||||
import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
@ -24,7 +25,7 @@ import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
import { useChecklistBeforePublish } from '@/app/components/workflow/hooks'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom, workspacePermissionKeysLoadingAtom } from '@/context/app-context-state'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
@ -64,9 +65,9 @@ const Popup = ({
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||
const mutateDatasetRes = useDatasetDetailContextWithSelector(s => s.mutateDatasetRes)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const isLoadingWorkspacePermissionKeys = useAppContextWithSelector(state => state.isLoadingWorkspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const [published, setPublished] = useState(false)
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import type { WorkflowProps } from '@/app/components/workflow'
|
||||
import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
@ -6,7 +8,7 @@ import {
|
||||
import { WorkflowWithInnerContext } from '@/app/components/workflow'
|
||||
import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use-fetch-workflow-inspect-vars'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { getDatasetACLCapabilities } from '@/utils/permission'
|
||||
import {
|
||||
@ -30,8 +32,8 @@ const RagPipelineMain = ({
|
||||
}: RagPipelineMainProps) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const datasetACLCapabilities = useMemo(
|
||||
() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||
currentUserId,
|
||||
@ -41,7 +43,12 @@ const RagPipelineMain = ({
|
||||
[dataset?.maintainer, dataset?.permission_keys, currentUserId, workspacePermissionKeys],
|
||||
)
|
||||
|
||||
const handleWorkflowDataUpdate = useCallback((payload: any) => {
|
||||
type WorkflowDataUpdatePayload = {
|
||||
rag_pipeline_variables?: Parameters<NonNullable<ReturnType<typeof workflowStore.getState>['setRagPipelineVariables']>>[0]
|
||||
environment_variables?: Parameters<ReturnType<typeof workflowStore.getState>['setEnvironmentVariables']>[0]
|
||||
}
|
||||
|
||||
const handleWorkflowDataUpdate = useCallback((payload: WorkflowDataUpdatePayload) => {
|
||||
const {
|
||||
rag_pipeline_variables,
|
||||
environment_variables,
|
||||
@ -179,7 +186,7 @@ const RagPipelineMain = ({
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
viewport={viewport}
|
||||
hooksStore={hooksStore as any}
|
||||
hooksStore={hooksStore as unknown as Partial<HooksStoreShape>}
|
||||
onWorkflowDataUpdate={handleWorkflowDataUpdate}
|
||||
>
|
||||
<RagPipelineChildren />
|
||||
|
||||
@ -40,6 +40,21 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor(),
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys(),
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useMembers: () => ({
|
||||
data: {
|
||||
|
||||
@ -34,6 +34,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys(),
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/use-snippets', () => ({
|
||||
useCreateSnippetMutation: () => ({
|
||||
mutateAsync: mockCreateMutateAsync,
|
||||
|
||||
@ -19,11 +19,12 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CreateSnippetDialog from '@/app/components/snippets/create-snippet-dialog'
|
||||
import { canCreateAndModifySnippets, canManageSnippets } from '@/app/components/snippets/utils/permission'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { TagSelector } from '@/features/tag-management/components/tag-selector'
|
||||
import Link from '@/next/link'
|
||||
import { useMembers } from '@/service/use-common'
|
||||
@ -46,7 +47,7 @@ const SnippetCard = ({
|
||||
}: Props) => {
|
||||
const { t } = useTranslation('snippet')
|
||||
const { t: tCommon } = useTranslation()
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { data: membersData } = useMembers()
|
||||
const [isOperationsMenuOpen, setIsOperationsMenuOpen] = useState(false)
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
|
||||
|
||||
@ -5,9 +5,10 @@ import type { SnippetListItem } from '@/types/snippet'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { currentWorkspaceLoadingAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { TagFilter } from '@/features/tag-management/components/tag-filter'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import dynamic from '@/next/dynamic'
|
||||
@ -56,8 +57,8 @@ const SnippetCardSkeleton = ({ count }: SnippetCardSkeletonProps) => {
|
||||
|
||||
const SnippetList = () => {
|
||||
const { t } = useTranslation()
|
||||
const isLoadingCurrentWorkspace = useAppContextWithSelector(state => state.isLoadingCurrentWorkspace)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
// eslint-disable-next-line react/use-state -- custom URL query hook, not React.useState
|
||||
const {
|
||||
query: { tagIDs, keywords, creatorIDs },
|
||||
|
||||
@ -33,6 +33,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: contextMocks.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: toastMocks,
|
||||
}))
|
||||
|
||||
@ -58,6 +58,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockAppContext.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
let capturedHooksStore: Record<string, unknown> | undefined
|
||||
let capturedWorkflowNodes: WorkflowProps['nodes'] | undefined
|
||||
let snippetDetailStoreState: {
|
||||
|
||||
@ -5,6 +5,7 @@ import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-s
|
||||
import type { SnippetCanvasData, SnippetDetailPayload, SnippetInputField } from '@/models/snippet'
|
||||
import type { SnippetDraftSyncPayload } from '@/types/snippet'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -23,7 +24,7 @@ import {
|
||||
initialEdges,
|
||||
initialNodes,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useSnippetDraftStore } from '../draft-store'
|
||||
import { useConfigsMap } from '../hooks/use-configs-map'
|
||||
import { useGetRunAndTraceUrl } from '../hooks/use-get-run-and-trace-url'
|
||||
@ -143,7 +144,7 @@ const SnippetMain = ({
|
||||
const effectiveDraftEdges = localDraftState?.edges ?? draftEdges
|
||||
const effectiveDraftViewport = localDraftState?.viewport ?? draftViewport
|
||||
const { graph, snippet } = effectiveDraftPayload
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canEditSnippet = canCreateAndModifySnippets(workspacePermissionKeys)
|
||||
const canSave = canEditSnippet && currentCanvasNodeCount > 0
|
||||
const {
|
||||
|
||||
@ -52,6 +52,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys(),
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: {
|
||||
success: (...args: unknown[]) => mockToastSuccess(...args),
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import type { CreateSnippetDialogPayload } from '@/app/components/snippets/create-snippet-dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleClient } from '@/service/client'
|
||||
import { useCreateSnippetMutation } from '@/service/use-snippets'
|
||||
@ -11,7 +12,7 @@ import { canCreateAndModifySnippets } from '../utils/permission'
|
||||
export const useCreateSnippet = () => {
|
||||
const { t } = useTranslation()
|
||||
const { push } = useRouter()
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const createSnippetMutation = useCreateSnippetMutation()
|
||||
const [isCreateSnippetDialogOpen, setIsCreateSnippetDialogOpen] = useState(false)
|
||||
const [isCreatingSnippet, setIsCreatingSnippet] = useState(false)
|
||||
|
||||
@ -16,10 +16,11 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import {
|
||||
useConfirmSnippetImportMutation,
|
||||
@ -107,7 +108,7 @@ function ImportSnippetDSLDialog({
|
||||
}: ImportSnippetDSLDialogProps) {
|
||||
const { t } = useTranslation()
|
||||
const { push } = useRouter()
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canCreateAndModifySnippet = canCreateAndModifySnippets(workspacePermissionKeys)
|
||||
const importSnippetMutation = useImportSnippetDSLMutation()
|
||||
const confirmSnippetImportMutation = useConfirmSnippetImportMutation()
|
||||
|
||||
@ -88,6 +88,23 @@ vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => appContextState,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
isLoadingCurrentWorkspace: appContextState.isLoadingCurrentWorkspace,
|
||||
currentWorkspace: appContextState.currentWorkspace,
|
||||
userProfile: appContextState.userProfile,
|
||||
workspacePermissionKeys: appContextState.workspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
useSearchParams: () => ({
|
||||
get: (key: string) => (key === 'replayRunId' ? searchParamsValue : null),
|
||||
|
||||
@ -4,6 +4,7 @@ import type { CollaborationUpdate } from '@/app/components/workflow/collaboratio
|
||||
import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store/store'
|
||||
import type { Edge, Node } from '@/app/components/workflow/types'
|
||||
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -21,7 +22,7 @@ import { useCollaboration } from '@/app/components/workflow/collaboration/hooks/
|
||||
import { useWorkflowUpdate } from '@/app/components/workflow/hooks/use-workflow-interactions'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||
import { getAppACLCapabilities } from '@/utils/permission'
|
||||
import {
|
||||
@ -77,8 +78,8 @@ const WorkflowMain = ({
|
||||
const filteredCursors = Object.fromEntries(
|
||||
Object.entries(cursors).filter(([userId]) => userId !== myUserId),
|
||||
)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const appACLCapabilities = useMemo(
|
||||
() => getAppACLCapabilities(appDetail?.permission_keys, {
|
||||
currentUserId,
|
||||
|
||||
@ -54,6 +54,21 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
userProfile: { id: 'user-1' },
|
||||
workspacePermissionKeys: ['app.create_and_management'],
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('../use-workflow-template', () => ({
|
||||
useWorkflowTemplate: () => ({
|
||||
nodes: appStoreState.appDetail.mode === 'workflow'
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { Edge, Node } from '@/app/components/workflow/types'
|
||||
import type { FileUploadConfigResponse } from '@/models/common'
|
||||
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
@ -13,7 +14,7 @@ import {
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useWorkflowConfig } from '@/service/use-workflow'
|
||||
import {
|
||||
fetchNodesDefaultConfigs,
|
||||
@ -71,8 +72,8 @@ export const useWorkflowInit = () => {
|
||||
edges: edgesTemplate,
|
||||
} = useWorkflowTemplate()
|
||||
const appDetail = useAppStore(state => state.appDetail)!
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const appACLCapabilities = useMemo(() => getAppACLCapabilities(appDetail.permission_keys, {
|
||||
currentUserId,
|
||||
resourceMaintainer: appDetail.maintainer,
|
||||
@ -118,9 +119,10 @@ export const useWorkflowInit = () => {
|
||||
setSyncWorkflowDraftHash(initialData.hash)
|
||||
setIsLoading(false)
|
||||
}
|
||||
catch (error: any) {
|
||||
if (error && error.json && !error.bodyUsed && appDetail) {
|
||||
error.json().then((err: any) => {
|
||||
catch (error: unknown) {
|
||||
const responseError = error as { bodyUsed?: boolean, json?: () => Promise<{ code?: string }> }
|
||||
if (responseError.json && !responseError.bodyUsed && appDetail) {
|
||||
responseError.json().then((err) => {
|
||||
if (err.code === 'draft_workflow_not_exist') {
|
||||
const isAdvancedChat = appDetail.mode === AppModeEnum.ADVANCED_CHAT
|
||||
const initialGraph = {
|
||||
@ -187,7 +189,7 @@ export const useWorkflowInit = () => {
|
||||
if (!acc[block.type])
|
||||
acc[block.type] = { ...block.config }
|
||||
return acc
|
||||
}, {} as Record<string, any>),
|
||||
}, {} as Record<string, unknown>),
|
||||
})
|
||||
workflowStore.getState().setPublishedAt(publishedWorkflow?.created_at ?? 0)
|
||||
const graph = publishedWorkflow?.graph
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
||||
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
@ -19,7 +20,7 @@ import {
|
||||
initialEdges,
|
||||
initialNodes,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceAtom, currentWorkspaceLoadingAtom, userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useSearchParams } from '@/next/navigation'
|
||||
import { fetchRunDetail } from '@/service/log'
|
||||
import { useAppTriggers } from '@/service/use-tools'
|
||||
@ -45,16 +46,19 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
fileUploadConfigResponse,
|
||||
} = useWorkflowInit()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { isLoadingCurrentWorkspace, currentWorkspace, userProfile, workspacePermissionKeys } = useAppContext()
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
// Initialize trigger status at application level
|
||||
const { setTriggerStatuses } = useTriggerStatusStore()
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
const appACLCapabilities = useMemo(() => getAppACLCapabilities(appDetail?.permission_keys, {
|
||||
currentUserId: userProfile?.id,
|
||||
currentUserId,
|
||||
resourceMaintainer: appDetail?.maintainer,
|
||||
workspacePermissionKeys,
|
||||
}), [appDetail?.maintainer, appDetail?.permission_keys, userProfile?.id, workspacePermissionKeys])
|
||||
}), [appDetail?.maintainer, appDetail?.permission_keys, currentUserId, workspacePermissionKeys])
|
||||
const appId = appDetail?.id
|
||||
const isWorkflowMode = appDetail?.mode === AppModeEnum.WORKFLOW
|
||||
const { data: triggersResponse } = useAppTriggers(isWorkflowMode ? appId : undefined, {
|
||||
@ -78,8 +82,8 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
// Cancel any pending debounced sync operations
|
||||
const { debouncedSyncWorkflowDraft } = workflowStore.getState()
|
||||
// The debounced function from lodash has a cancel method
|
||||
if (debouncedSyncWorkflowDraft && 'cancel' in debouncedSyncWorkflowDraft)
|
||||
(debouncedSyncWorkflowDraft as any).cancel()
|
||||
const cancellableSyncWorkflowDraft = debouncedSyncWorkflowDraft as { cancel?: () => void } | undefined
|
||||
cancellableSyncWorkflowDraft?.cancel?.()
|
||||
}
|
||||
}, [workflowStore])
|
||||
|
||||
|
||||
@ -2,11 +2,12 @@ import type { ScheduleTriggerNodeType } from '../types'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { useNodesReadOnly } from '@/app/components/workflow/hooks'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { createAccountProfileQueryWrapper } from '@/test/account-profile-query'
|
||||
import { BlockEnum } from '../../../types'
|
||||
import useConfig from '../use-config'
|
||||
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks', () => ({
|
||||
useNodesReadOnly: vi.fn(),
|
||||
}))
|
||||
@ -17,12 +18,11 @@ vi.mock('@/app/components/workflow/nodes/_base/hooks/use-node-crud', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: vi.fn(),
|
||||
useAppContext: mockUseAppContext,
|
||||
}))
|
||||
|
||||
const mockUseNodesReadOnly = vi.mocked(useNodesReadOnly)
|
||||
const mockUseNodeCrud = vi.mocked(useNodeCrud)
|
||||
const mockUseAppContext = vi.mocked(useAppContext)
|
||||
|
||||
const setInputs = vi.fn()
|
||||
|
||||
@ -48,7 +48,7 @@ describe('trigger-schedule/use-config', () => {
|
||||
mockUseNodesReadOnly.mockReturnValue({ nodesReadOnly: false, getNodesReadOnly: () => false })
|
||||
mockUseAppContext.mockReturnValue({
|
||||
userProfile: { timezone: 'Asia/Shanghai' },
|
||||
} as ReturnType<typeof useAppContext>)
|
||||
})
|
||||
mockUseNodeCrud.mockReturnValue({
|
||||
inputs: createData(),
|
||||
setInputs,
|
||||
|
||||
@ -8,12 +8,13 @@ import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useEducationDiscount } from '@/app/components/billing/hooks/use-education-discount'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { useSetEducationVerifying } from '@/app/education-apply/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||
@ -52,7 +53,8 @@ const EducationApplyAgeContent = () => {
|
||||
mutateAsync: educationAdd,
|
||||
} = useEducationAdd({ onSuccess: noop })
|
||||
const { onPlanInfoChanged, isEducationAccount, plan } = useProviderContext()
|
||||
const { currentWorkspace, workspacePermissionKeys } = useAppContext()
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const updateEducationStatus = useInvalidateEducationStatus()
|
||||
const docLink = useDocLink()
|
||||
const { handleEducationDiscount } = useEducationDiscount()
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { Avatar } from '@langgenius/dify-ui/avatar'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Triangle } from '@/app/components/base/icons/src/public/education'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { useLogout } from '@/service/use-common'
|
||||
|
||||
const UserInfo = () => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
|
||||
const { mutateAsync: logout } = useLogout()
|
||||
const handleLogout = async () => {
|
||||
|
||||
@ -5,11 +5,12 @@ import { Provider as JotaiProvider } from 'jotai'
|
||||
import { queryClientAtom } from 'jotai-tanstack-query'
|
||||
import { useHydrateAtoms } from 'jotai/react/utils'
|
||||
import { Suspense } from 'react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { setUserId, setUserProperties } from '@/app/components/base/amplitude'
|
||||
import { flushRegistrationSuccess } from '@/app/components/base/amplitude/registration-tracking'
|
||||
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
|
||||
import { ZENDESK_FIELD_IDS } from '@/config'
|
||||
import { initialWorkspaceInfo, useAppContext, useSelector } from '../app-context'
|
||||
import { AppContext, initialWorkspaceInfo, useSelector } from '../app-context'
|
||||
import { AppContextProvider } from '../app-context-provider'
|
||||
|
||||
const mockGetRequest = vi.hoisted(() => vi.fn())
|
||||
@ -157,7 +158,7 @@ vi.mock('@/app/components/header/maintenance-notice', () => ({
|
||||
}))
|
||||
|
||||
function AppContextProbe() {
|
||||
const context = useAppContext()
|
||||
const context = useContext(AppContext)
|
||||
const selectedWorkspacePermissionKeys = useSelector(state => state.workspacePermissionKeys)
|
||||
|
||||
return (
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen'
|
||||
import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/common'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
||||
import { createContext, useContextSelector } from 'use-context-selector'
|
||||
import {
|
||||
initialLangGeniusVersionInfo as defaultLangGeniusVersionInfo,
|
||||
userProfilePlaceholder as defaultUserProfilePlaceholder,
|
||||
@ -53,5 +53,3 @@ export const AppContext = createContext<AppContextValue>({
|
||||
export function useSelector<T>(selector: (value: AppContextValue) => T): T {
|
||||
return useContextSelector(AppContext, selector)
|
||||
}
|
||||
|
||||
export const useAppContext = () => useContext(AppContext)
|
||||
|
||||
@ -11,6 +11,7 @@ import type { InputVar } from '@/app/components/workflow/types'
|
||||
import type { ExpireNoticeModalPayloadProps } from '@/app/education-apply/expire-notice-modal'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
import type { ModerationConfig, PromptVariable } from '@/models/debug'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
DEFAULT_ACCOUNT_SETTING_TAB,
|
||||
@ -20,7 +21,7 @@ import {
|
||||
isWorkspaceSettingTab,
|
||||
} from '@/app/components/header/account-setting/constants'
|
||||
import { useSetEducationVerifying } from '@/app/education-apply/storage'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceIdAtom } from '@/context/app-context-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
useAccountSettingModal,
|
||||
@ -103,7 +104,7 @@ export const ModalContextProvider = ({
|
||||
}> | null>(null)
|
||||
const [showUpdatePluginModal, setShowUpdatePluginModal] = useState<ModalState<UpdatePluginPayload> | null>(null)
|
||||
const [showEducationExpireNoticeModal, setShowEducationExpireNoticeModal] = useState<ModalState<ExpireNoticeModalPayloadProps> | null>(null)
|
||||
const { currentWorkspace } = useAppContext()
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
|
||||
const setEducationVerifying = useSetEducationVerifying()
|
||||
|
||||
const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false)
|
||||
@ -146,7 +147,7 @@ export const ModalContextProvider = ({
|
||||
} = useTriggerEventsLimitModal({
|
||||
plan,
|
||||
isFetchedPlan,
|
||||
currentWorkspaceId: currentWorkspace?.id,
|
||||
currentWorkspaceId,
|
||||
})
|
||||
|
||||
const handleCancelModerationSettingModal = () => {
|
||||
|
||||
@ -52,6 +52,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => mockUseAppContext(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockUseAppContext())
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
type DefaultPlanShape = typeof defaultPlan
|
||||
type ResetShape = {
|
||||
apiRateLimit: number | null
|
||||
|
||||
@ -15,8 +15,9 @@ import {
|
||||
ComboboxItem,
|
||||
ComboboxItemText,
|
||||
} from '@langgenius/dify-ui/combobox'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { SubjectType } from '@/models/access-control'
|
||||
|
||||
export function SubjectItem({
|
||||
@ -154,7 +155,7 @@ type MemberItemProps = {
|
||||
}
|
||||
|
||||
function MemberItem({ member, subject, selectedMembers }: MemberItemProps) {
|
||||
const currentUser = useSelector(s => s.userProfile)
|
||||
const currentUser = useAtomValue(userProfileAtom)
|
||||
const { t } = useTranslation()
|
||||
const isChecked = selectedMembers.some(selectedMember => selectedMember.id === member.id)
|
||||
return (
|
||||
|
||||
@ -21,6 +21,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
const mockTags: Tag[] = [
|
||||
{ id: 'tag-1', name: 'Frontend', type: 'app', binding_count: '' },
|
||||
{ id: 'tag-2', name: 'Backend', type: 'app', binding_count: '' },
|
||||
|
||||
@ -50,6 +50,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
tags: {
|
||||
|
||||
@ -21,6 +21,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
const i18n = {
|
||||
selectorPlaceholder: 'common.tag.selectorPlaceholder',
|
||||
operationClear: 'common.operation.clear',
|
||||
|
||||
@ -39,6 +39,20 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@tanstack/react-query', () => ({
|
||||
useQuery: () => ({ data: mockUseQueryData.current }),
|
||||
useMutation: (mutationOptions: { mutationFn: (input: unknown) => Promise<unknown> }) => ({
|
||||
|
||||
@ -3,9 +3,10 @@ import type { TagType } from '@dify/contracts/api/console/tags/types.gen'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { getTagManagePermissionKey } from '../utils'
|
||||
@ -19,7 +20,7 @@ type TagManagementModalProps = {
|
||||
}
|
||||
export const TagManagementModal = ({ show, type, onClose, onTagsChange }: TagManagementModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canManageTags = hasPermission(workspacePermissionKeys, getTagManagePermissionKey(type))
|
||||
const { data: tagList = [] } = useQuery(consoleQuery.tags.get.queryOptions({
|
||||
input: {
|
||||
@ -69,7 +70,7 @@ export const TagManagementModal = ({ show, type, onClose, onTagsChange }: TagMan
|
||||
<div className="relative pb-2 text-xl/7.5 font-semibold text-text-primary">{t('tag.manageTags', { ns: 'common' })}</div>
|
||||
<DialogCloseButton className="top-4 right-4" />
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<input aria-label={t('tag.addNew', { ns: 'common' }) || ''} className="w-25 shrink-0 appearance-none rounded-lg border border-dashed border-divider-regular bg-transparent px-2 py-1 text-sm/5 text-text-secondary caret-primary-600 outline-hidden placeholder:text-text-quaternary focus:border-solid" placeholder={t('tag.addNew', { ns: 'common' }) || ''} autoFocus value={name} onChange={e => setName(e.target.value)} onKeyDown={e => e.key === 'Enter' && !e.nativeEvent.isComposing && createNewTag()} onBlur={createNewTag} />
|
||||
<input aria-label={t('tag.addNew', { ns: 'common' }) || ''} className="w-25 shrink-0 appearance-none rounded-lg border border-dashed border-divider-regular bg-transparent px-2 py-1 text-sm/5 text-text-secondary caret-primary-600 outline-hidden placeholder:text-text-quaternary focus:border-solid" placeholder={t('tag.addNew', { ns: 'common' }) || ''} value={name} onChange={e => setName(e.target.value)} onKeyDown={e => e.key === 'Enter' && !e.nativeEvent.isComposing && createNewTag()} onBlur={createNewTag} />
|
||||
{tagList.map(tag => (<TagItemEditor key={tag.id} tag={tag} onTagsChange={onTagsChange} />))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import type { TagType } from '@dify/contracts/api/console/tags/types.gen'
|
||||
import type { TagComboboxItem } from './tag-combobox-item'
|
||||
import { ComboboxEmpty, ComboboxInput, ComboboxInputGroup, ComboboxItem, ComboboxItemIndicator, ComboboxItemText, ComboboxList, ComboboxSeparator, useComboboxFilteredItems } from '@langgenius/dify-ui/combobox'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { Fragment } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { getTagManagePermissionKey } from '../utils'
|
||||
import { isCreateTagOption } from './tag-combobox-item'
|
||||
@ -26,7 +27,7 @@ export const TagSearchContent = ({
|
||||
canBindOrUnbindTags = false,
|
||||
}: TagSearchContentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canManageTags = hasPermission(workspacePermissionKeys, getTagManagePermissionKey(type))
|
||||
const filteredItems = useComboboxFilteredItems<TagComboboxItem>()
|
||||
const realItemCount = filteredItems.filter(tag => !isCreateTagOption(tag)).length
|
||||
|
||||
@ -6,9 +6,10 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Combobox, ComboboxContent, ComboboxTrigger } from '@langgenius/dify-ui/combobox'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { useApplyTagBindingsMutation } from '../hooks/use-tag-mutations'
|
||||
@ -71,7 +72,7 @@ export const TagSelector = ({
|
||||
const [open, setOpen] = useState(false)
|
||||
const [draftTags, setDraftTags] = useState<Tag[]>(value)
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canManageTags = hasPermission(workspacePermissionKeys, getTagManagePermissionKey(type))
|
||||
|
||||
const applyTagBindingsMutation = useApplyTagBindingsMutation()
|
||||
|
||||
@ -5,6 +5,7 @@ import type {
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useCallback,
|
||||
useRef,
|
||||
@ -13,7 +14,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { DSLImportStatus } from '@/models/app'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
@ -46,7 +47,8 @@ export const useImportDSL = () => {
|
||||
const { push } = useRouter()
|
||||
const invalidateAppList = useInvalidateAppList()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { userProfile, workspacePermissionKeys } = useAppContext()
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||
const [versions, setVersions] = useState<{ importedVersion: string, systemVersion: string }>()
|
||||
const importIdRef = useRef<string>('')
|
||||
@ -98,8 +100,8 @@ export const useImportDSL = () => {
|
||||
invalidateAppList()
|
||||
await handleCheckPluginDependencies(app_id)
|
||||
getRedirection({ id: app_id, mode: app_mode, permission_keys }, push, {
|
||||
currentUserId: userProfile?.id,
|
||||
resourceMaintainer: userProfile?.id,
|
||||
currentUserId,
|
||||
resourceMaintainer: currentUserId,
|
||||
workspacePermissionKeys,
|
||||
isRbacEnabled,
|
||||
})
|
||||
@ -124,7 +126,7 @@ export const useImportDSL = () => {
|
||||
finally {
|
||||
setIsFetching(false)
|
||||
}
|
||||
}, [isFetching, t, handleCheckPluginDependencies, isRbacEnabled, push, setNeedRefresh, invalidateAppList, userProfile?.id, workspacePermissionKeys])
|
||||
}, [isFetching, t, handleCheckPluginDependencies, isRbacEnabled, push, setNeedRefresh, invalidateAppList, currentUserId, workspacePermissionKeys])
|
||||
|
||||
const handleImportDSLConfirm = useCallback(async (
|
||||
{
|
||||
@ -154,8 +156,8 @@ export const useImportDSL = () => {
|
||||
setNeedRefresh('1')
|
||||
invalidateAppList()
|
||||
getRedirection({ id: app_id, mode: app_mode, permission_keys }, push, {
|
||||
currentUserId: userProfile?.id,
|
||||
resourceMaintainer: userProfile?.id,
|
||||
currentUserId,
|
||||
resourceMaintainer: currentUserId,
|
||||
workspacePermissionKeys,
|
||||
isRbacEnabled,
|
||||
})
|
||||
@ -172,7 +174,7 @@ export const useImportDSL = () => {
|
||||
finally {
|
||||
setIsFetching(false)
|
||||
}
|
||||
}, [isFetching, t, handleCheckPluginDependencies, isRbacEnabled, setNeedRefresh, push, invalidateAppList, userProfile?.id, workspacePermissionKeys])
|
||||
}, [isFetching, t, handleCheckPluginDependencies, isRbacEnabled, setNeedRefresh, push, invalidateAppList, currentUserId, workspacePermissionKeys])
|
||||
|
||||
return {
|
||||
handleImportDSL,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user