mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 10:38:32 +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' },
|
||||
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', () => ({
|
||||
useEventEmitterContextContext: () => ({
|
||||
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', () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
@ -2,14 +2,19 @@
|
||||
import type { FC } from 'react'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { usePathname, useRouter } from '@/next/navigation'
|
||||
import { useDatasetDetail } from '@/service/knowledge/use-dataset'
|
||||
@ -58,23 +63,20 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const {
|
||||
isLoadingCurrentWorkspace,
|
||||
isLoadingWorkspacePermissionKeys,
|
||||
userProfile,
|
||||
workspacePermissionKeys,
|
||||
} = useAppContext()
|
||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const isRbacEnabled = useAtomValue(datasetRbacEnabledAtom)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
const { data: datasetRes, error, refetch: mutateDatasetRes } = useDatasetDetail(datasetId)
|
||||
const shouldRedirect = shouldRedirectToDatasetList(error)
|
||||
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(datasetRes?.permission_keys, {
|
||||
currentUserId: userProfile?.id,
|
||||
currentUserId,
|
||||
resourceMaintainer: datasetRes?.maintainer,
|
||||
workspacePermissionKeys,
|
||||
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 isHitTestingPath = pathname.endsWith('/hitTesting')
|
||||
const isPermissionControlledPath = isAccessConfigPath || isHitTestingPath
|
||||
|
||||
@ -20,10 +20,22 @@ vi.mock('@/context/app-context', () => ({
|
||||
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', () => ({
|
||||
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', () => ({
|
||||
ExternalKnowledgeApiProvider: ({ children, enabled }: { children: ReactNode, enabled?: boolean }) => {
|
||||
mockExternalKnowledgeApiProviderEnabled = enabled
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useEffect } from 'react'
|
||||
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 { ExternalKnowledgeApiProvider } from '@/context/external-knowledge-api-context'
|
||||
import { usePathname, useRouter } from '@/next/navigation'
|
||||
@ -21,10 +27,10 @@ const isDatasetExternalConnectPath = (pathname: string) => {
|
||||
}
|
||||
|
||||
export default function DatasetsLayout({ children }: { children: React.ReactNode }) {
|
||||
const currentWorkspaceId = useAppContextSelector(state => state.currentWorkspace.id)
|
||||
const isLoadingCurrentWorkspace = useAppContextSelector(state => state.isLoadingCurrentWorkspace)
|
||||
const isLoadingWorkspacePermissionKeys = useAppContextSelector(state => state.isLoadingWorkspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAppContextSelector(state => state.workspacePermissionKeys)
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
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)),
|
||||
}))
|
||||
|
||||
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', () => ({
|
||||
default: (props: AccessRulesEditorProps) => {
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
@ -2,15 +2,18 @@
|
||||
|
||||
import type { ResourceOpenScope } from '@/models/access-control'
|
||||
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 { useTranslation } from 'react-i18next'
|
||||
import AccessRulesEditor from '@/app/components/access-rules-editor'
|
||||
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 { useLocale } from '@/context/i18n'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { getAccessControlTemplateLanguage } from '@/i18n-config/language'
|
||||
import {
|
||||
useDatasetAccessRules,
|
||||
@ -30,16 +33,15 @@ const DatasetAccessConfigPage = ({ datasetId }: DatasetAccessConfigPageProps) =>
|
||||
const locale = useLocale()
|
||||
const language = useMemo(() => getAccessControlTemplateLanguage(locale), [locale])
|
||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||
const canAccessConfig = useMemo(() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const isRbacEnabled = useAtomValue(datasetRbacEnabledAtom)
|
||||
const canAccessConfig = getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||
currentUserId,
|
||||
resourceMaintainer: dataset?.maintainer,
|
||||
workspacePermissionKeys,
|
||||
isRbacEnabled,
|
||||
}).canAccessConfig, [currentUserId, dataset?.maintainer, dataset?.permission_keys, isRbacEnabled, workspacePermissionKeys])
|
||||
}).canAccessConfig
|
||||
const { data: datasetAccessRulesResponse, isLoading: isLoadingDatasetAccessRules } = useDatasetAccessRules(datasetId, language, { enabled: canAccessConfig })
|
||||
const { data: datasetUserAccessSettingsResponse, isLoading: isLoadingDatasetUserAccessSettings } = useDatasetUserAccessSettings(datasetId, language, { enabled: canAccessConfig })
|
||||
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
|
||||
const mockSetShowAccountSettingModal = vi.fn()
|
||||
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
|
||||
let mockDatasetDetail: DataSet | undefined
|
||||
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 { RETRIEVE_METHOD } from '@/types/app'
|
||||
import { produce } from 'immer'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
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 { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
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 { DataSourceProvider } from '@/models/common'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
@ -43,9 +48,9 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
|
||||
const router = useRouter()
|
||||
const openIntegrationsSetting = useIntegrationsSetting()
|
||||
const datasetDetail = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const isLoadingWorkspacePermissionKeys = useAppContextWithSelector(state => state.isLoadingWorkspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
|
||||
const canAddDocumentsToDataset = !datasetId || getDatasetACLCapabilities(datasetDetail?.permission_keys, {
|
||||
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
|
||||
const mockInvalidDocumentList = vi.fn()
|
||||
const mockInvalidDocumentDetail = vi.fn()
|
||||
@ -92,6 +101,12 @@ vi.mock('@/service/knowledge/use-document', () => ({
|
||||
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
|
||||
vi.mock('@/service/knowledge/use-segment', () => ({
|
||||
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', () => ({
|
||||
default: () => ({
|
||||
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({
|
||||
defaultOptions: {
|
||||
queries: { retry: false, gcTime: 0 },
|
||||
|
||||
@ -2,6 +2,7 @@ import type { SimpleDocumentDetail } from '@/models/datasets'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { pick } from 'es-toolkit/object'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
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 SummaryStatus from '@/app/components/datasets/documents/detail/completed/common/summary-status'
|
||||
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 useTimestamp from '@/hooks/use-timestamp'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
@ -62,8 +66,8 @@ const DocumentTableRow = React.memo(({
|
||||
const searchParams = useSearchParams()
|
||||
const documentNameId = React.useId()
|
||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||
currentUserId,
|
||||
resourceMaintainer: dataset?.maintainer,
|
||||
|
||||
@ -4,11 +4,15 @@ import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group'
|
||||
import { Pagination } from '@langgenius/dify-ui/pagination'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import {
|
||||
userProfileIdAtom,
|
||||
workspacePermissionKeysAtom,
|
||||
} from '@/context/app-context-state'
|
||||
import { useDatasetDetailContextWithSelector as useDatasetDetailContext } from '@/context/dataset-detail'
|
||||
import { ChunkingMode, DocumentActionType } from '@/models/datasets'
|
||||
import { getDatasetACLCapabilities } from '@/utils/permission'
|
||||
@ -61,8 +65,8 @@ const DocumentList = ({
|
||||
const pageSize = pagination.limit ?? 10
|
||||
const totalPages = Math.max(Math.ceil(pagination.total / pageSize), 1)
|
||||
const datasetConfig = useDatasetDetailContext(s => s.dataset)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const datasetACLCapabilities = useMemo(() => getDatasetACLCapabilities(datasetConfig?.permission_keys, {
|
||||
currentUserId,
|
||||
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', () => ({
|
||||
useCurrentPlanVectorSpace: () => ({
|
||||
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', () => ({
|
||||
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 } }),
|
||||
|
||||
@ -5,11 +5,16 @@ import type { Node } from '@/app/components/workflow/types'
|
||||
import type { FileIndexingEstimateResponse } from '@/models/datasets'
|
||||
import type { InitialDocumentDetail } from '@/models/pipeline'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
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 { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
@ -40,9 +45,9 @@ const CreateFormPipeline = () => {
|
||||
const enableBilling = useProviderContextSelector(state => state.enableBilling)
|
||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||
const pipelineId = dataset?.pipeline_id
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const isLoadingWorkspacePermissionKeys = useAppContextWithSelector(state => state.isLoadingWorkspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const dataSourceStore = useDataSourceStore()
|
||||
const canAddDocumentsToDataset = getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||
currentUserId,
|
||||
|
||||
@ -4,6 +4,7 @@ import type { DocumentDisplayStatus, FileItem, FullDocumentDetail } from '@/mode
|
||||
import type { SegmentImportStatus } from '@/types/dataset'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
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 Loading from '@/app/components/base/loading'
|
||||
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 useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { ChunkingMode, DisplayStatusList } from '@/models/datasets'
|
||||
@ -49,8 +53,8 @@ const DocumentDetail: FC<DocumentDetailProps> = ({ datasetId, documentId }) => {
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const embeddingAvailable = !!dataset?.embedding_available
|
||||
const datasetACLCapabilities = useMemo(
|
||||
() => getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback } from 'react'
|
||||
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 { useProviderContext } from '@/context/provider-context'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
@ -31,8 +35,8 @@ const Documents: FC<IDocumentsProps> = ({ datasetId }) => {
|
||||
const isFreePlan = plan.type === 'sandbox'
|
||||
|
||||
const dataset = useDatasetDetailContextWithSelector(s => s.dataset)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const embeddingAvailable = !!dataset?.embedding_available
|
||||
const datasetACLCapabilities = getDatasetACLCapabilities(dataset?.permission_keys, {
|
||||
currentUserId,
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import {
|
||||
userProfileIdAtom,
|
||||
workspacePermissionKeysAtom,
|
||||
} from '@/context/app-context-state'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url'
|
||||
import Link from '@/next/link'
|
||||
@ -22,8 +26,8 @@ const Card = ({
|
||||
const datasetId = useDatasetDetailContextWithSelector(state => state.dataset?.id)
|
||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||
const mutateDatasetRes = useDatasetDetailContextWithSelector(state => state.mutateDatasetRes)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { mutateAsync: enableDatasetServiceApi } = useEnableDatasetServiceApi()
|
||||
const { mutateAsync: disableDatasetServiceApi } = useDisableDatasetServiceApi()
|
||||
|
||||
|
||||
@ -15,6 +15,14 @@ vi.mock('@/context/app-context', () => ({
|
||||
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', () => ({
|
||||
useRouter: () => ({
|
||||
push: vi.fn(),
|
||||
@ -24,6 +32,12 @@ vi.mock('@/next/navigation', () => ({
|
||||
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
|
||||
vi.mock('@/next/link', () => ({
|
||||
default: ({ children, href, ...props }: { children: React.ReactNode, href: string, [key: string]: unknown }) => (
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 Card from './card'
|
||||
|
||||
@ -19,7 +20,7 @@ const ServiceApi = ({
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = 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 handleOpenSecretKeyModal = useCallback(() => {
|
||||
|
||||
@ -83,6 +83,12 @@ vi.mock('@/context/app-context', () => ({
|
||||
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 mockHitTestingMutateAsync = 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', () => ({
|
||||
useHitTesting: vi.fn(() => ({
|
||||
mutateAsync: mockHitTestingMutateAsync,
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
} from '@langgenius/dify-ui/drawer'
|
||||
import { Pagination } from '@langgenius/dify-ui/pagination'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
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 Loading from '@/app/components/base/loading'
|
||||
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 useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useDatasetTestingRecords } from '@/service/knowledge/use-dataset'
|
||||
@ -63,13 +67,13 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
|
||||
|
||||
const [currPage, setCurrPage] = useState<number>(0)
|
||||
const { dataset: currentDataset } = useContext(DatasetDetailContext)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const canRunRetrievalRecall = React.useMemo(() => getDatasetACLCapabilities(currentDataset?.permission_keys, {
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canRunRetrievalRecall = getDatasetACLCapabilities(currentDataset?.permission_keys, {
|
||||
currentUserId,
|
||||
resourceMaintainer: currentDataset?.maintainer,
|
||||
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 total = recordsRes?.total || 0
|
||||
|
||||
@ -8,6 +8,7 @@ const mockReplace = vi.fn()
|
||||
let mockAppContextState = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||
}
|
||||
let mockIsCurrentWorkspaceOwner = true
|
||||
@ -27,6 +28,12 @@ vi.mock('@/context/app-context', () => ({
|
||||
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
|
||||
const mockSetShowExternalApiPanel = vi.fn()
|
||||
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
|
||||
vi.mock('@/hooks/use-document-title', () => ({
|
||||
default: vi.fn(),
|
||||
@ -132,6 +145,7 @@ describe('List', () => {
|
||||
mockAppContextState = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||
}
|
||||
mockIsCurrentWorkspaceOwner = true
|
||||
@ -170,6 +184,7 @@ describe('List', () => {
|
||||
mockAppContextState = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||
}
|
||||
|
||||
@ -282,6 +297,7 @@ describe('List', () => {
|
||||
mockAppContextState = {
|
||||
isCurrentWorkspaceEditor: false,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||
}
|
||||
const { useDatasetList } = await import('@/service/knowledge/use-dataset')
|
||||
@ -303,6 +319,7 @@ describe('List', () => {
|
||||
mockAppContextState = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
workspacePermissionKeys: [],
|
||||
}
|
||||
const { useDatasetList } = await import('@/service/knowledge/use-dataset')
|
||||
@ -367,6 +384,7 @@ describe('List', () => {
|
||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
||||
isCurrentWorkspaceEditor: false,
|
||||
isCurrentWorkspaceManager: false,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
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 () => {
|
||||
mockAppContextState = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||
}
|
||||
vi.doMock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
currentWorkspace: { role: 'admin' },
|
||||
@ -425,6 +449,7 @@ describe('List', () => {
|
||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||
}),
|
||||
}))
|
||||
@ -452,6 +477,7 @@ describe('List', () => {
|
||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
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 () => {
|
||||
mockAppContextState = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
workspacePermissionKeys: ['dataset.create_and_management', 'dataset.external.connect'],
|
||||
}
|
||||
vi.doMock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
currentWorkspace: { role: 'editor' },
|
||||
@ -489,6 +521,7 @@ describe('List', () => {
|
||||
useSelector: (selector: (state: typeof mockAppContextState) => unknown) => selector({
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: false,
|
||||
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),
|
||||
}))
|
||||
|
||||
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', () => ({
|
||||
useDatasetCardState: () => ({
|
||||
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', () => ({
|
||||
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)),
|
||||
}))
|
||||
|
||||
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', () => {
|
||||
const createMockDataset = (overrides: Partial<DataSet> = {}): DataSet => ({
|
||||
id: 'dataset-1',
|
||||
|
||||
@ -5,10 +5,13 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import {
|
||||
datasetRbacEnabledAtom,
|
||||
userProfileIdAtom,
|
||||
workspacePermissionKeysAtom,
|
||||
} from '@/context/app-context-state'
|
||||
import { getDatasetACLCapabilities } from '@/utils/permission'
|
||||
import Operations from '../operations'
|
||||
|
||||
@ -28,10 +31,9 @@ const OperationsDropdown = ({
|
||||
openAccessConfig,
|
||||
}: OperationsDropdownProps) => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const isRbacEnabled = useAtomValue(datasetRbacEnabledAtom)
|
||||
const datasetACLCapabilities = React.useMemo(() => getDatasetACLCapabilities(dataset.permission_keys, {
|
||||
currentUserId,
|
||||
resourceMaintainer: dataset.maintainer,
|
||||
|
||||
@ -3,9 +3,13 @@ import type { KeyboardEvent, MouseEvent } from 'react'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useMemo } from 'react'
|
||||
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 { useRouter } from '@/next/navigation'
|
||||
import { getDatasetACLCapabilities, hasOnlyDatasetPreviewPermission, hasPermission } from '@/utils/permission'
|
||||
@ -32,8 +36,8 @@ const DatasetCard = ({
|
||||
}: DatasetCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { push } = useRouter()
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
const datasetCard = useDatasetCardController({ dataset, onSuccess })
|
||||
const {
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import { useBoolean, useDebounceFn } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
|
||||
// Libraries
|
||||
import { useState } from 'react'
|
||||
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 { TagManagementModal } from '@/features/tag-management/components/tag-management-modal'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
@ -22,7 +26,7 @@ import DatasetListHeader from './header'
|
||||
const List = () => {
|
||||
const { t } = useTranslation()
|
||||
const { push } = useRouter()
|
||||
const { isCurrentWorkspaceOwner } = useAppContext()
|
||||
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
||||
const [showTagManagementModal, setShowTagManagementModal] = useState(false)
|
||||
const { showExternalApiPanel, setShowExternalApiPanel } = useExternalApiPanel()
|
||||
const [includeAll, { toggle: toggleIncludeAll }] = useBoolean(false)
|
||||
@ -48,7 +52,7 @@ const List = () => {
|
||||
handleTagsUpdate()
|
||||
}
|
||||
|
||||
const workspacePermissionKeys = useAppContextSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canCreateDataset = hasPermission(workspacePermissionKeys, 'dataset.create_and_management')
|
||||
const canConnectExternalDataset = hasPermission(workspacePermissionKeys, 'dataset.external.connect')
|
||||
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 => ({
|
||||
id: 'dataset-1',
|
||||
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
|
||||
vi.mock('@/service/datasets', () => ({
|
||||
updateDatasetSetting: vi.fn().mockResolvedValue({}),
|
||||
|
||||
@ -19,17 +19,29 @@ vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
// Mock app-context
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: () => ({
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Current User',
|
||||
email: 'current@example.com',
|
||||
avatar_url: '',
|
||||
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
|
||||
vi.mock('@/app/components/base/image-uploader/hooks', () => ({
|
||||
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', () => {
|
||||
const mockDataset: DataSet = {
|
||||
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 => ({
|
||||
id: 'dataset-1',
|
||||
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
|
||||
vi.mock('@/service/datasets', () => ({
|
||||
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 { RetrievalConfig } from '@/types/app'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
|
||||
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 { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import {
|
||||
userProfileIdAtom,
|
||||
workspacePermissionKeysAtom,
|
||||
} from '@/context/app-context-state'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { DatasetPermission } from '@/models/datasets'
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
@ -30,8 +34,8 @@ export const useFormState = () => {
|
||||
const { t } = useTranslation()
|
||||
const currentDataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||
const mutateDatasets = useDatasetDetailContextWithSelector(state => state.mutateDatasetRes)
|
||||
const currentUserId = useAppContextWithSelector(state => state.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const datasetACLCapabilities = useMemo(
|
||||
() => getDatasetACLCapabilities(currentDataset?.permission_keys, {
|
||||
currentUserId,
|
||||
|
||||
@ -4,17 +4,32 @@ import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features
|
||||
import { DatasetPermission } from '@/models/datasets'
|
||||
import PermissionSelector from '../index'
|
||||
|
||||
// Mock app-context
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: () => ({
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Current User',
|
||||
email: 'current@example.com',
|
||||
avatar_url: '',
|
||||
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', () => {
|
||||
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' }!,
|
||||
@ -33,6 +48,7 @@ describe('PermissionSelector', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockIsRbacEnabled = false
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@ -409,6 +425,8 @@ describe('PermissionSelector', () => {
|
||||
})
|
||||
|
||||
it('should show access config hint and remain closed when RBAC is enabled', () => {
|
||||
mockIsRbacEnabled = true
|
||||
|
||||
renderWithSystemFeatures(<PermissionSelector {...defaultProps} />, {
|
||||
systemFeatures: {
|
||||
rbac_enabled: true,
|
||||
|
||||
@ -7,12 +7,14 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@langgenius/dify-ui/popover'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import {
|
||||
datasetRbacEnabledAtom,
|
||||
userProfileAtom,
|
||||
} from '@/context/app-context-state'
|
||||
import { DatasetPermission } from '@/models/datasets'
|
||||
import MemberItem from './member-item'
|
||||
import Item from './permission-item'
|
||||
@ -35,8 +37,8 @@ const PermissionSelector = ({
|
||||
onMemberSelect,
|
||||
}: RoleSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const userProfile = useAppContextWithSelector(state => state.userProfile)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const isRbacEnabled = useAtomValue(datasetRbacEnabledAtom)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const [keywords, setKeywords] = useState('')
|
||||
@ -89,7 +91,7 @@ const PermissionSelector = ({
|
||||
const isAllTeamMembers = permission === DatasetPermission.allTeamMembers
|
||||
const isPartialMembers = permission === DatasetPermission.partialMembers
|
||||
const selectedMemberNames = selectedMembers.map(member => member.name).join(', ')
|
||||
const isDisabledByRBAC = systemFeatures.rbac_enabled
|
||||
const isDisabledByRBAC = isRbacEnabled
|
||||
const isDisabled = disabled || isDisabledByRBAC
|
||||
|
||||
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 { userProfileQueryOptions } from '@/features/account-profile/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 { consoleQuery } from '@/service/client'
|
||||
import { langGeniusVersionQueryOptions } from '@/service/lang-genius-version'
|
||||
@ -15,7 +16,7 @@ import {
|
||||
initialLangGeniusVersionInfo,
|
||||
initialWorkspaceInfo,
|
||||
userProfilePlaceholder,
|
||||
} from './app-context'
|
||||
} from './app-context-defaults'
|
||||
import {
|
||||
emptyWorkspacePermissionKeys,
|
||||
getLangGeniusVersionInfo,
|
||||
@ -29,12 +30,22 @@ const accountProfileQueryAtom = atomWithSuspenseQuery(() => userProfileQueryOpti
|
||||
|
||||
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 => {
|
||||
const accountProfileQuery = get(accountProfileQueryAtom) as SuspenseQueryResult<UserProfileWithMeta>
|
||||
|
||||
return accountProfileQuery.data?.profile || userProfilePlaceholder
|
||||
})
|
||||
|
||||
export const userProfileIdAtom = atom((get) => {
|
||||
return get(userProfileAtom).id
|
||||
})
|
||||
|
||||
const profileMetaAtom = atom((get) => {
|
||||
const accountProfileQuery = get(accountProfileQueryAtom) as SuspenseQueryResult<UserProfileWithMeta>
|
||||
|
||||
@ -58,18 +69,26 @@ export const currentWorkspaceAtom = atom((get) => {
|
||||
return get(normalizedCurrentWorkspaceAtom)
|
||||
})
|
||||
|
||||
export const currentWorkspaceIdAtom = atom((get) => {
|
||||
return get(currentWorkspaceAtom).id
|
||||
})
|
||||
|
||||
export const workspaceRoleFlagsAtom = atom((get) => {
|
||||
return getWorkspaceRoleFlags(get(currentWorkspaceAtom))
|
||||
})
|
||||
|
||||
export const isCurrentWorkspaceOwnerAtom = atom((get) => {
|
||||
return get(workspaceRoleFlagsAtom).isCurrentWorkspaceOwner
|
||||
})
|
||||
|
||||
const workspacePermissionKeysQueryAtom = atomWithQuery((get) => {
|
||||
const workspaceId = get(currentWorkspaceAtom).id
|
||||
const workspaceId = get(currentWorkspaceIdAtom)
|
||||
|
||||
return workspacePermissionKeysQueryOptions(workspaceId)
|
||||
})
|
||||
|
||||
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) => {
|
||||
@ -80,6 +99,10 @@ export const currentWorkspaceLoadingAtom = atom((get) => {
|
||||
return get(currentWorkspaceQueryAtom).isPending
|
||||
})
|
||||
|
||||
export const datasetRbacEnabledAtom = atom((get) => {
|
||||
return get(systemFeaturesAtom).rbac_enabled
|
||||
})
|
||||
|
||||
export const currentWorkspaceValidatingAtom = atom((get) => {
|
||||
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 { noop } from 'es-toolkit/function'
|
||||
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 = {
|
||||
userProfile: GetAccountProfileResponse
|
||||
@ -22,37 +27,11 @@ export type AppContextValue = {
|
||||
workspacePermissionKeys: string[]
|
||||
}
|
||||
|
||||
export const userProfilePlaceholder = {
|
||||
id: '',
|
||||
name: '',
|
||||
email: '',
|
||||
avatar: '',
|
||||
avatar_url: '',
|
||||
is_password_set: false,
|
||||
}
|
||||
export const userProfilePlaceholder = defaultUserProfilePlaceholder
|
||||
|
||||
export const initialLangGeniusVersionInfo = {
|
||||
current_env: '',
|
||||
current_version: '',
|
||||
latest_version: '',
|
||||
release_date: '',
|
||||
release_notes: '',
|
||||
version: '',
|
||||
can_auto_update: false,
|
||||
}
|
||||
export const initialLangGeniusVersionInfo = defaultLangGeniusVersionInfo
|
||||
|
||||
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,
|
||||
}
|
||||
export const initialWorkspaceInfo = defaultWorkspaceInfo
|
||||
|
||||
export const AppContext = createContext<AppContextValue>({
|
||||
userProfile: userProfilePlaceholder,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user