mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
refactor(web): migrate dataset access context (#38523)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
915655683c
commit
ee0068eed4
@ -31,14 +31,44 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
userProfile: { id: 'user-1' },
|
userProfile: { id: 'user-1' },
|
||||||
workspacePermissionKeys: [],
|
workspacePermissionKeys: [],
|
||||||
}),
|
}),
|
||||||
|
useSelector: (selector: (state: {
|
||||||
|
isCurrentWorkspaceDatasetOperator: boolean
|
||||||
|
isLoadingCurrentWorkspace: boolean
|
||||||
|
isLoadingWorkspacePermissionKeys: boolean
|
||||||
|
userProfile: { id: string }
|
||||||
|
workspacePermissionKeys: string[]
|
||||||
|
}) => unknown) => selector({
|
||||||
|
isCurrentWorkspaceDatasetOperator: false,
|
||||||
|
isLoadingCurrentWorkspace: false,
|
||||||
|
isLoadingWorkspacePermissionKeys: false,
|
||||||
|
userProfile: { id: 'user-1' },
|
||||||
|
workspacePermissionKeys: [],
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => ({
|
||||||
|
userProfile: { id: 'user-1' },
|
||||||
|
workspacePermissionKeys: [],
|
||||||
|
}), () => ({
|
||||||
|
isRbacEnabled: mockIsRbacEnabled,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/context/event-emitter', () => ({
|
vi.mock('@/context/event-emitter', () => ({
|
||||||
useEventEmitterContextContext: () => ({
|
useEventEmitterContextContext: () => ({
|
||||||
eventEmitter: undefined,
|
eventEmitter: undefined,
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/hooks/use-document-title', () => ({
|
vi.mock('@/hooks/use-document-title', () => ({
|
||||||
default: vi.fn(),
|
default: vi.fn(),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -2,14 +2,19 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import type { DataSet } from '@/models/datasets'
|
import type { DataSet } from '@/models/datasets'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
import { useAtomValue } from 'jotai'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import {
|
||||||
|
currentWorkspaceLoadingAtom,
|
||||||
|
datasetRbacEnabledAtom,
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
workspacePermissionKeysLoadingAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import DatasetDetailContext from '@/context/dataset-detail'
|
import DatasetDetailContext from '@/context/dataset-detail'
|
||||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
|
||||||
import useDocumentTitle from '@/hooks/use-document-title'
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
import { usePathname, useRouter } from '@/next/navigation'
|
import { usePathname, useRouter } from '@/next/navigation'
|
||||||
import { useDatasetDetail } from '@/service/knowledge/use-dataset'
|
import { useDatasetDetail } from '@/service/knowledge/use-dataset'
|
||||||
@ -58,23 +63,20 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||||
const {
|
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||||
isLoadingCurrentWorkspace,
|
const isRbacEnabled = useAtomValue(datasetRbacEnabledAtom)
|
||||||
isLoadingWorkspacePermissionKeys,
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
userProfile,
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
workspacePermissionKeys,
|
|
||||||
} = useAppContext()
|
|
||||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
|
||||||
|
|
||||||
const { data: datasetRes, error, refetch: mutateDatasetRes } = useDatasetDetail(datasetId)
|
const { data: datasetRes, error, refetch: mutateDatasetRes } = useDatasetDetail(datasetId)
|
||||||
const shouldRedirect = shouldRedirectToDatasetList(error)
|
const shouldRedirect = shouldRedirectToDatasetList(error)
|
||||||
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(datasetRes?.permission_keys, {
|
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(datasetRes?.permission_keys, {
|
||||||
currentUserId: userProfile?.id,
|
currentUserId,
|
||||||
resourceMaintainer: datasetRes?.maintainer,
|
resourceMaintainer: datasetRes?.maintainer,
|
||||||
workspacePermissionKeys,
|
workspacePermissionKeys,
|
||||||
isRbacEnabled,
|
isRbacEnabled,
|
||||||
}), [datasetRes?.maintainer, datasetRes?.permission_keys, isRbacEnabled, userProfile?.id, workspacePermissionKeys])
|
}), [datasetRes?.maintainer, datasetRes?.permission_keys, isRbacEnabled, currentUserId, workspacePermissionKeys])
|
||||||
const isAccessConfigPath = pathname.endsWith('/access-config')
|
const isAccessConfigPath = pathname.endsWith('/access-config')
|
||||||
const isHitTestingPath = pathname.endsWith('/hitTesting')
|
const isHitTestingPath = pathname.endsWith('/hitTesting')
|
||||||
const isPermissionControlledPath = isAccessConfigPath || isHitTestingPath
|
const isPermissionControlledPath = isAccessConfigPath || isHitTestingPath
|
||||||
|
|||||||
@ -20,10 +20,22 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
useSelector: (selector: (state: AppContextMock) => unknown) => selector(mockUseAppContext()),
|
useSelector: (selector: (state: AppContextMock) => unknown) => selector(mockUseAppContext()),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => mockUseAppContext())
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/context/external-api-panel-context', () => ({
|
vi.mock('@/context/external-api-panel-context', () => ({
|
||||||
ExternalApiPanelProvider: ({ children }: { children: ReactNode }) => <>{children}</>,
|
ExternalApiPanelProvider: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/context/external-knowledge-api-context', () => ({
|
vi.mock('@/context/external-knowledge-api-context', () => ({
|
||||||
ExternalKnowledgeApiProvider: ({ children, enabled }: { children: ReactNode, enabled?: boolean }) => {
|
ExternalKnowledgeApiProvider: ({ children, enabled }: { children: ReactNode, enabled?: boolean }) => {
|
||||||
mockExternalKnowledgeApiProviderEnabled = enabled
|
mockExternalKnowledgeApiProviderEnabled = enabled
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
import {
|
||||||
|
currentWorkspaceIdAtom,
|
||||||
|
currentWorkspaceLoadingAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
workspacePermissionKeysLoadingAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { ExternalApiPanelProvider } from '@/context/external-api-panel-context'
|
import { ExternalApiPanelProvider } from '@/context/external-api-panel-context'
|
||||||
import { ExternalKnowledgeApiProvider } from '@/context/external-knowledge-api-context'
|
import { ExternalKnowledgeApiProvider } from '@/context/external-knowledge-api-context'
|
||||||
import { usePathname, useRouter } from '@/next/navigation'
|
import { usePathname, useRouter } from '@/next/navigation'
|
||||||
@ -21,10 +27,10 @@ const isDatasetExternalConnectPath = (pathname: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function DatasetsLayout({ children }: { children: React.ReactNode }) {
|
export default function DatasetsLayout({ children }: { children: React.ReactNode }) {
|
||||||
const currentWorkspaceId = useAppContextSelector(state => state.currentWorkspace.id)
|
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
|
||||||
const isLoadingCurrentWorkspace = useAppContextSelector(state => state.isLoadingCurrentWorkspace)
|
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||||
const isLoadingWorkspacePermissionKeys = useAppContextSelector(state => state.isLoadingWorkspacePermissionKeys)
|
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||||
const workspacePermissionKeys = useAppContextSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const isLoadingAccess = isLoadingCurrentWorkspace || !!isLoadingWorkspacePermissionKeys
|
const isLoadingAccess = isLoadingCurrentWorkspace || !!isLoadingWorkspacePermissionKeys
|
||||||
|
|||||||
142
web/app/components/datasets/__tests__/mock-dataset-access.ts
Normal file
142
web/app/components/datasets/__tests__/mock-dataset-access.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
const DATASET_ACCESS_ATOM_KIND = Symbol('dataset-access-atom-kind')
|
||||||
|
|
||||||
|
type DatasetAccessMockState = {
|
||||||
|
userProfile?: {
|
||||||
|
id?: string
|
||||||
|
name?: string
|
||||||
|
email?: string
|
||||||
|
avatar?: string
|
||||||
|
avatar_url?: string
|
||||||
|
is_password_set?: boolean
|
||||||
|
} | null
|
||||||
|
currentWorkspace?: {
|
||||||
|
id?: string
|
||||||
|
} | null
|
||||||
|
isCurrentWorkspaceOwner?: boolean
|
||||||
|
isLoadingCurrentWorkspace?: boolean
|
||||||
|
isLoadingWorkspacePermissionKeys?: boolean
|
||||||
|
workspacePermissionKeys?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatasetAccessMockOptions = {
|
||||||
|
isRbacEnabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatasetAccessAtomKind
|
||||||
|
= | 'userProfile'
|
||||||
|
| 'userProfileId'
|
||||||
|
| 'currentWorkspaceId'
|
||||||
|
| 'isCurrentWorkspaceOwner'
|
||||||
|
| 'workspacePermissionKeys'
|
||||||
|
| 'currentWorkspaceLoading'
|
||||||
|
| 'workspacePermissionKeysLoading'
|
||||||
|
| 'datasetRbacEnabled'
|
||||||
|
|
||||||
|
type DatasetAccessMockAtom = {
|
||||||
|
[DATASET_ACCESS_ATOM_KIND]: DatasetAccessAtomKind
|
||||||
|
}
|
||||||
|
|
||||||
|
type DatasetAccessMockRegistry = {
|
||||||
|
getState: () => DatasetAccessMockState
|
||||||
|
getOptions: () => DatasetAccessMockOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultUserProfile = {
|
||||||
|
id: 'user-1',
|
||||||
|
name: 'User',
|
||||||
|
email: 'user@example.com',
|
||||||
|
avatar: '',
|
||||||
|
avatar_url: '',
|
||||||
|
is_password_set: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
let datasetAccessMockRegistry: DatasetAccessMockRegistry | undefined
|
||||||
|
|
||||||
|
const createMockAtom = (
|
||||||
|
kind: DatasetAccessAtomKind,
|
||||||
|
): DatasetAccessMockAtom => ({
|
||||||
|
[DATASET_ACCESS_ATOM_KIND]: kind,
|
||||||
|
})
|
||||||
|
|
||||||
|
const isDatasetAccessMockAtom = (atom: unknown): atom is DatasetAccessMockAtom => {
|
||||||
|
return typeof atom === 'object' && atom !== null && DATASET_ACCESS_ATOM_KIND in atom
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserProfile = (state: DatasetAccessMockState) => ({
|
||||||
|
...defaultUserProfile,
|
||||||
|
...state.userProfile,
|
||||||
|
})
|
||||||
|
|
||||||
|
const getWorkspacePermissionKeys = (state: DatasetAccessMockState) => state.workspacePermissionKeys ?? []
|
||||||
|
|
||||||
|
export const createDatasetAccessAtomMock = async (
|
||||||
|
importOriginal: <T>() => Promise<T>,
|
||||||
|
getState: () => DatasetAccessMockState,
|
||||||
|
getOptions: () => DatasetAccessMockOptions = () => ({}),
|
||||||
|
) => {
|
||||||
|
const actual = await importOriginal<typeof import('@/context/app-context-state')>()
|
||||||
|
datasetAccessMockRegistry = {
|
||||||
|
getState,
|
||||||
|
getOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
userProfileAtom: createMockAtom('userProfile'),
|
||||||
|
userProfileIdAtom: createMockAtom('userProfileId'),
|
||||||
|
currentWorkspaceIdAtom: createMockAtom('currentWorkspaceId'),
|
||||||
|
isCurrentWorkspaceOwnerAtom: createMockAtom('isCurrentWorkspaceOwner'),
|
||||||
|
workspacePermissionKeysAtom: createMockAtom('workspacePermissionKeys'),
|
||||||
|
currentWorkspaceLoadingAtom: createMockAtom('currentWorkspaceLoading'),
|
||||||
|
workspacePermissionKeysLoadingAtom: createMockAtom('workspacePermissionKeysLoading'),
|
||||||
|
datasetRbacEnabledAtom: createMockAtom('datasetRbacEnabled'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDatasetAccessJotaiMock = async (
|
||||||
|
importOriginal: <T>() => Promise<T>,
|
||||||
|
) => {
|
||||||
|
const actual = await importOriginal<typeof import('jotai')>()
|
||||||
|
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useAtomValue: (atom: unknown) => {
|
||||||
|
if (!isDatasetAccessMockAtom(atom))
|
||||||
|
return actual.useAtomValue(atom as Parameters<typeof actual.useAtomValue>[0])
|
||||||
|
|
||||||
|
if (!datasetAccessMockRegistry)
|
||||||
|
throw new Error('Dataset access atom mock is not initialized')
|
||||||
|
|
||||||
|
const state = datasetAccessMockRegistry.getState()
|
||||||
|
const options = datasetAccessMockRegistry.getOptions()
|
||||||
|
const userProfile = getUserProfile(state)
|
||||||
|
const workspacePermissionKeys = getWorkspacePermissionKeys(state)
|
||||||
|
|
||||||
|
if (atom[DATASET_ACCESS_ATOM_KIND] === 'userProfile')
|
||||||
|
return userProfile
|
||||||
|
|
||||||
|
if (atom[DATASET_ACCESS_ATOM_KIND] === 'userProfileId')
|
||||||
|
return userProfile.id
|
||||||
|
|
||||||
|
if (atom[DATASET_ACCESS_ATOM_KIND] === 'currentWorkspaceId')
|
||||||
|
return state.currentWorkspace?.id ?? 'workspace-1'
|
||||||
|
|
||||||
|
if (atom[DATASET_ACCESS_ATOM_KIND] === 'isCurrentWorkspaceOwner')
|
||||||
|
return state.isCurrentWorkspaceOwner ?? false
|
||||||
|
|
||||||
|
if (atom[DATASET_ACCESS_ATOM_KIND] === 'workspacePermissionKeys')
|
||||||
|
return workspacePermissionKeys
|
||||||
|
|
||||||
|
if (atom[DATASET_ACCESS_ATOM_KIND] === 'currentWorkspaceLoading')
|
||||||
|
return state.isLoadingCurrentWorkspace ?? false
|
||||||
|
|
||||||
|
if (atom[DATASET_ACCESS_ATOM_KIND] === 'workspacePermissionKeysLoading')
|
||||||
|
return state.isLoadingWorkspacePermissionKeys ?? false
|
||||||
|
|
||||||
|
if (atom[DATASET_ACCESS_ATOM_KIND] === 'datasetRbacEnabled')
|
||||||
|
return options.isRbacEnabled ?? true
|
||||||
|
|
||||||
|
throw new Error(`Unsupported dataset access atom: ${atom[DATASET_ACCESS_ATOM_KIND]}`)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -79,6 +79,14 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
useSelector: vi.fn((selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState)),
|
useSelector: vi.fn((selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState)),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState, () => ({
|
||||||
|
isRbacEnabled: mockIsRbacEnabled,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/app/components/access-rules-editor', () => ({
|
vi.mock('@/app/components/access-rules-editor', () => ({
|
||||||
default: (props: AccessRulesEditorProps) => {
|
default: (props: AccessRulesEditorProps) => {
|
||||||
mockAccessRulesEditor.props = props
|
mockAccessRulesEditor.props = props
|
||||||
@ -88,6 +96,12 @@ vi.mock('@/app/components/access-rules-editor', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
describe('DatasetAccessConfigPage', () => {
|
describe('DatasetAccessConfigPage', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
|||||||
@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
import type { ResourceOpenScope } from '@/models/access-control'
|
import type { ResourceOpenScope } from '@/models/access-control'
|
||||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
import { useAtomValue } from 'jotai'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import AccessRulesEditor from '@/app/components/access-rules-editor'
|
import AccessRulesEditor from '@/app/components/access-rules-editor'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
datasetRbacEnabledAtom,
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import { useLocale } from '@/context/i18n'
|
import { useLocale } from '@/context/i18n'
|
||||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
|
||||||
import { getAccessControlTemplateLanguage } from '@/i18n-config/language'
|
import { getAccessControlTemplateLanguage } from '@/i18n-config/language'
|
||||||
import {
|
import {
|
||||||
useDatasetAccessRules,
|
useDatasetAccessRules,
|
||||||
@ -30,16 +33,15 @@ const DatasetAccessConfigPage = ({ datasetId }: DatasetAccessConfigPageProps) =>
|
|||||||
const locale = useLocale()
|
const locale = useLocale()
|
||||||
const language = useMemo(() => getAccessControlTemplateLanguage(locale), [locale])
|
const language = useMemo(() => getAccessControlTemplateLanguage(locale), [locale])
|
||||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
const isRbacEnabled = useAtomValue(datasetRbacEnabledAtom)
|
||||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
const canAccessConfig = getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||||
const canAccessConfig = useMemo(() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
|
||||||
currentUserId,
|
currentUserId,
|
||||||
resourceMaintainer: dataset?.maintainer,
|
resourceMaintainer: dataset?.maintainer,
|
||||||
workspacePermissionKeys,
|
workspacePermissionKeys,
|
||||||
isRbacEnabled,
|
isRbacEnabled,
|
||||||
}).canAccessConfig, [currentUserId, dataset?.maintainer, dataset?.permission_keys, isRbacEnabled, workspacePermissionKeys])
|
}).canAccessConfig
|
||||||
const { data: datasetAccessRulesResponse, isLoading: isLoadingDatasetAccessRules } = useDatasetAccessRules(datasetId, language, { enabled: canAccessConfig })
|
const { data: datasetAccessRulesResponse, isLoading: isLoadingDatasetAccessRules } = useDatasetAccessRules(datasetId, language, { enabled: canAccessConfig })
|
||||||
const { data: datasetUserAccessSettingsResponse, isLoading: isLoadingDatasetUserAccessSettings } = useDatasetUserAccessSettings(datasetId, language, { enabled: canAccessConfig })
|
const { data: datasetUserAccessSettingsResponse, isLoading: isLoadingDatasetUserAccessSettings } = useDatasetUserAccessSettings(datasetId, language, { enabled: canAccessConfig })
|
||||||
const { mutate: updateDatasetOpenScope, isPending: isUpdatingDatasetOpenScope } = useUpdateDatasetOpenScope(datasetId)
|
const { mutate: updateDatasetOpenScope, isPending: isUpdatingDatasetOpenScope } = useUpdateDatasetOpenScope(datasetId)
|
||||||
|
|||||||
@ -54,6 +54,16 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => ({
|
||||||
|
userProfile: { id: mockCurrentUserId },
|
||||||
|
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||||
|
isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
// Mock modal context
|
// Mock modal context
|
||||||
const mockSetShowAccountSettingModal = vi.fn()
|
const mockSetShowAccountSettingModal = vi.fn()
|
||||||
vi.mock('@/context/modal-context', () => ({
|
vi.mock('@/context/modal-context', () => ({
|
||||||
@ -68,6 +78,12 @@ vi.mock('@/context/modal-context', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
// Mock dataset detail context
|
// Mock dataset detail context
|
||||||
let mockDatasetDetail: DataSet | undefined
|
let mockDatasetDetail: DataSet | undefined
|
||||||
vi.mock('@/context/dataset-detail', () => ({
|
vi.mock('@/context/dataset-detail', () => ({
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { NotionPage } from '@/models/common'
|
|||||||
import type { CrawlOptions, CrawlResultItem, createDocumentResponse, FileItem } from '@/models/datasets'
|
import type { CrawlOptions, CrawlResultItem, createDocumentResponse, FileItem } from '@/models/datasets'
|
||||||
import type { RETRIEVE_METHOD } from '@/types/app'
|
import type { RETRIEVE_METHOD } from '@/types/app'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -10,7 +11,11 @@ import Loading from '@/app/components/base/loading'
|
|||||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||||
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import { useIntegrationsSetting } from '@/app/components/header/account-setting/use-integrations-setting'
|
import { useIntegrationsSetting } from '@/app/components/header/account-setting/use-integrations-setting'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
workspacePermissionKeysLoadingAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import { DataSourceProvider } from '@/models/common'
|
import { DataSourceProvider } from '@/models/common'
|
||||||
import { DataSourceType } from '@/models/datasets'
|
import { DataSourceType } from '@/models/datasets'
|
||||||
@ -43,9 +48,9 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const openIntegrationsSetting = useIntegrationsSetting()
|
const openIntegrationsSetting = useIntegrationsSetting()
|
||||||
const datasetDetail = useDatasetDetailContextWithSelector(state => state.dataset)
|
const datasetDetail = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const isLoadingWorkspacePermissionKeys = useAppContextWithSelector(state => state.isLoadingWorkspacePermissionKeys)
|
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
|
const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
|
||||||
const canAddDocumentsToDataset = !datasetId || getDatasetACLCapabilities(datasetDetail?.permission_keys, {
|
const canAddDocumentsToDataset = !datasetId || getDatasetACLCapabilities(datasetDetail?.permission_keys, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
|||||||
@ -55,6 +55,15 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => ({
|
||||||
|
userProfile: { id: 'test-user' },
|
||||||
|
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
// Mock document service hooks
|
// Mock document service hooks
|
||||||
const mockInvalidDocumentList = vi.fn()
|
const mockInvalidDocumentList = vi.fn()
|
||||||
const mockInvalidDocumentDetail = vi.fn()
|
const mockInvalidDocumentDetail = vi.fn()
|
||||||
@ -92,6 +101,12 @@ vi.mock('@/service/knowledge/use-document', () => ({
|
|||||||
useInvalidDocumentDetail: vi.fn(() => mockInvalidDocumentDetail),
|
useInvalidDocumentDetail: vi.fn(() => mockInvalidDocumentDetail),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
// Mock segment service hooks
|
// Mock segment service hooks
|
||||||
vi.mock('@/service/knowledge/use-segment', () => ({
|
vi.mock('@/service/knowledge/use-segment', () => ({
|
||||||
useSegmentListKey: 'segment-list-key',
|
useSegmentListKey: 'segment-list-key',
|
||||||
|
|||||||
@ -42,6 +42,15 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => ({
|
||||||
|
userProfile: { id: 'user-1' },
|
||||||
|
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/app/components/datasets/metadata/hooks/use-batch-edit-document-metadata', () => ({
|
vi.mock('@/app/components/datasets/metadata/hooks/use-batch-edit-document-metadata', () => ({
|
||||||
default: () => ({
|
default: () => ({
|
||||||
isShowEditModal: false,
|
isShowEditModal: false,
|
||||||
@ -52,6 +61,12 @@ vi.mock('@/app/components/datasets/metadata/hooks/use-batch-edit-document-metada
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
const createTestQueryClient = () => new QueryClient({
|
const createTestQueryClient = () => new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: { retry: false, gcTime: 0 },
|
queries: { retry: false, gcTime: 0 },
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { SimpleDocumentDetail } from '@/models/datasets'
|
|||||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||||
import { pick } from 'es-toolkit/object'
|
import { pick } from 'es-toolkit/object'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -9,7 +10,10 @@ import ChunkingModeLabel from '@/app/components/datasets/common/chunking-mode-la
|
|||||||
import Operations from '@/app/components/datasets/documents/components/operations'
|
import Operations from '@/app/components/datasets/documents/components/operations'
|
||||||
import SummaryStatus from '@/app/components/datasets/documents/detail/completed/common/summary-status'
|
import SummaryStatus from '@/app/components/datasets/documents/detail/completed/common/summary-status'
|
||||||
import StatusItem from '@/app/components/datasets/documents/status-item'
|
import StatusItem from '@/app/components/datasets/documents/status-item'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import useTimestamp from '@/hooks/use-timestamp'
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
import { DataSourceType } from '@/models/datasets'
|
import { DataSourceType } from '@/models/datasets'
|
||||||
@ -62,8 +66,8 @@ const DocumentTableRow = React.memo(({
|
|||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const documentNameId = React.useId()
|
const documentNameId = React.useId()
|
||||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
resourceMaintainer: dataset?.maintainer,
|
resourceMaintainer: dataset?.maintainer,
|
||||||
|
|||||||
@ -4,11 +4,15 @@ import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
|||||||
import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group'
|
import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group'
|
||||||
import { Pagination } from '@langgenius/dify-ui/pagination'
|
import { Pagination } from '@langgenius/dify-ui/pagination'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import EditMetadataBatchModal from '@/app/components/datasets/metadata/edit-metadata-batch/modal'
|
import EditMetadataBatchModal from '@/app/components/datasets/metadata/edit-metadata-batch/modal'
|
||||||
import useBatchEditDocumentMetadata from '@/app/components/datasets/metadata/hooks/use-batch-edit-document-metadata'
|
import useBatchEditDocumentMetadata from '@/app/components/datasets/metadata/hooks/use-batch-edit-document-metadata'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useDatasetDetailContextWithSelector as useDatasetDetailContext } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector as useDatasetDetailContext } from '@/context/dataset-detail'
|
||||||
import { ChunkingMode, DocumentActionType } from '@/models/datasets'
|
import { ChunkingMode, DocumentActionType } from '@/models/datasets'
|
||||||
import { getDatasetACLCapabilities } from '@/utils/permission'
|
import { getDatasetACLCapabilities } from '@/utils/permission'
|
||||||
@ -61,8 +65,8 @@ const DocumentList = ({
|
|||||||
const pageSize = pagination.limit ?? 10
|
const pageSize = pagination.limit ?? 10
|
||||||
const totalPages = Math.max(Math.ceil(pagination.total / pageSize), 1)
|
const totalPages = Math.max(Math.ceil(pagination.total / pageSize), 1)
|
||||||
const datasetConfig = useDatasetDetailContext(s => s.dataset)
|
const datasetConfig = useDatasetDetailContext(s => s.dataset)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const datasetACLCapabilities = useMemo(() => getDatasetACLCapabilities(datasetConfig?.permission_keys, {
|
const datasetACLCapabilities = useMemo(() => getDatasetACLCapabilities(datasetConfig?.permission_keys, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
resourceMaintainer: datasetConfig?.maintainer,
|
resourceMaintainer: datasetConfig?.maintainer,
|
||||||
|
|||||||
@ -63,6 +63,16 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => ({
|
||||||
|
userProfile: { id: mockCurrentUserId },
|
||||||
|
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||||
|
isLoadingWorkspacePermissionKeys: mockIsLoadingWorkspacePermissionKeys,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/service/use-billing', () => ({
|
vi.mock('@/service/use-billing', () => ({
|
||||||
useCurrentPlanVectorSpace: () => ({
|
useCurrentPlanVectorSpace: () => ({
|
||||||
data: {
|
data: {
|
||||||
@ -73,6 +83,12 @@ vi.mock('@/service/use-billing', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/context/dataset-detail', () => ({
|
vi.mock('@/context/dataset-detail', () => ({
|
||||||
useDatasetDetailContextWithSelector: (selector: (state: { dataset: { id: string, pipeline_id: string, permission_keys: string[] } }) => unknown) =>
|
useDatasetDetailContextWithSelector: (selector: (state: { dataset: { id: string, pipeline_id: string, permission_keys: string[] } }) => unknown) =>
|
||||||
selector({ dataset: { id: 'test-dataset-id', pipeline_id: 'test-pipeline-id', permission_keys: mockDatasetPermissionKeys } }),
|
selector({ dataset: { id: 'test-dataset-id', pipeline_id: 'test-pipeline-id', permission_keys: mockDatasetPermissionKeys } }),
|
||||||
|
|||||||
@ -5,11 +5,16 @@ import type { Node } from '@/app/components/workflow/types'
|
|||||||
import type { FileIndexingEstimateResponse } from '@/models/datasets'
|
import type { FileIndexingEstimateResponse } from '@/models/datasets'
|
||||||
import type { InitialDocumentDetail } from '@/models/pipeline'
|
import type { InitialDocumentDetail } from '@/models/pipeline'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { PlanUpgradeModal } from '@/app/components/billing/plan-upgrade-modal'
|
import { PlanUpgradeModal } from '@/app/components/billing/plan-upgrade-modal'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
workspacePermissionKeysLoadingAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import { useProviderContextSelector } from '@/context/provider-context'
|
import { useProviderContextSelector } from '@/context/provider-context'
|
||||||
import { DatasourceType } from '@/models/pipeline'
|
import { DatasourceType } from '@/models/pipeline'
|
||||||
@ -40,9 +45,9 @@ const CreateFormPipeline = () => {
|
|||||||
const enableBilling = useProviderContextSelector(state => state.enableBilling)
|
const enableBilling = useProviderContextSelector(state => state.enableBilling)
|
||||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||||
const pipelineId = dataset?.pipeline_id
|
const pipelineId = dataset?.pipeline_id
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const isLoadingWorkspacePermissionKeys = useAppContextWithSelector(state => state.isLoadingWorkspacePermissionKeys)
|
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const dataSourceStore = useDataSourceStore()
|
const dataSourceStore = useDataSourceStore()
|
||||||
const canAddDocumentsToDataset = getDatasetACLCapabilities(dataset?.permission_keys, {
|
const canAddDocumentsToDataset = getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type { DocumentDisplayStatus, FileItem, FullDocumentDetail } from '@/mode
|
|||||||
import type { SegmentImportStatus } from '@/types/dataset'
|
import type { SegmentImportStatus } from '@/types/dataset'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -11,7 +12,10 @@ import Divider from '@/app/components/base/divider'
|
|||||||
import FloatRightContainer from '@/app/components/base/float-right-container'
|
import FloatRightContainer from '@/app/components/base/float-right-container'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import Metadata from '@/app/components/datasets/metadata/metadata-document'
|
import Metadata from '@/app/components/datasets/metadata/metadata-document'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
import { ChunkingMode, DisplayStatusList } from '@/models/datasets'
|
import { ChunkingMode, DisplayStatusList } from '@/models/datasets'
|
||||||
@ -49,8 +53,8 @@ const DocumentDetail: FC<DocumentDetailProps> = ({ datasetId, documentId }) => {
|
|||||||
const isMobile = media === MediaType.mobile
|
const isMobile = media === MediaType.mobile
|
||||||
|
|
||||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const embeddingAvailable = !!dataset?.embedding_available
|
const embeddingAvailable = !!dataset?.embedding_available
|
||||||
const datasetACLCapabilities = useMemo(
|
const datasetACLCapabilities = useMemo(
|
||||||
() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
import { useProviderContext } from '@/context/provider-context'
|
||||||
import { DataSourceType } from '@/models/datasets'
|
import { DataSourceType } from '@/models/datasets'
|
||||||
@ -31,8 +35,8 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
|
|||||||
const isFreePlan = plan.type === 'sandbox'
|
const isFreePlan = plan.type === 'sandbox'
|
||||||
|
|
||||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const embeddingAvailable = !!dataset?.embedding_available
|
const embeddingAvailable = !!dataset?.embedding_available
|
||||||
const datasetACLCapabilities = getDatasetACLCapabilities(dataset?.permission_keys, {
|
const datasetACLCapabilities = getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||||
import { Switch } from '@langgenius/dify-ui/switch'
|
import { Switch } from '@langgenius/dify-ui/switch'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url'
|
import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url'
|
||||||
import Link from '@/next/link'
|
import Link from '@/next/link'
|
||||||
@ -22,8 +26,8 @@ const Card = ({
|
|||||||
const datasetId = useDatasetDetailContextWithSelector(state => state.dataset?.id)
|
const datasetId = useDatasetDetailContextWithSelector(state => state.dataset?.id)
|
||||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||||
const mutateDatasetRes = useDatasetDetailContextWithSelector(state => state.mutateDatasetRes)
|
const mutateDatasetRes = useDatasetDetailContextWithSelector(state => state.mutateDatasetRes)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const { mutateAsync: enableDatasetServiceApi } = useEnableDatasetServiceApi()
|
const { mutateAsync: enableDatasetServiceApi } = useEnableDatasetServiceApi()
|
||||||
const { mutateAsync: disableDatasetServiceApi } = useDisableDatasetServiceApi()
|
const { mutateAsync: disableDatasetServiceApi } = useDisableDatasetServiceApi()
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,14 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
selector({ workspacePermissionKeys: mockWorkspacePermissionKeys }),
|
selector({ workspacePermissionKeys: mockWorkspacePermissionKeys }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => ({
|
||||||
|
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/next/navigation', () => ({
|
vi.mock('@/next/navigation', () => ({
|
||||||
useRouter: () => ({
|
useRouter: () => ({
|
||||||
push: vi.fn(),
|
push: vi.fn(),
|
||||||
@ -24,6 +32,12 @@ vi.mock('@/next/navigation', () => ({
|
|||||||
useSearchParams: () => new URLSearchParams(),
|
useSearchParams: () => new URLSearchParams(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
// Mock next/link
|
// Mock next/link
|
||||||
vi.mock('@/next/link', () => ({
|
vi.mock('@/next/link', () => ({
|
||||||
default: ({ children, href, ...props }: { children: React.ReactNode, href: string, [key: string]: unknown }) => (
|
default: ({ children, href, ...props }: { children: React.ReactNode, href: string, [key: string]: unknown }) => (
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import SecretKeyModal from '@/app/components/develop/secret-key/secret-key-modal'
|
import SecretKeyModal from '@/app/components/develop/secret-key/secret-key-modal'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||||
import { hasPermission } from '@/utils/permission'
|
import { hasPermission } from '@/utils/permission'
|
||||||
import Card from './card'
|
import Card from './card'
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ const ServiceApi = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [isSecretKeyModalVisible, setIsSecretKeyModalVisible] = useState(false)
|
const [isSecretKeyModalVisible, setIsSecretKeyModalVisible] = useState(false)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const canManageSecretKey = hasPermission(workspacePermissionKeys, 'dataset.api_key.manage')
|
const canManageSecretKey = hasPermission(workspacePermissionKeys, 'dataset.api_key.manage')
|
||||||
|
|
||||||
const handleOpenSecretKeyModal = useCallback(() => {
|
const handleOpenSecretKeyModal = useCallback(() => {
|
||||||
|
|||||||
@ -83,6 +83,12 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState),
|
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState)
|
||||||
|
})
|
||||||
|
|
||||||
const mockRecordsRefetch = vi.fn()
|
const mockRecordsRefetch = vi.fn()
|
||||||
const mockHitTestingMutateAsync = vi.fn()
|
const mockHitTestingMutateAsync = vi.fn()
|
||||||
const mockExternalHitTestingMutateAsync = vi.fn()
|
const mockExternalHitTestingMutateAsync = vi.fn()
|
||||||
@ -101,6 +107,12 @@ vi.mock('@/service/knowledge/use-dataset', () => ({
|
|||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('@/service/knowledge/use-hit-testing', () => ({
|
vi.mock('@/service/knowledge/use-hit-testing', () => ({
|
||||||
useHitTesting: vi.fn(() => ({
|
useHitTesting: vi.fn(() => ({
|
||||||
mutateAsync: mockHitTestingMutateAsync,
|
mutateAsync: mockHitTestingMutateAsync,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
} from '@langgenius/dify-ui/drawer'
|
} from '@langgenius/dify-ui/drawer'
|
||||||
import { Pagination } from '@langgenius/dify-ui/pagination'
|
import { Pagination } from '@langgenius/dify-ui/pagination'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -27,7 +28,10 @@ import { useContext } from 'use-context-selector'
|
|||||||
import FloatRightContainer from '@/app/components/base/float-right-container'
|
import FloatRightContainer from '@/app/components/base/float-right-container'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import docStyle from '@/app/components/datasets/documents/detail/completed/style.module.css'
|
import docStyle from '@/app/components/datasets/documents/detail/completed/style.module.css'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import DatasetDetailContext from '@/context/dataset-detail'
|
import DatasetDetailContext from '@/context/dataset-detail'
|
||||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||||
import { useDatasetTestingRecords } from '@/service/knowledge/use-dataset'
|
import { useDatasetTestingRecords } from '@/service/knowledge/use-dataset'
|
||||||
@ -63,13 +67,13 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
|
|||||||
|
|
||||||
const [currPage, setCurrPage] = useState<number>(0)
|
const [currPage, setCurrPage] = useState<number>(0)
|
||||||
const { dataset: currentDataset } = useContext(DatasetDetailContext)
|
const { dataset: currentDataset } = useContext(DatasetDetailContext)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const canRunRetrievalRecall = React.useMemo(() => getDatasetACLCapabilities(currentDataset?.permission_keys, {
|
const canRunRetrievalRecall = getDatasetACLCapabilities(currentDataset?.permission_keys, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
resourceMaintainer: currentDataset?.maintainer,
|
resourceMaintainer: currentDataset?.maintainer,
|
||||||
workspacePermissionKeys,
|
workspacePermissionKeys,
|
||||||
}).canRetrievalRecall, [currentDataset?.maintainer, currentDataset?.permission_keys, currentUserId, workspacePermissionKeys])
|
}).canRetrievalRecall
|
||||||
const { data: recordsRes, refetch: recordsRefetch, isLoading: isRecordsLoading } = useDatasetTestingRecords(datasetId, { limit, page: currPage + 1 }, { enabled: canRunRetrievalRecall })
|
const { data: recordsRes, refetch: recordsRefetch, isLoading: isRecordsLoading } = useDatasetTestingRecords(datasetId, { limit, page: currPage + 1 }, { enabled: canRunRetrievalRecall })
|
||||||
|
|
||||||
const total = recordsRes?.total || 0
|
const total = recordsRes?.total || 0
|
||||||
|
|||||||
@ -8,6 +8,7 @@ const mockReplace = vi.fn()
|
|||||||
let mockAppContextState = {
|
let mockAppContextState = {
|
||||||
isCurrentWorkspaceEditor: true,
|
isCurrentWorkspaceEditor: true,
|
||||||
isCurrentWorkspaceManager: true,
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: true,
|
||||||
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||||
}
|
}
|
||||||
let mockIsCurrentWorkspaceOwner = true
|
let mockIsCurrentWorkspaceOwner = true
|
||||||
@ -27,6 +28,12 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState),
|
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState)
|
||||||
|
})
|
||||||
|
|
||||||
// Mock external api panel context
|
// Mock external api panel context
|
||||||
const mockSetShowExternalApiPanel = vi.fn()
|
const mockSetShowExternalApiPanel = vi.fn()
|
||||||
vi.mock('@/context/external-api-panel-context', () => ({
|
vi.mock('@/context/external-api-panel-context', () => ({
|
||||||
@ -36,6 +43,12 @@ vi.mock('@/context/external-api-panel-context', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
// Mock useDocumentTitle hook
|
// Mock useDocumentTitle hook
|
||||||
vi.mock('@/hooks/use-document-title', () => ({
|
vi.mock('@/hooks/use-document-title', () => ({
|
||||||
default: vi.fn(),
|
default: vi.fn(),
|
||||||
@ -132,6 +145,7 @@ describe('List', () => {
|
|||||||
mockAppContextState = {
|
mockAppContextState = {
|
||||||
isCurrentWorkspaceEditor: true,
|
isCurrentWorkspaceEditor: true,
|
||||||
isCurrentWorkspaceManager: true,
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: true,
|
||||||
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||||
}
|
}
|
||||||
mockIsCurrentWorkspaceOwner = true
|
mockIsCurrentWorkspaceOwner = true
|
||||||
@ -170,6 +184,7 @@ describe('List', () => {
|
|||||||
mockAppContextState = {
|
mockAppContextState = {
|
||||||
isCurrentWorkspaceEditor: true,
|
isCurrentWorkspaceEditor: true,
|
||||||
isCurrentWorkspaceManager: true,
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: true,
|
||||||
workspacePermissionKeys: ['dataset.create_and_management'],
|
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,6 +297,7 @@ describe('List', () => {
|
|||||||
mockAppContextState = {
|
mockAppContextState = {
|
||||||
isCurrentWorkspaceEditor: false,
|
isCurrentWorkspaceEditor: false,
|
||||||
isCurrentWorkspaceManager: true,
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: true,
|
||||||
workspacePermissionKeys: ['dataset.create_and_management'],
|
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||||
}
|
}
|
||||||
const { useDatasetList } = await import('@/service/knowledge/use-dataset')
|
const { useDatasetList } = await import('@/service/knowledge/use-dataset')
|
||||||
@ -303,6 +319,7 @@ describe('List', () => {
|
|||||||
mockAppContextState = {
|
mockAppContextState = {
|
||||||
isCurrentWorkspaceEditor: true,
|
isCurrentWorkspaceEditor: true,
|
||||||
isCurrentWorkspaceManager: true,
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: true,
|
||||||
workspacePermissionKeys: [],
|
workspacePermissionKeys: [],
|
||||||
}
|
}
|
||||||
const { useDatasetList } = await import('@/service/knowledge/use-dataset')
|
const { useDatasetList } = await import('@/service/knowledge/use-dataset')
|
||||||
@ -367,6 +384,7 @@ describe('List', () => {
|
|||||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
||||||
isCurrentWorkspaceEditor: false,
|
isCurrentWorkspaceEditor: false,
|
||||||
isCurrentWorkspaceManager: false,
|
isCurrentWorkspaceManager: false,
|
||||||
|
isCurrentWorkspaceOwner: false,
|
||||||
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
@ -417,6 +435,12 @@ describe('List', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not show ExternalAPIPanel without dataset.external.connect even when panel state is open', async () => {
|
it('should not show ExternalAPIPanel without dataset.external.connect even when panel state is open', async () => {
|
||||||
|
mockAppContextState = {
|
||||||
|
isCurrentWorkspaceEditor: true,
|
||||||
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: true,
|
||||||
|
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||||
|
}
|
||||||
vi.doMock('@/context/app-context', () => ({
|
vi.doMock('@/context/app-context', () => ({
|
||||||
useAppContext: () => ({
|
useAppContext: () => ({
|
||||||
currentWorkspace: { role: 'admin' },
|
currentWorkspace: { role: 'admin' },
|
||||||
@ -425,6 +449,7 @@ describe('List', () => {
|
|||||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
||||||
isCurrentWorkspaceEditor: true,
|
isCurrentWorkspaceEditor: true,
|
||||||
isCurrentWorkspaceManager: true,
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: true,
|
||||||
workspacePermissionKeys: ['dataset.create_and_management'],
|
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
@ -452,6 +477,7 @@ describe('List', () => {
|
|||||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
||||||
isCurrentWorkspaceEditor: true,
|
isCurrentWorkspaceEditor: true,
|
||||||
isCurrentWorkspaceManager: true,
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: true,
|
||||||
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
@ -481,6 +507,12 @@ describe('List', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should not show include all checkbox when not workspace owner', async () => {
|
it('should not show include all checkbox when not workspace owner', async () => {
|
||||||
|
mockAppContextState = {
|
||||||
|
isCurrentWorkspaceEditor: true,
|
||||||
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: false,
|
||||||
|
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||||
|
}
|
||||||
vi.doMock('@/context/app-context', () => ({
|
vi.doMock('@/context/app-context', () => ({
|
||||||
useAppContext: () => ({
|
useAppContext: () => ({
|
||||||
currentWorkspace: { role: 'editor' },
|
currentWorkspace: { role: 'editor' },
|
||||||
@ -489,6 +521,7 @@ describe('List', () => {
|
|||||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
||||||
isCurrentWorkspaceEditor: true,
|
isCurrentWorkspaceEditor: true,
|
||||||
isCurrentWorkspaceManager: true,
|
isCurrentWorkspaceManager: true,
|
||||||
|
isCurrentWorkspaceOwner: false,
|
||||||
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -53,6 +53,12 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState),
|
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState)
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('../hooks/use-dataset-card-state', () => ({
|
vi.mock('../hooks/use-dataset-card-state', () => ({
|
||||||
useDatasetCardState: () => ({
|
useDatasetCardState: () => ({
|
||||||
modalState: {
|
modalState: {
|
||||||
@ -72,6 +78,12 @@ vi.mock('../hooks/use-dataset-card-state', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
vi.mock('../components/corner-labels', () => ({
|
vi.mock('../components/corner-labels', () => ({
|
||||||
default: () => <div data-testid="corner-labels" />,
|
default: () => <div data-testid="corner-labels" />,
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -24,6 +24,20 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
useSelector: vi.fn((selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState)),
|
useSelector: vi.fn((selector: (state: typeof mockAppContextState) => unknown) => selector(mockAppContextState)),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState, () => ({
|
||||||
|
isRbacEnabled: mockIsRbacEnabled,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
describe('OperationsDropdown', () => {
|
describe('OperationsDropdown', () => {
|
||||||
const createMockDataset = (overrides: Partial<DataSet> = {}): DataSet => ({
|
const createMockDataset = (overrides: Partial<DataSet> = {}): DataSet => ({
|
||||||
id: 'dataset-1',
|
id: 'dataset-1',
|
||||||
|
|||||||
@ -5,10 +5,13 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@langgenius/dify-ui/dropdown-menu'
|
} from '@langgenius/dify-ui/dropdown-menu'
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
import { useAtomValue } from 'jotai'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
datasetRbacEnabledAtom,
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { getDatasetACLCapabilities } from '@/utils/permission'
|
import { getDatasetACLCapabilities } from '@/utils/permission'
|
||||||
import Operations from '../operations'
|
import Operations from '../operations'
|
||||||
|
|
||||||
@ -28,10 +31,9 @@ const OperationsDropdown = ({
|
|||||||
openAccessConfig,
|
openAccessConfig,
|
||||||
}: OperationsDropdownProps) => {
|
}: OperationsDropdownProps) => {
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
const isRbacEnabled = useAtomValue(datasetRbacEnabledAtom)
|
||||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
|
||||||
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(dataset.permission_keys, {
|
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(dataset.permission_keys, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
resourceMaintainer: dataset.maintainer,
|
resourceMaintainer: dataset.maintainer,
|
||||||
|
|||||||
@ -3,9 +3,13 @@ import type { KeyboardEvent, MouseEvent } from 'react'
|
|||||||
import type { DataSet } from '@/models/datasets'
|
import type { DataSet } from '@/models/datasets'
|
||||||
import { cn } from '@langgenius/dify-ui/cn'
|
import { cn } from '@langgenius/dify-ui/cn'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { DatasetCardTags } from '@/features/tag-management/components/dataset-card-tags'
|
import { DatasetCardTags } from '@/features/tag-management/components/dataset-card-tags'
|
||||||
import { useRouter } from '@/next/navigation'
|
import { useRouter } from '@/next/navigation'
|
||||||
import { getDatasetACLCapabilities, hasOnlyDatasetPreviewPermission, hasPermission } from '@/utils/permission'
|
import { getDatasetACLCapabilities, hasOnlyDatasetPreviewPermission, hasPermission } from '@/utils/permission'
|
||||||
@ -32,8 +36,8 @@ const DatasetCard = ({
|
|||||||
}: DatasetCardProps) => {
|
}: DatasetCardProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
|
|
||||||
const datasetCard = useDatasetCardController({ dataset, onSuccess })
|
const datasetCard = useDatasetCardController({ dataset, onSuccess })
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useBoolean, useDebounceFn } from 'ahooks'
|
import { useBoolean, useDebounceFn } from 'ahooks'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
// Libraries
|
// Libraries
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useAppContext, useSelector as useAppContextSelector } from '@/context/app-context'
|
import {
|
||||||
|
isCurrentWorkspaceOwnerAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
||||||
import { TagManagementModal } from '@/features/tag-management/components/tag-management-modal'
|
import { TagManagementModal } from '@/features/tag-management/components/tag-management-modal'
|
||||||
import useDocumentTitle from '@/hooks/use-document-title'
|
import useDocumentTitle from '@/hooks/use-document-title'
|
||||||
@ -22,7 +26,7 @@ import DatasetListHeader from './header'
|
|||||||
const List = () => {
|
const List = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
const { isCurrentWorkspaceOwner } = useAppContext()
|
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
||||||
const [showTagManagementModal, setShowTagManagementModal] = useState(false)
|
const [showTagManagementModal, setShowTagManagementModal] = useState(false)
|
||||||
const { showExternalApiPanel, setShowExternalApiPanel } = useExternalApiPanel()
|
const { showExternalApiPanel, setShowExternalApiPanel } = useExternalApiPanel()
|
||||||
const [includeAll, { toggle: toggleIncludeAll }] = useBoolean(false)
|
const [includeAll, { toggle: toggleIncludeAll }] = useBoolean(false)
|
||||||
@ -48,7 +52,7 @@ const List = () => {
|
|||||||
handleTagsUpdate()
|
handleTagsUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspacePermissionKeys = useAppContextSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const canCreateDataset = hasPermission(workspacePermissionKeys, 'dataset.create_and_management')
|
const canCreateDataset = hasPermission(workspacePermissionKeys, 'dataset.create_and_management')
|
||||||
const canConnectExternalDataset = hasPermission(workspacePermissionKeys, 'dataset.external.connect')
|
const canConnectExternalDataset = hasPermission(workspacePermissionKeys, 'dataset.external.connect')
|
||||||
const { data: apiBaseInfo } = useDatasetApiBaseUrl()
|
const { data: apiBaseInfo } = useDatasetApiBaseUrl()
|
||||||
|
|||||||
@ -47,6 +47,17 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => ({
|
||||||
|
userProfile: mockUserProfile,
|
||||||
|
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||||
|
}), () => ({
|
||||||
|
isRbacEnabled: false,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
const createMockDataset = (overrides: Partial<DataSet> = {}): DataSet => ({
|
const createMockDataset = (overrides: Partial<DataSet> = {}): DataSet => ({
|
||||||
id: 'dataset-1',
|
id: 'dataset-1',
|
||||||
name: 'Test Dataset',
|
name: 'Test Dataset',
|
||||||
@ -129,6 +140,12 @@ vi.mock('@/context/dataset-detail', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
vi.mock('@/service/datasets', () => ({
|
vi.mock('@/service/datasets', () => ({
|
||||||
updateDatasetSetting: vi.fn().mockResolvedValue({}),
|
updateDatasetSetting: vi.fn().mockResolvedValue({}),
|
||||||
|
|||||||
@ -19,17 +19,29 @@ vi.mock('@tanstack/react-query', async (importOriginal) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mock app-context
|
const mockAppContextState = vi.hoisted(() => ({
|
||||||
vi.mock('@/context/app-context', () => ({
|
userProfile: {
|
||||||
useSelector: () => ({
|
|
||||||
id: 'user-1',
|
id: 'user-1',
|
||||||
name: 'Current User',
|
name: 'Current User',
|
||||||
email: 'current@example.com',
|
email: 'current@example.com',
|
||||||
avatar_url: '',
|
avatar_url: '',
|
||||||
role: 'owner',
|
role: 'owner',
|
||||||
}),
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Mock app-context
|
||||||
|
vi.mock('@/context/app-context', () => ({
|
||||||
|
useSelector: () => mockAppContextState.userProfile,
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState, () => ({
|
||||||
|
isRbacEnabled: false,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
// Mock image uploader hooks for AppIconPicker
|
// Mock image uploader hooks for AppIconPicker
|
||||||
vi.mock('@/app/components/base/image-uploader/hooks', () => ({
|
vi.mock('@/app/components/base/image-uploader/hooks', () => ({
|
||||||
useLocalFileUploader: () => ({
|
useLocalFileUploader: () => ({
|
||||||
@ -47,6 +59,12 @@ vi.mock('@/app/components/base/image-uploader/hooks', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
describe('BasicInfoSection', () => {
|
describe('BasicInfoSection', () => {
|
||||||
const mockDataset: DataSet = {
|
const mockDataset: DataSet = {
|
||||||
id: 'dataset-1',
|
id: 'dataset-1',
|
||||||
|
|||||||
@ -23,6 +23,15 @@ vi.mock('@/context/app-context', () => ({
|
|||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => ({
|
||||||
|
userProfile: { id: 'user-1' },
|
||||||
|
workspacePermissionKeys: [],
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
const createDefaultMockDataset = (): DataSet => ({
|
const createDefaultMockDataset = (): DataSet => ({
|
||||||
id: 'dataset-1',
|
id: 'dataset-1',
|
||||||
name: 'Test Dataset',
|
name: 'Test Dataset',
|
||||||
@ -104,6 +113,12 @@ vi.mock('@/context/dataset-detail', () => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
vi.mock('@/service/datasets', () => ({
|
vi.mock('@/service/datasets', () => ({
|
||||||
updateDatasetSetting: vi.fn().mockResolvedValue({}),
|
updateDatasetSetting: vi.fn().mockResolvedValue({}),
|
||||||
|
|||||||
@ -5,12 +5,16 @@ import type { Member } from '@/models/common'
|
|||||||
import type { IconInfo, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
|
import type { IconInfo, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
|
||||||
import type { RetrievalConfig } from '@/types/app'
|
import type { RetrievalConfig } from '@/types/app'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
|
userProfileIdAtom,
|
||||||
|
workspacePermissionKeysAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||||
import { DatasetPermission } from '@/models/datasets'
|
import { DatasetPermission } from '@/models/datasets'
|
||||||
import { updateDatasetSetting } from '@/service/datasets'
|
import { updateDatasetSetting } from '@/service/datasets'
|
||||||
@ -30,8 +34,8 @@ export const useFormState = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const currentDataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
const currentDataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||||
const mutateDatasets = useDatasetDetailContextWithSelector(state => state.mutateDatasetRes)
|
const mutateDatasets = useDatasetDetailContextWithSelector(state => state.mutateDatasetRes)
|
||||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const datasetACLCapabilities = useMemo(
|
const datasetACLCapabilities = useMemo(
|
||||||
() => getDatasetACLCapabilities(currentDataset?.permission_keys, {
|
() => getDatasetACLCapabilities(currentDataset?.permission_keys, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
|||||||
@ -4,17 +4,32 @@ import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features
|
|||||||
import { DatasetPermission } from '@/models/datasets'
|
import { DatasetPermission } from '@/models/datasets'
|
||||||
import PermissionSelector from '../index'
|
import PermissionSelector from '../index'
|
||||||
|
|
||||||
// Mock app-context
|
const mockAppContextState = vi.hoisted(() => ({
|
||||||
vi.mock('@/context/app-context', () => ({
|
userProfile: {
|
||||||
useSelector: () => ({
|
|
||||||
id: 'user-1',
|
id: 'user-1',
|
||||||
name: 'Current User',
|
name: 'Current User',
|
||||||
email: 'current@example.com',
|
email: 'current@example.com',
|
||||||
avatar_url: '',
|
avatar_url: '',
|
||||||
role: 'owner',
|
role: 'owner',
|
||||||
}),
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
let mockIsRbacEnabled = false
|
||||||
|
|
||||||
|
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessAtomMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessAtomMock(importOriginal, () => mockAppContextState, () => ({
|
||||||
|
isRbacEnabled: mockIsRbacEnabled,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock('jotai', async (importOriginal) => {
|
||||||
|
const { createDatasetAccessJotaiMock } = await import('@/app/components/datasets/__tests__/mock-dataset-access')
|
||||||
|
|
||||||
|
return createDatasetAccessJotaiMock(importOriginal)
|
||||||
|
})
|
||||||
|
|
||||||
describe('PermissionSelector', () => {
|
describe('PermissionSelector', () => {
|
||||||
const mockMemberList: Member[] = [
|
const mockMemberList: Member[] = [
|
||||||
{ id: 'user-1', name: 'Current User', email: 'current@example.com', avatar: '', avatar_url: '', role: 'owner', roles: [], last_login_at: '', created_at: '', status: 'active' }!,
|
{ id: 'user-1', name: 'Current User', email: 'current@example.com', avatar: '', avatar_url: '', role: 'owner', roles: [], last_login_at: '', created_at: '', status: 'active' }!,
|
||||||
@ -33,6 +48,7 @@ describe('PermissionSelector', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
mockIsRbacEnabled = false
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Rendering', () => {
|
describe('Rendering', () => {
|
||||||
@ -409,6 +425,8 @@ describe('PermissionSelector', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should show access config hint and remain closed when RBAC is enabled', () => {
|
it('should show access config hint and remain closed when RBAC is enabled', () => {
|
||||||
|
mockIsRbacEnabled = true
|
||||||
|
|
||||||
renderWithSystemFeatures(<PermissionSelector {...defaultProps} />, {
|
renderWithSystemFeatures(<PermissionSelector {...defaultProps} />, {
|
||||||
systemFeatures: {
|
systemFeatures: {
|
||||||
rbac_enabled: true,
|
rbac_enabled: true,
|
||||||
|
|||||||
@ -7,12 +7,14 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@langgenius/dify-ui/popover'
|
} from '@langgenius/dify-ui/popover'
|
||||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
|
||||||
import { useDebounceFn } from 'ahooks'
|
import { useDebounceFn } from 'ahooks'
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
import {
|
||||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
datasetRbacEnabledAtom,
|
||||||
|
userProfileAtom,
|
||||||
|
} from '@/context/app-context-state'
|
||||||
import { DatasetPermission } from '@/models/datasets'
|
import { DatasetPermission } from '@/models/datasets'
|
||||||
import MemberItem from './member-item'
|
import MemberItem from './member-item'
|
||||||
import Item from './permission-item'
|
import Item from './permission-item'
|
||||||
@ -35,8 +37,8 @@ const PermissionSelector = ({
|
|||||||
onMemberSelect,
|
onMemberSelect,
|
||||||
}: RoleSelectorProps) => {
|
}: RoleSelectorProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const userProfile = useAppContextWithSelector(state => state.userProfile)
|
const userProfile = useAtomValue(userProfileAtom)
|
||||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
const isRbacEnabled = useAtomValue(datasetRbacEnabledAtom)
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
const [keywords, setKeywords] = useState('')
|
const [keywords, setKeywords] = useState('')
|
||||||
@ -89,7 +91,7 @@ const PermissionSelector = ({
|
|||||||
const isAllTeamMembers = permission === DatasetPermission.allTeamMembers
|
const isAllTeamMembers = permission === DatasetPermission.allTeamMembers
|
||||||
const isPartialMembers = permission === DatasetPermission.partialMembers
|
const isPartialMembers = permission === DatasetPermission.partialMembers
|
||||||
const selectedMemberNames = selectedMembers.map(member => member.name).join(', ')
|
const selectedMemberNames = selectedMembers.map(member => member.name).join(', ')
|
||||||
const isDisabledByRBAC = systemFeatures.rbac_enabled
|
const isDisabledByRBAC = isRbacEnabled
|
||||||
const isDisabled = disabled || isDisabledByRBAC
|
const isDisabled = disabled || isDisabledByRBAC
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
34
web/context/app-context-defaults.ts
Normal file
34
web/context/app-context-defaults.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen'
|
||||||
|
import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/common'
|
||||||
|
|
||||||
|
export const userProfilePlaceholder: GetAccountProfileResponse = {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
avatar: '',
|
||||||
|
avatar_url: '',
|
||||||
|
is_password_set: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialLangGeniusVersionInfo: LangGeniusVersionResponse = {
|
||||||
|
current_env: '',
|
||||||
|
current_version: '',
|
||||||
|
latest_version: '',
|
||||||
|
release_date: '',
|
||||||
|
release_notes: '',
|
||||||
|
version: '',
|
||||||
|
can_auto_update: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialWorkspaceInfo: ICurrentWorkspace = {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
plan: '',
|
||||||
|
status: '',
|
||||||
|
created_at: 0,
|
||||||
|
role: 'normal',
|
||||||
|
providers: [],
|
||||||
|
trial_credits: 200,
|
||||||
|
trial_credits_used: 0,
|
||||||
|
next_credit_reset_date: 0,
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ import { atom } from 'jotai'
|
|||||||
import { atomWithQuery, atomWithSuspenseQuery, queryClientAtom } from 'jotai-tanstack-query'
|
import { atomWithQuery, atomWithSuspenseQuery, queryClientAtom } from 'jotai-tanstack-query'
|
||||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||||
|
import { defaultSystemFeatures } from '@/features/system-features/config'
|
||||||
import { workspacePermissionKeysQueryOptions } from '@/service/access-control/use-permission-keys'
|
import { workspacePermissionKeysQueryOptions } from '@/service/access-control/use-permission-keys'
|
||||||
import { consoleQuery } from '@/service/client'
|
import { consoleQuery } from '@/service/client'
|
||||||
import { langGeniusVersionQueryOptions } from '@/service/lang-genius-version'
|
import { langGeniusVersionQueryOptions } from '@/service/lang-genius-version'
|
||||||
@ -15,7 +16,7 @@ import {
|
|||||||
initialLangGeniusVersionInfo,
|
initialLangGeniusVersionInfo,
|
||||||
initialWorkspaceInfo,
|
initialWorkspaceInfo,
|
||||||
userProfilePlaceholder,
|
userProfilePlaceholder,
|
||||||
} from './app-context'
|
} from './app-context-defaults'
|
||||||
import {
|
import {
|
||||||
emptyWorkspacePermissionKeys,
|
emptyWorkspacePermissionKeys,
|
||||||
getLangGeniusVersionInfo,
|
getLangGeniusVersionInfo,
|
||||||
@ -29,12 +30,22 @@ const accountProfileQueryAtom = atomWithSuspenseQuery(() => userProfileQueryOpti
|
|||||||
|
|
||||||
const systemFeaturesQueryAtom = atomWithSuspenseQuery(() => systemFeaturesQueryOptions())
|
const systemFeaturesQueryAtom = atomWithSuspenseQuery(() => systemFeaturesQueryOptions())
|
||||||
|
|
||||||
|
const systemFeaturesAtom = atom((get): GetSystemFeaturesResponse => {
|
||||||
|
const systemFeaturesQuery = get(systemFeaturesQueryAtom) as SuspenseQueryResult<GetSystemFeaturesResponse>
|
||||||
|
|
||||||
|
return systemFeaturesQuery.data ?? defaultSystemFeatures
|
||||||
|
})
|
||||||
|
|
||||||
export const userProfileAtom = atom((get): GetAccountProfileResponse => {
|
export const userProfileAtom = atom((get): GetAccountProfileResponse => {
|
||||||
const accountProfileQuery = get(accountProfileQueryAtom) as SuspenseQueryResult<UserProfileWithMeta>
|
const accountProfileQuery = get(accountProfileQueryAtom) as SuspenseQueryResult<UserProfileWithMeta>
|
||||||
|
|
||||||
return accountProfileQuery.data?.profile || userProfilePlaceholder
|
return accountProfileQuery.data?.profile || userProfilePlaceholder
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const userProfileIdAtom = atom((get) => {
|
||||||
|
return get(userProfileAtom).id
|
||||||
|
})
|
||||||
|
|
||||||
const profileMetaAtom = atom((get) => {
|
const profileMetaAtom = atom((get) => {
|
||||||
const accountProfileQuery = get(accountProfileQueryAtom) as SuspenseQueryResult<UserProfileWithMeta>
|
const accountProfileQuery = get(accountProfileQueryAtom) as SuspenseQueryResult<UserProfileWithMeta>
|
||||||
|
|
||||||
@ -58,18 +69,26 @@ export const currentWorkspaceAtom = atom((get) => {
|
|||||||
return get(normalizedCurrentWorkspaceAtom)
|
return get(normalizedCurrentWorkspaceAtom)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const currentWorkspaceIdAtom = atom((get) => {
|
||||||
|
return get(currentWorkspaceAtom).id
|
||||||
|
})
|
||||||
|
|
||||||
export const workspaceRoleFlagsAtom = atom((get) => {
|
export const workspaceRoleFlagsAtom = atom((get) => {
|
||||||
return getWorkspaceRoleFlags(get(currentWorkspaceAtom))
|
return getWorkspaceRoleFlags(get(currentWorkspaceAtom))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const isCurrentWorkspaceOwnerAtom = atom((get) => {
|
||||||
|
return get(workspaceRoleFlagsAtom).isCurrentWorkspaceOwner
|
||||||
|
})
|
||||||
|
|
||||||
const workspacePermissionKeysQueryAtom = atomWithQuery((get) => {
|
const workspacePermissionKeysQueryAtom = atomWithQuery((get) => {
|
||||||
const workspaceId = get(currentWorkspaceAtom).id
|
const workspaceId = get(currentWorkspaceIdAtom)
|
||||||
|
|
||||||
return workspacePermissionKeysQueryOptions(workspaceId)
|
return workspacePermissionKeysQueryOptions(workspaceId)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const workspacePermissionKeysAtom = atom((get) => {
|
export const workspacePermissionKeysAtom = atom((get) => {
|
||||||
return get(workspacePermissionKeysQueryAtom).data?.workspace.permission_keys ?? emptyWorkspacePermissionKeys
|
return get(workspacePermissionKeysQueryAtom).data?.workspace?.permission_keys ?? emptyWorkspacePermissionKeys
|
||||||
})
|
})
|
||||||
|
|
||||||
export const workspacePermissionKeysLoadingAtom = atom((get) => {
|
export const workspacePermissionKeysLoadingAtom = atom((get) => {
|
||||||
@ -80,6 +99,10 @@ export const currentWorkspaceLoadingAtom = atom((get) => {
|
|||||||
return get(currentWorkspaceQueryAtom).isPending
|
return get(currentWorkspaceQueryAtom).isPending
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const datasetRbacEnabledAtom = atom((get) => {
|
||||||
|
return get(systemFeaturesAtom).rbac_enabled
|
||||||
|
})
|
||||||
|
|
||||||
export const currentWorkspaceValidatingAtom = atom((get) => {
|
export const currentWorkspaceValidatingAtom = atom((get) => {
|
||||||
return get(currentWorkspaceQueryAtom).isFetching
|
return get(currentWorkspaceQueryAtom).isFetching
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,6 +4,11 @@ import type { GetAccountProfileResponse } from '@dify/contracts/api/console/acco
|
|||||||
import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/common'
|
import type { ICurrentWorkspace, LangGeniusVersionResponse } from '@/models/common'
|
||||||
import { noop } from 'es-toolkit/function'
|
import { noop } from 'es-toolkit/function'
|
||||||
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
||||||
|
import {
|
||||||
|
initialLangGeniusVersionInfo as defaultLangGeniusVersionInfo,
|
||||||
|
userProfilePlaceholder as defaultUserProfilePlaceholder,
|
||||||
|
initialWorkspaceInfo as defaultWorkspaceInfo,
|
||||||
|
} from './app-context-defaults'
|
||||||
|
|
||||||
export type AppContextValue = {
|
export type AppContextValue = {
|
||||||
userProfile: GetAccountProfileResponse
|
userProfile: GetAccountProfileResponse
|
||||||
@ -22,37 +27,11 @@ export type AppContextValue = {
|
|||||||
workspacePermissionKeys: string[]
|
workspacePermissionKeys: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const userProfilePlaceholder = {
|
export const userProfilePlaceholder = defaultUserProfilePlaceholder
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
email: '',
|
|
||||||
avatar: '',
|
|
||||||
avatar_url: '',
|
|
||||||
is_password_set: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initialLangGeniusVersionInfo = {
|
export const initialLangGeniusVersionInfo = defaultLangGeniusVersionInfo
|
||||||
current_env: '',
|
|
||||||
current_version: '',
|
|
||||||
latest_version: '',
|
|
||||||
release_date: '',
|
|
||||||
release_notes: '',
|
|
||||||
version: '',
|
|
||||||
can_auto_update: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initialWorkspaceInfo: ICurrentWorkspace = {
|
export const initialWorkspaceInfo = defaultWorkspaceInfo
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
plan: '',
|
|
||||||
status: '',
|
|
||||||
created_at: 0,
|
|
||||||
role: 'normal',
|
|
||||||
providers: [],
|
|
||||||
trial_credits: 200,
|
|
||||||
trial_credits_used: 0,
|
|
||||||
next_credit_reset_date: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AppContext = createContext<AppContextValue>({
|
export const AppContext = createContext<AppContextValue>({
|
||||||
userProfile: userProfilePlaceholder,
|
userProfile: userProfilePlaceholder,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user