mirror of
https://github.com/langgenius/dify.git
synced 2026-10-30 18:34:29 +08:00
refactor(web): remove profile bootstrap atom layer (#40474)
This commit is contained in:
@@ -45,6 +45,7 @@ const render = (ui: ReactElement, options: RenderOptions = {}, vectorSpaceUsageU
|
||||
seedEducationStatus(queryClient, mockEducationStatus)
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
accountProfile: mockConsoleState.userProfile as { email?: string },
|
||||
accountProfileMeta: { currentVersion: '1.0.0' },
|
||||
systemFeatures: { deployment_edition: 'CLOUD' },
|
||||
queryClient,
|
||||
})
|
||||
@@ -61,11 +62,6 @@ vi.mock('@/context/workspace-state', async () => {
|
||||
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createWorkspaceStateModuleMock(() => mockConsoleState)
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState)
|
||||
})
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
setShowPricingModal: mockSetShowPricingModal,
|
||||
|
||||
@@ -32,6 +32,7 @@ const render = (ui: ReactElement, options: RenderOptions = {}) => {
|
||||
seedEducationStatus(queryClient, mockEducationStatus)
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
accountProfile: mockConsoleState.userProfile as { email?: string },
|
||||
accountProfileMeta: { currentVersion: '1.0.0' },
|
||||
systemFeatures: { deployment_edition: 'CLOUD' },
|
||||
queryClient,
|
||||
})
|
||||
@@ -50,11 +51,6 @@ vi.mock('@/context/workspace-state', async () => {
|
||||
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createWorkspaceStateModuleMock(() => mockConsoleState)
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState)
|
||||
})
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
setShowPricingModal: mockSetShowPricingModal,
|
||||
|
||||
@@ -26,6 +26,7 @@ const mockFetchSubscriptionUrls = vi.hoisted(() => vi.fn())
|
||||
const render = (ui: React.ReactElement) => {
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
accountProfile: mockConsoleState.userProfile as { email?: string },
|
||||
accountProfileMeta: { currentVersion: '1.0.0' },
|
||||
educationStatus: mockEducationStatus,
|
||||
})
|
||||
return renderWithConsoleState(ui, { wrapper })
|
||||
@@ -40,11 +41,6 @@ vi.mock('@/context/workspace-state', async () => {
|
||||
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createWorkspaceStateModuleMock(() => mockConsoleState)
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState)
|
||||
})
|
||||
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useGetLanguage: () => 'en-US',
|
||||
useGetPricingPageLanguage: () => 'en',
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ProfileBootstrapGate } from '../profile-bootstrap-gate'
|
||||
|
||||
const profileQueryKey = ['console', 'account', 'profile', 'get']
|
||||
|
||||
type Deferred<T> = {
|
||||
promise: Promise<T>
|
||||
resolve: (value: T) => void
|
||||
reject: (reason?: unknown) => void
|
||||
}
|
||||
|
||||
function createDeferred<T>(): Deferred<T> {
|
||||
let resolve!: (value: T) => void
|
||||
let reject!: (reason?: unknown) => void
|
||||
const promise = new Promise<T>((nextResolve, nextReject) => {
|
||||
resolve = nextResolve
|
||||
reject = nextReject
|
||||
})
|
||||
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
profileQuery: undefined as Deferred<{ id: string }> | undefined,
|
||||
}))
|
||||
|
||||
vi.mock('@/features/account-profile/client', () => ({
|
||||
userProfileQueryOptions: () => ({
|
||||
queryKey: profileQueryKey,
|
||||
queryFn: () => mocks.profileQuery!.promise,
|
||||
}),
|
||||
}))
|
||||
|
||||
function createQueryClient() {
|
||||
return new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function renderGate(children: ReactNode, queryClient = createQueryClient()) {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ProfileBootstrapGate>{children}</ProfileBootstrapGate>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
return queryClient
|
||||
}
|
||||
|
||||
describe('ProfileBootstrapGate', () => {
|
||||
beforeEach(() => {
|
||||
mocks.profileQuery = createDeferred()
|
||||
})
|
||||
|
||||
it('waits for the profile before mounting atom consumers', async () => {
|
||||
renderGate(<div>Console shell</div>)
|
||||
|
||||
expect(screen.queryByText('Console shell')).not.toBeInTheDocument()
|
||||
|
||||
await act(async () => {
|
||||
mocks.profileQuery!.resolve({ id: 'user-1' })
|
||||
})
|
||||
|
||||
expect(await screen.findByText('Console shell')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps atom consumers mounted when a cached profile background refetch fails', async () => {
|
||||
const queryClient = createQueryClient()
|
||||
queryClient.setQueryData(profileQueryKey, { id: 'user-1' }, { updatedAt: 1 })
|
||||
|
||||
renderGate(<div>Console shell</div>, queryClient)
|
||||
|
||||
expect(screen.getByText('Console shell')).toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(queryClient.getQueryState(profileQueryKey)?.fetchStatus).toBe('fetching')
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
mocks.profileQuery!.reject(new Error('profile refetch failed'))
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryClient.getQueryState(profileQueryKey)?.status).toBe('error')
|
||||
})
|
||||
expect(screen.getByText('Console shell')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen'
|
||||
import type { DeploymentEdition } from '@dify/contracts/api/console/system-features/types.gen'
|
||||
import type { GetWorkspacesCurrentSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { skipToken, useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { Fragment, useEffect, useRef } from 'react'
|
||||
import { setUserId, setUserProperties } from '@/app/components/base/amplitude'
|
||||
@@ -12,10 +12,11 @@ import { useAmplitudeInitialized } from '@/app/components/base/amplitude/use-amp
|
||||
import { useAnalyticsConsent } from '@/app/components/base/analytics-consent/consent-store'
|
||||
import { setZendeskConversationFields } from '@/app/components/base/zendesk/utils'
|
||||
import { ZENDESK_FIELD_IDS } from '@/config'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { getLangGeniusVersionInfo } from '@/context/app-context-normalizers'
|
||||
import { currentWorkspaceAtom } from '@/context/workspace-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { deploymentEditionAtom } from '@/features/system-features/state'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
|
||||
type AmplitudeProperties = Record<string, string | number | boolean>
|
||||
|
||||
@@ -108,13 +109,37 @@ function syncZendeskField({
|
||||
}
|
||||
|
||||
function ZendeskConversationSync() {
|
||||
const { data: userProfile } = useSuspenseQuery({
|
||||
const { data: accountProfile } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.profile,
|
||||
select: (data) => ({
|
||||
email: data.profile.email,
|
||||
meta: data.meta,
|
||||
}),
|
||||
})
|
||||
const { data: systemFeatures } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: (data) => ({
|
||||
brandingEnabled: data.branding.enabled,
|
||||
deploymentEdition: data.deployment_edition,
|
||||
}),
|
||||
})
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const deploymentEdition = useAtomValue(deploymentEditionAtom)
|
||||
const { data: versionData } = useQuery(
|
||||
consoleQuery.version.get.queryOptions({
|
||||
input: accountProfile.meta.currentVersion
|
||||
? {
|
||||
query: {
|
||||
current_version: accountProfile.meta.currentVersion,
|
||||
},
|
||||
}
|
||||
: skipToken,
|
||||
enabled: !systemFeatures.brandingEnabled,
|
||||
}),
|
||||
)
|
||||
const langGeniusVersionInfo = getLangGeniusVersionInfo({
|
||||
meta: accountProfile.meta,
|
||||
versionData,
|
||||
})
|
||||
const syncStateRef = useRef<ZendeskSyncState>({})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -122,7 +147,7 @@ function ZendeskConversationSync() {
|
||||
|
||||
syncZendeskField({
|
||||
fieldId: ZENDESK_FIELD_IDS.ENVIRONMENT,
|
||||
deploymentEdition,
|
||||
deploymentEdition: systemFeatures.deploymentEdition,
|
||||
value: langGeniusVersionInfo.current_env.toLowerCase(),
|
||||
previousValue: syncStateRef.current.environment,
|
||||
setNextValue: (value) => {
|
||||
@@ -131,7 +156,7 @@ function ZendeskConversationSync() {
|
||||
})
|
||||
syncZendeskField({
|
||||
fieldId: ZENDESK_FIELD_IDS.VERSION,
|
||||
deploymentEdition,
|
||||
deploymentEdition: systemFeatures.deploymentEdition,
|
||||
value: langGeniusVersionInfo.version,
|
||||
previousValue: syncStateRef.current.version,
|
||||
setNextValue: (value) => {
|
||||
@@ -140,8 +165,8 @@ function ZendeskConversationSync() {
|
||||
})
|
||||
syncZendeskField({
|
||||
fieldId: ZENDESK_FIELD_IDS.EMAIL,
|
||||
deploymentEdition,
|
||||
value: userProfile.email,
|
||||
deploymentEdition: systemFeatures.deploymentEdition,
|
||||
value: accountProfile.email,
|
||||
previousValue: syncStateRef.current.email,
|
||||
setNextValue: (value) => {
|
||||
nextState.email = value
|
||||
@@ -149,7 +174,7 @@ function ZendeskConversationSync() {
|
||||
})
|
||||
syncZendeskField({
|
||||
fieldId: ZENDESK_FIELD_IDS.WORKSPACE_ID,
|
||||
deploymentEdition,
|
||||
deploymentEdition: systemFeatures.deploymentEdition,
|
||||
value: currentWorkspace.id,
|
||||
previousValue: syncStateRef.current.workspaceId,
|
||||
setNextValue: (value) => {
|
||||
@@ -158,7 +183,7 @@ function ZendeskConversationSync() {
|
||||
})
|
||||
|
||||
syncStateRef.current = nextState
|
||||
}, [currentWorkspace.id, deploymentEdition, langGeniusVersionInfo, userProfile.email])
|
||||
}, [accountProfile.email, currentWorkspace.id, langGeniusVersionInfo, systemFeatures])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
|
||||
export function ProfileBootstrapGate({ children }: { children: ReactNode }) {
|
||||
useSuspenseQuery(userProfileQueryOptions())
|
||||
|
||||
return children
|
||||
}
|
||||
@@ -5,17 +5,14 @@ import { ModalContextProvider } from '@/context/modal-context-provider'
|
||||
import { ProviderContextProvider } from '@/context/provider-context-provider'
|
||||
import { ExternalServiceSync } from './external-service-sync'
|
||||
import { CommonLayoutHydrationBoundary } from './hydration-boundary'
|
||||
import { ProfileBootstrapGate } from './profile-bootstrap-gate'
|
||||
|
||||
export async function ConsoleRuntimeProviders({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<OAuthRegistrationAnalytics />
|
||||
<CommonLayoutHydrationBoundary>
|
||||
<ProfileBootstrapGate>
|
||||
<ExternalServiceSync />
|
||||
{children}
|
||||
</ProfileBootstrapGate>
|
||||
<ExternalServiceSync />
|
||||
{children}
|
||||
</CommonLayoutHydrationBoundary>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -8,14 +8,14 @@ import type { SiteInfo } from '@/models/share'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { Suspense, use, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { createTheme } from '@/app/components/base/chat/embedded-chatbot/theme/theme'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { basePath } from '@/utils/var'
|
||||
import {
|
||||
compressAndEncodeBase64,
|
||||
@@ -141,14 +141,15 @@ const EmbeddedContent = ({
|
||||
)
|
||||
const latestResolvedIframeUrlRef = useRef('')
|
||||
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: currentEnv } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentEnv,
|
||||
})
|
||||
const theme = createTheme(
|
||||
siteInfo?.chat_color_theme ?? null,
|
||||
siteInfo?.chat_color_theme_inverted ?? false,
|
||||
)
|
||||
const isTestEnv =
|
||||
langGeniusVersionInfo.current_env === 'TESTING' ||
|
||||
langGeniusVersionInfo.current_env === 'DEVELOPMENT'
|
||||
const isTestEnv = currentEnv === 'TESTING' || currentEnv === 'DEVELOPMENT'
|
||||
|
||||
const handleHiddenInputValueChange = (variable: string, value: WorkflowLaunchInputValue) => {
|
||||
const nextHiddenInputValues = {
|
||||
|
||||
@@ -5,13 +5,11 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Meter, MeterIndicator, MeterTrack } from '@langgenius/dify-ui/meter'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { mailToSupport } from '@/app/components/header/utils/util'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { langGeniusCurrentVersionAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import UpgradeBtn from '../upgrade-btn'
|
||||
import s from './style.module.css'
|
||||
@@ -19,11 +17,13 @@ import s from './style.module.css'
|
||||
const AppsFull: FC<{ loc: string; className?: string }> = ({ loc, className }) => {
|
||||
const { t } = useTranslation()
|
||||
const { plan } = useProviderContext()
|
||||
const { data: userProfileEmail } = useSuspenseQuery({
|
||||
const { data: accountProfile } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.profile.email,
|
||||
select: (data) => ({
|
||||
email: data.profile.email,
|
||||
currentVersion: data.meta.currentVersion,
|
||||
}),
|
||||
})
|
||||
const currentVersion = useAtomValue(langGeniusCurrentVersionAtom)
|
||||
const isTeam = plan.type === Plan.team
|
||||
const usage = plan.usage.buildApps
|
||||
const total = plan.total.buildApps
|
||||
@@ -66,7 +66,11 @@ const AppsFull: FC<{ loc: string; className?: string }> = ({ loc, className }) =
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={mailToSupport(userProfileEmail, plan.type, currentVersion)}
|
||||
href={mailToSupport(
|
||||
accountProfile.email,
|
||||
plan.type,
|
||||
accountProfile.currentVersion ?? '',
|
||||
)}
|
||||
>
|
||||
{t(($) => $['apps.contactUs'], { ns: 'billing' })}
|
||||
</a>
|
||||
|
||||
-10
@@ -7,7 +7,6 @@ import { createMockProviderContextValue } from '@/__mocks__/provider-context'
|
||||
import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/image-uploader/utils'
|
||||
import { defaultPlan } from '@/app/components/billing/config'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { initialLangGeniusVersionInfo } from '@/context/app-context-defaults'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { createConsoleQueryClient, renderHookWithConsoleQuery } from '@/test/console/query-data'
|
||||
import useWebAppBrand from '../use-web-app-brand'
|
||||
@@ -114,14 +113,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
refreshCurrentWorkspace: consoleStateRef.value?.refreshCurrentWorkspace,
|
||||
}))
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => ({
|
||||
...consoleStateRef.value,
|
||||
refreshCurrentWorkspace: consoleStateRef.value?.refreshCurrentWorkspace,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
@@ -168,7 +159,6 @@ const createConsoleState = (overrides: Partial<ConsoleStateFixture> = {}): Conso
|
||||
isCurrentWorkspaceDatasetOperator: false,
|
||||
workspacePermissionKeys: ['customization.manage'],
|
||||
refreshCurrentWorkspace: vi.fn(),
|
||||
langGeniusVersionInfo: initialLangGeniusVersionInfo,
|
||||
isLoadingCurrentWorkspace: false,
|
||||
...overrides,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { act, fireEvent, screen } from '@testing-library/react'
|
||||
import { render } from '@/test/console/render'
|
||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { DetailSidebarFrame } from '..'
|
||||
import { DETAIL_SIDEBAR_STORAGE_KEY } from '../storage'
|
||||
|
||||
@@ -12,14 +12,6 @@ const { hotkeyRegistrations } = vi.hoisted(() => ({
|
||||
}
|
||||
>(),
|
||||
}))
|
||||
const mockConsoleState = vi.hoisted(() => ({
|
||||
current: {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: '',
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@tanstack/react-hotkeys', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-hotkeys')>()
|
||||
return {
|
||||
@@ -34,11 +26,6 @@ vi.mock('@tanstack/react-hotkeys', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState.current)
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/main-nav/components/account-section', () => ({
|
||||
default: ({ compact }: { compact?: boolean }) => (
|
||||
<button type="button" aria-label="account">
|
||||
@@ -59,8 +46,8 @@ vi.mock('@/app/components/header/env-nav', () => ({
|
||||
default: () => <div>Environment tag</div>,
|
||||
}))
|
||||
|
||||
function renderDetailSidebarFrame() {
|
||||
return render(
|
||||
function renderDetailSidebarFrame(currentEnv: string | null = null) {
|
||||
return renderWithConsoleQuery(
|
||||
<DetailSidebarFrame
|
||||
renderTop={({ expand, onToggle }) => (
|
||||
<div data-testid="detail-top" data-expand={expand}>
|
||||
@@ -75,6 +62,7 @@ function renderDetailSidebarFrame() {
|
||||
</div>
|
||||
)}
|
||||
/>,
|
||||
{ accountProfileMeta: { currentEnv } },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -82,11 +70,6 @@ describe('DetailSidebarFrame', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
hotkeyRegistrations.clear()
|
||||
mockConsoleState.current = {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: '',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it('renders expanded detail content by default and registers the shortcut for focused inputs', () => {
|
||||
@@ -109,13 +92,7 @@ describe('DetailSidebarFrame', () => {
|
||||
})
|
||||
|
||||
it('collapses detail content from the top toggle and hides environment metadata', () => {
|
||||
mockConsoleState.current = {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'TESTING',
|
||||
},
|
||||
}
|
||||
|
||||
renderDetailSidebarFrame()
|
||||
renderDetailSidebarFrame('TESTING')
|
||||
fireEvent.click(screen.getByTestId('detail-toggle'))
|
||||
|
||||
expect(screen.getByTestId('detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useHotkey } from '@tanstack/react-hotkeys'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import EnvNav from '@/app/components/header/env-nav'
|
||||
import AccountSection from '@/app/components/main-nav/components/account-section'
|
||||
import HelpMenu from '@/app/components/main-nav/components/help-menu'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { DETAIL_SIDEBAR_TOGGLE_HOTKEY } from './hotkeys'
|
||||
import { useDetailSidebarMode } from './storage'
|
||||
|
||||
@@ -44,7 +44,10 @@ export function DetailSidebarFrame({
|
||||
renderTop,
|
||||
renderSection,
|
||||
}: DetailSidebarFrameProps) {
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: currentEnv } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentEnv,
|
||||
})
|
||||
const [storedDetailSidebarExpand, setStoredDetailSidebarExpand] = useDetailSidebarMode()
|
||||
const detailNavigationMode = storedDetailSidebarExpand === 'collapse' ? 'collapse' : 'expand'
|
||||
const detailNavigationExpanded = detailNavigationMode === 'expand'
|
||||
@@ -60,7 +63,6 @@ export function DetailSidebarFrame({
|
||||
const detailNavigationVisibleExpanded =
|
||||
detailNavigationExpanded || isDetailNavigationHoverPreviewOpen
|
||||
const bottomNavigationExpanded = detailNavigationVisibleExpanded
|
||||
const currentEnv = langGeniusVersionInfo?.current_env
|
||||
const showEnvTag = currentEnv === 'TESTING' || currentEnv === 'DEVELOPMENT'
|
||||
|
||||
function handleToggleDetailNavigation() {
|
||||
|
||||
@@ -28,11 +28,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createPermissionStateModuleMock(() => mockConsoleState.current ?? {})
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState.current ?? {})
|
||||
})
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
useRouter: vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
@@ -158,13 +153,6 @@ const baseConsoleState: ConsoleStateFixture = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceDatasetOperator: false,
|
||||
refreshCurrentWorkspace: vi.fn(),
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'testing',
|
||||
current_version: '0.1.0',
|
||||
latest_version: '0.1.0',
|
||||
release_notes: '',
|
||||
version: '0.1.0',
|
||||
},
|
||||
isLoadingCurrentWorkspace: false,
|
||||
workspacePermissionKeys: [
|
||||
'workspace.member.manage',
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import type { ConsoleStateFixture } from '@/test/console/state-fixture'
|
||||
import { screen } from '@testing-library/react'
|
||||
import { vi } from 'vitest'
|
||||
import { render } from '@/test/console/render'
|
||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import EnvNav from '../index'
|
||||
|
||||
const mockConsoleState = vi.hoisted(() => ({
|
||||
current: {} as Partial<ConsoleStateFixture>,
|
||||
}))
|
||||
const mockConsoleStateReader = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState.current)
|
||||
})
|
||||
const renderEnvNav = (currentEnv: string) =>
|
||||
renderWithConsoleQuery(<EnvNav />, {
|
||||
accountProfileMeta: { currentEnv },
|
||||
})
|
||||
|
||||
describe('EnvNav', () => {
|
||||
beforeEach(() => {
|
||||
@@ -20,41 +14,17 @@ describe('EnvNav', () => {
|
||||
})
|
||||
|
||||
it('should render null when environment is PRODUCTION', () => {
|
||||
const consoleState = {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'PRODUCTION',
|
||||
},
|
||||
} as unknown as ConsoleStateFixture
|
||||
mockConsoleState.current = consoleState
|
||||
mockConsoleStateReader.mockReturnValue(consoleState)
|
||||
|
||||
const { container } = render(<EnvNav />)
|
||||
const { container } = renderEnvNav('PRODUCTION')
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
|
||||
it('should render TESTING tag and icon when environment is TESTING', () => {
|
||||
const consoleState = {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'TESTING',
|
||||
},
|
||||
} as unknown as ConsoleStateFixture
|
||||
mockConsoleState.current = consoleState
|
||||
mockConsoleStateReader.mockReturnValue(consoleState)
|
||||
|
||||
render(<EnvNav />)
|
||||
renderEnvNav('TESTING')
|
||||
expect(screen.getByText('common.environment.testing')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render DEVELOPMENT tag and icon when environment is DEVELOPMENT', () => {
|
||||
const consoleState = {
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'DEVELOPMENT',
|
||||
},
|
||||
} as unknown as ConsoleStateFixture
|
||||
mockConsoleState.current = consoleState
|
||||
mockConsoleStateReader.mockReturnValue(consoleState)
|
||||
|
||||
render(<EnvNav />)
|
||||
renderEnvNav('DEVELOPMENT')
|
||||
expect(screen.getByText('common.environment.development')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
|
||||
const headerEnvClassName: { [k: string]: string } = {
|
||||
DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]',
|
||||
@@ -11,24 +11,25 @@ const headerEnvClassName: { [k: string]: string } = {
|
||||
|
||||
const EnvNav = () => {
|
||||
const { t } = useTranslation()
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const showEnvTag =
|
||||
langGeniusVersionInfo.current_env === 'TESTING' ||
|
||||
langGeniusVersionInfo.current_env === 'DEVELOPMENT'
|
||||
const { data: currentEnv } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentEnv,
|
||||
})
|
||||
const showEnvTag = currentEnv === 'TESTING' || currentEnv === 'DEVELOPMENT'
|
||||
|
||||
if (!showEnvTag) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`mr-1 flex h-5.5 items-center rounded-md border px-2 text-xs font-medium ${headerEnvClassName[langGeniusVersionInfo.current_env]} `}
|
||||
className={`mr-1 flex h-5.5 items-center rounded-md border px-2 text-xs font-medium ${headerEnvClassName[currentEnv]} `}
|
||||
>
|
||||
{langGeniusVersionInfo.current_env === 'TESTING' && (
|
||||
{currentEnv === 'TESTING' && (
|
||||
<>
|
||||
<span aria-hidden className="i-custom-vender-solid-education-beaker-02 size-3" />
|
||||
<div className="ml-1">{t(($) => $['environment.testing'], { ns: 'common' })}</div>
|
||||
</>
|
||||
)}
|
||||
{langGeniusVersionInfo.current_env === 'DEVELOPMENT' && (
|
||||
{currentEnv === 'DEVELOPMENT' && (
|
||||
<>
|
||||
<span aria-hidden className="i-custom-vender-solid-development-terminal-square size-3" />
|
||||
<div className="ml-1">{t(($) => $['environment.development'], { ns: 'common' })}</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
StepByStepTourStatePatchPayload,
|
||||
StepByStepTourStateResponse,
|
||||
} from '@dify/contracts/api/console/onboarding/types.gen'
|
||||
import type { GetVersionResponse } from '@dify/contracts/api/console/version/types.gen'
|
||||
import type {
|
||||
GetWorkspacesCurrentSummaryResponse,
|
||||
TenantListItemResponse,
|
||||
@@ -12,6 +13,7 @@ import type { Mock } from 'vitest'
|
||||
import type { StepByStepTourSessionState } from '@/app/components/step-by-step-tour/types'
|
||||
import type { ModalContextState } from '@/context/modal-context'
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
import type { UserProfileWithMeta } from '@/features/account-profile/client'
|
||||
import type { ConsoleStateFixture } from '@/test/console/state-fixture'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
@@ -156,8 +158,13 @@ const mockStepByStepTour = vi.hoisted(() => {
|
||||
stateQueryKey,
|
||||
}
|
||||
})
|
||||
type MainNavConsoleState = ConsoleStateFixture & {
|
||||
profileMeta: UserProfileWithMeta['meta']
|
||||
versionData: GetVersionResponse
|
||||
}
|
||||
|
||||
const mockConsoleState = vi.hoisted(() => ({
|
||||
current: undefined as ConsoleStateFixture | undefined,
|
||||
current: undefined as MainNavConsoleState | undefined,
|
||||
}))
|
||||
|
||||
vi.mock('@/features/agent-v2/feature-flag', () => ({
|
||||
@@ -176,11 +183,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createPermissionStateModuleMock(() => mockConsoleState.current ?? {})
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState.current ?? {})
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
@@ -486,7 +488,7 @@ const mainNavUserProfile = {
|
||||
is_password_set: true,
|
||||
}
|
||||
|
||||
const consoleState: ConsoleStateFixture = {
|
||||
const consoleState: MainNavConsoleState = {
|
||||
userProfile: mainNavUserProfile,
|
||||
currentWorkspace: {
|
||||
id: 'workspace-1',
|
||||
@@ -500,12 +502,13 @@ const consoleState: ConsoleStateFixture = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceDatasetOperator: false,
|
||||
refreshCurrentWorkspace: vi.fn(),
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'testing',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '1.0.0',
|
||||
release_notes: '',
|
||||
profileMeta: {
|
||||
currentEnv: 'testing',
|
||||
currentVersion: '1.0.0',
|
||||
},
|
||||
versionData: {
|
||||
version: '1.0.0',
|
||||
release_notes: '',
|
||||
},
|
||||
isLoadingCurrentWorkspace: false,
|
||||
isLoadingWorkspacePermissionKeys: false,
|
||||
@@ -545,10 +548,19 @@ const renderMainNav = (
|
||||
...(currentConsoleState.userProfile ?? {}),
|
||||
},
|
||||
meta: {
|
||||
currentVersion: null,
|
||||
currentEnv: null,
|
||||
currentVersion: currentConsoleState.profileMeta.currentVersion,
|
||||
currentEnv: currentConsoleState.profileMeta.currentEnv,
|
||||
},
|
||||
})
|
||||
const currentVersion = currentConsoleState.profileMeta.currentVersion
|
||||
if (currentVersion) {
|
||||
queryClient.setQueryData(
|
||||
consoleQuery.version.get.queryOptions({
|
||||
input: { query: { current_version: currentVersion } },
|
||||
}).queryKey,
|
||||
currentConsoleState.versionData,
|
||||
)
|
||||
}
|
||||
queryClient.setQueryData(consoleQuery.workspaces.get.queryKey(), { workspaces: mockWorkspaces })
|
||||
queryClient.setQueryData(mockStepByStepTour.stateQueryKey, mockStepByStepTour.state)
|
||||
const store = options.store ?? createStore()
|
||||
@@ -1124,9 +1136,8 @@ describe('MainNav', () => {
|
||||
const user = userEvent.setup()
|
||||
mockConsoleState.current = {
|
||||
...consoleState,
|
||||
langGeniusVersionInfo: {
|
||||
...consoleState.langGeniusVersionInfo,
|
||||
latest_version: '1.1.0',
|
||||
versionData: {
|
||||
version: '1.1.0',
|
||||
release_notes: 'https://github.com/langgenius/dify/releases/tag/1.1.0',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -52,11 +52,6 @@ vi.mock('@/config', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState.current)
|
||||
})
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: vi.fn(),
|
||||
}))
|
||||
@@ -90,6 +85,9 @@ describe('SupportMenu', () => {
|
||||
const renderSupportMenu = () => {
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
accountProfile: mockConsoleState.current.userProfile,
|
||||
accountProfileMeta: {
|
||||
currentVersion: mockConsoleState.current.langGeniusVersionInfo.current_version,
|
||||
},
|
||||
systemFeatures: { deployment_edition: deploymentEdition },
|
||||
})
|
||||
return render(
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { skipToken, useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -36,15 +36,17 @@ import {
|
||||
stepByStepTourStateUpdatingAtom,
|
||||
} from '@/app/components/step-by-step-tour/state'
|
||||
import { useSetStepByStepTourShellMode } from '@/app/components/step-by-step-tour/storage'
|
||||
import { getLangGeniusVersionInfo } from '@/context/app-context-normalizers'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import {
|
||||
currentWorkspaceIdAtom,
|
||||
currentWorkspaceLoadingAtom,
|
||||
isCurrentWorkspaceOwnerAtom,
|
||||
} from '@/context/workspace-state'
|
||||
import { env } from '@/env'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import styles from './help-menu.module.css'
|
||||
import AccountAboutDialog from './help-menu/account-about-dialog'
|
||||
import SupportMenu from './support-menu'
|
||||
@@ -86,8 +88,20 @@ const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMe
|
||||
const { t } = useTranslation()
|
||||
const docLink = useDocLink()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { data: profileMeta } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta,
|
||||
})
|
||||
const { data: versionData } = useQuery(
|
||||
consoleQuery.version.get.queryOptions({
|
||||
input: profileMeta.currentVersion
|
||||
? { query: { current_version: profileMeta.currentVersion } }
|
||||
: skipToken,
|
||||
enabled: !systemFeatures.branding.enabled,
|
||||
}),
|
||||
)
|
||||
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const langGeniusVersionInfo = getLangGeniusVersionInfo({ meta: profileMeta, versionData })
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const learnDifyHidden = useLearnDifyHiddenValue()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DropdownMenuItem, DropdownMenuLinkItem } from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { openZendeskWindow } from '@/app/components/base/zendesk/utils'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
@@ -12,7 +11,6 @@ import { mailToSupport } from '@/app/components/header/utils/util'
|
||||
import { SUPPORT_EMAIL_ADDRESS, ZENDESK_WIDGET_KEY } from '@/config'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
|
||||
@@ -23,11 +21,13 @@ export default function SupportMenu() {
|
||||
select: ({ deployment_edition }) => deployment_edition,
|
||||
})
|
||||
const { enableBilling, plan } = useProviderContext()
|
||||
const { data: userProfileEmail } = useSuspenseQuery({
|
||||
const { data: accountProfile } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.profile.email,
|
||||
select: (data) => ({
|
||||
email: data.profile.email,
|
||||
currentVersion: data.meta.currentVersion,
|
||||
}),
|
||||
})
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { setShowPricingModal } = useModalContext()
|
||||
const hasDedicatedChannel = plan.type !== Plan.sandbox || Boolean(SUPPORT_EMAIL_ADDRESS.trim())
|
||||
const shouldShowUpgradeContact =
|
||||
@@ -82,9 +82,9 @@ export default function SupportMenu() {
|
||||
<DropdownMenuLinkItem
|
||||
className="mx-0 h-8 gap-1 px-3 py-1"
|
||||
href={mailToSupport(
|
||||
userProfileEmail,
|
||||
accountProfile.email,
|
||||
plan.type,
|
||||
langGeniusVersionInfo?.current_version,
|
||||
accountProfile.currentVersion ?? '',
|
||||
SUPPORT_EMAIL_ADDRESS,
|
||||
)}
|
||||
rel="noopener noreferrer"
|
||||
|
||||
@@ -10,11 +10,11 @@ import Badge from '@/app/components/base/badge'
|
||||
import { DifyLogo } from '@/app/components/base/logo/dify-logo'
|
||||
import EnvNav from '@/app/components/header/env-nav'
|
||||
import StepByStepTourMount from '@/app/components/step-by-step-tour/mount'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import {
|
||||
isCurrentWorkspaceDatasetOperatorAtom,
|
||||
isCurrentWorkspaceEditorAtom,
|
||||
} from '@/context/workspace-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag'
|
||||
import { useCanManageAgents } from '@/features/agent-v2/permissions'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
@@ -33,15 +33,16 @@ const WebAppsSection = dynamic(() => import('./components/web-apps-section'), {
|
||||
export function MainNav({ className }: MainNavProps) {
|
||||
const { t } = useTranslation()
|
||||
const pathname = usePathname()
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const isCurrentWorkspaceDatasetOperator = useAtomValue(isCurrentWorkspaceDatasetOperatorAtom)
|
||||
const isCurrentWorkspaceEditor = useAtomValue(isCurrentWorkspaceEditorAtom)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { data: currentEnv } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentEnv,
|
||||
})
|
||||
const agentV2Enabled = isAgentV2Enabled()
|
||||
const canManageAgents = useCanManageAgents()
|
||||
const showEnvTag =
|
||||
langGeniusVersionInfo.current_env === 'TESTING' ||
|
||||
langGeniusVersionInfo.current_env === 'DEVELOPMENT'
|
||||
const showEnvTag = currentEnv === 'TESTING' || currentEnv === 'DEVELOPMENT'
|
||||
const canUseAppDeploy = isCurrentWorkspaceEditor && systemFeatures.enable_app_deploy
|
||||
|
||||
const navItems = useMemo<MainNavItem[]>(
|
||||
|
||||
+14
-26
@@ -1,4 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
||||
import { renderHook } from '@/test/console/render'
|
||||
import useWorkspacePluginInstallPermission from '../use-workspace-plugin-install-permission'
|
||||
|
||||
@@ -7,30 +8,17 @@ let mockWorkspacePermissionKeys: string[] = []
|
||||
vi.mock('@/context/permission-state', async () => {
|
||||
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createPermissionStateModuleMock(() => ({
|
||||
langGeniusVersionInfo: {
|
||||
current_env: '',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '',
|
||||
release_notes: '',
|
||||
version: '',
|
||||
},
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => ({
|
||||
langGeniusVersionInfo: {
|
||||
current_env: '',
|
||||
current_version: '1.0.0',
|
||||
latest_version: '',
|
||||
release_notes: '',
|
||||
version: '',
|
||||
},
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}))
|
||||
})
|
||||
|
||||
const renderPermissionHook = () => {
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
accountProfileMeta: { currentVersion: '1.0.0' },
|
||||
})
|
||||
return renderHook(() => useWorkspacePluginInstallPermission(), { wrapper })
|
||||
}
|
||||
|
||||
describe('useWorkspacePluginInstallPermission', () => {
|
||||
beforeEach(() => {
|
||||
mockWorkspacePermissionKeys = []
|
||||
@@ -39,7 +27,7 @@ describe('useWorkspacePluginInstallPermission', () => {
|
||||
it('should grant install and update capabilities with plugin.install', () => {
|
||||
mockWorkspacePermissionKeys = ['plugin.install']
|
||||
|
||||
const { result } = renderHook(() => useWorkspacePluginInstallPermission())
|
||||
const { result } = renderPermissionHook()
|
||||
|
||||
expect(result.current.canInstallPlugin).toBe(true)
|
||||
expect(result.current.canUpdatePlugin).toBe(true)
|
||||
@@ -49,7 +37,7 @@ describe('useWorkspacePluginInstallPermission', () => {
|
||||
})
|
||||
|
||||
it('should not expose installed plugin list viewing as a permission capability', () => {
|
||||
const { result } = renderHook(() => useWorkspacePluginInstallPermission())
|
||||
const { result } = renderPermissionHook()
|
||||
|
||||
expect('canViewInstalledPlugins' in result.current).toBe(false)
|
||||
})
|
||||
@@ -57,7 +45,7 @@ describe('useWorkspacePluginInstallPermission', () => {
|
||||
it('should grant delete capability but not install or update with plugin.delete', () => {
|
||||
mockWorkspacePermissionKeys = ['plugin.delete']
|
||||
|
||||
const { result } = renderHook(() => useWorkspacePluginInstallPermission())
|
||||
const { result } = renderPermissionHook()
|
||||
|
||||
expect(result.current.canInstallPlugin).toBe(false)
|
||||
expect(result.current.canUpdatePlugin).toBe(false)
|
||||
@@ -69,7 +57,7 @@ describe('useWorkspacePluginInstallPermission', () => {
|
||||
it('should grant plugin debug capability with plugin.debug', () => {
|
||||
mockWorkspacePermissionKeys = ['plugin.debug']
|
||||
|
||||
const { result } = renderHook(() => useWorkspacePluginInstallPermission())
|
||||
const { result } = renderPermissionHook()
|
||||
|
||||
expect(result.current.canInstallPlugin).toBe(false)
|
||||
expect(result.current.canUpdatePlugin).toBe(false)
|
||||
@@ -81,7 +69,7 @@ describe('useWorkspacePluginInstallPermission', () => {
|
||||
it('should grant plugin preference setting capability with plugin.plugin_preferences', () => {
|
||||
mockWorkspacePermissionKeys = ['plugin.plugin_preferences']
|
||||
|
||||
const { result } = renderHook(() => useWorkspacePluginInstallPermission())
|
||||
const { result } = renderPermissionHook()
|
||||
|
||||
expect(result.current.canInstallPlugin).toBe(false)
|
||||
expect(result.current.canUpdatePlugin).toBe(false)
|
||||
@@ -91,7 +79,7 @@ describe('useWorkspacePluginInstallPermission', () => {
|
||||
})
|
||||
|
||||
it('should deny plugin capabilities without plugin install or manage permissions', () => {
|
||||
const { result } = renderHook(() => useWorkspacePluginInstallPermission())
|
||||
const { result } = renderPermissionHook()
|
||||
|
||||
expect(result.current.canInstallPlugin).toBe(false)
|
||||
expect(result.current.canUpdatePlugin).toBe(false)
|
||||
|
||||
+7
-3
@@ -1,11 +1,15 @@
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useMemo } from 'react'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
const useWorkspacePluginInstallPermission = () => {
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: currentVersion } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentVersion ?? '',
|
||||
})
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
const canInstallPlugin = useMemo(() => {
|
||||
@@ -34,7 +38,7 @@ const useWorkspacePluginInstallPermission = () => {
|
||||
canDeletePlugin,
|
||||
canDebugPlugin,
|
||||
canSetPluginPreferences,
|
||||
currentDifyVersion: langGeniusVersionInfo?.current_version,
|
||||
currentDifyVersion: currentVersion,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-8
@@ -1,7 +1,9 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import type { PluginDeclaration } from '../../../../types'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { render } from '@/test/console/render'
|
||||
import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
||||
import { render as renderWithConsoleState } from '@/test/console/render'
|
||||
import { PluginCategoryEnum, TaskStatus } from '../../../../types'
|
||||
import Install from '../install'
|
||||
|
||||
@@ -62,13 +64,14 @@ const mockConsoleState = vi.hoisted(() => ({
|
||||
langGeniusVersionInfo: { current_version: '1.0.0' as string | undefined },
|
||||
}))
|
||||
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
|
||||
return createVersionStateModuleMock(() => ({
|
||||
langGeniusVersionInfo: mockConsoleState.langGeniusVersionInfo,
|
||||
}))
|
||||
})
|
||||
const render = (ui: ReactElement) => {
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
accountProfileMeta: {
|
||||
currentVersion: mockConsoleState.langGeniusVersionInfo.current_version ?? null,
|
||||
},
|
||||
})
|
||||
return renderWithConsoleState(ui, { wrapper })
|
||||
}
|
||||
|
||||
vi.mock('../../../../card', () => ({
|
||||
default: ({
|
||||
|
||||
+9
-9
@@ -3,12 +3,12 @@ import type { FC } from 'react'
|
||||
import type { PluginDeclaration } from '../../../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { uninstallPlugin } from '@/service/plugins'
|
||||
import { useInstallPackageFromLocal, usePluginTaskList } from '@/service/use-plugins'
|
||||
import { isEqualOrLaterThanVersion } from '@/utils/semver'
|
||||
@@ -101,14 +101,14 @@ const Installed: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: currentVersion } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentVersion ?? '',
|
||||
})
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!langGeniusVersionInfo.current_version) return true
|
||||
return isEqualOrLaterThanVersion(
|
||||
langGeniusVersionInfo.current_version,
|
||||
payload.meta.minimum_dify_version ?? '0.0.0',
|
||||
)
|
||||
}, [langGeniusVersionInfo.current_version, payload.meta.minimum_dify_version])
|
||||
if (!currentVersion) return true
|
||||
return isEqualOrLaterThanVersion(currentVersion, payload.meta.minimum_dify_version ?? '0.0.0')
|
||||
}, [currentVersion, payload.meta.minimum_dify_version])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
+11
-8
@@ -1,8 +1,10 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import type { Plugin, PluginManifestInMarket } from '../../../../types'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { act } from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { render } from '@/test/console/render'
|
||||
import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
||||
import { render as renderWithConsoleState } from '@/test/console/render'
|
||||
import { PluginCategoryEnum, TaskStatus } from '../../../../types'
|
||||
import Install from '../install'
|
||||
|
||||
@@ -78,13 +80,14 @@ vi.mock('@/app/components/plugins/install-plugin/hooks/use-check-installed', ()
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
|
||||
return createVersionStateModuleMock(() => ({
|
||||
langGeniusVersionInfo: mockConsoleState.langGeniusVersionInfo,
|
||||
}))
|
||||
})
|
||||
const render = (ui: ReactElement) => {
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
accountProfileMeta: {
|
||||
currentVersion: mockConsoleState.langGeniusVersionInfo.current_version ?? null,
|
||||
},
|
||||
})
|
||||
return renderWithConsoleState(ui, { wrapper })
|
||||
}
|
||||
|
||||
// Mock service hooks
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
|
||||
+9
-6
@@ -3,12 +3,12 @@ import type { FC } from 'react'
|
||||
import type { InstallPackageResponse, Plugin, PluginManifestInMarket } from '../../../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import {
|
||||
useInstallPackageFromMarketPlace,
|
||||
usePluginDeclarationFromMarketPlace,
|
||||
@@ -126,15 +126,18 @@ const Installed: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: currentVersion } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentVersion ?? '',
|
||||
})
|
||||
const { data: pluginDeclaration } = usePluginDeclarationFromMarketPlace(uniqueIdentifier)
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!pluginDeclaration || !langGeniusVersionInfo.current_version) return true
|
||||
if (!pluginDeclaration || !currentVersion) return true
|
||||
return isEqualOrLaterThanVersion(
|
||||
langGeniusVersionInfo.current_version,
|
||||
currentVersion,
|
||||
pluginDeclaration?.manifest.meta.minimum_dify_version ?? '0.0.0',
|
||||
)
|
||||
}, [langGeniusVersionInfo.current_version, pluginDeclaration])
|
||||
}, [currentVersion, pluginDeclaration])
|
||||
|
||||
const { canInstall } = useInstallPluginLimit({
|
||||
...payload,
|
||||
|
||||
@@ -8,11 +8,6 @@ import PluginItem from '../index'
|
||||
|
||||
const mockEnableMarketplace = vi.fn(() => true)
|
||||
|
||||
const render = (ui: ReactElement) =>
|
||||
renderWithConsoleQuery(ui, {
|
||||
systemFeatures: { enable_marketplace: mockEnableMarketplace() },
|
||||
})
|
||||
|
||||
const mockTheme = vi.fn(() => 'light')
|
||||
vi.mock('@/hooks/use-theme', () => ({
|
||||
default: () => ({ theme: mockTheme() }),
|
||||
@@ -70,12 +65,13 @@ const createLangGeniusVersionInfo = (currentVersion: string) => ({
|
||||
version: '',
|
||||
})
|
||||
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => ({
|
||||
langGeniusVersionInfo: mockLangGeniusVersionInfo(),
|
||||
}))
|
||||
})
|
||||
const render = (ui: ReactElement) =>
|
||||
renderWithConsoleQuery(ui, {
|
||||
accountProfileMeta: {
|
||||
currentVersion: mockLangGeniusVersionInfo().current_version,
|
||||
},
|
||||
systemFeatures: { enable_marketplace: mockEnableMarketplace() },
|
||||
})
|
||||
|
||||
vi.mock('../action', () => ({
|
||||
default: ({ onDelete, pluginName }: { onDelete: () => void; pluginName: string }) => (
|
||||
|
||||
@@ -11,13 +11,12 @@ import {
|
||||
RiLoginCircleLine,
|
||||
} from '@remixicon/react'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import { API_PREFIX } from '@/config'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
@@ -81,15 +80,18 @@ const PluginItem: FC<Props> = ({
|
||||
return [PluginSource.github, PluginSource.marketplace].includes(source) ? author : ''
|
||||
}, [source, author])
|
||||
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: currentVersion } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentVersion ?? '',
|
||||
})
|
||||
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!langGeniusVersionInfo.current_version) return true
|
||||
if (!currentVersion) return true
|
||||
return isEqualOrLaterThanVersion(
|
||||
langGeniusVersionInfo.current_version,
|
||||
currentVersion,
|
||||
declarationMeta.minimum_dify_version ?? '0.0.0',
|
||||
)
|
||||
}, [declarationMeta.minimum_dify_version, langGeniusVersionInfo.current_version])
|
||||
}, [currentVersion, declarationMeta.minimum_dify_version])
|
||||
|
||||
const isDeprecated = useMemo(() => {
|
||||
return status === 'deleted' && !!deprecated_reason
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
usePluginAutoUpgradeSettings,
|
||||
usePluginPermissionSettings,
|
||||
} from '@/service/use-plugins'
|
||||
import { renderHookWithConsoleQuery as renderHook } from '@/test/console/query-data'
|
||||
import { renderHookWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { PermissionType, PluginCategoryEnum } from '../../types'
|
||||
import useReferenceSetting, { useCanInstallPluginFromMarketplace } from '../use-reference-setting'
|
||||
|
||||
@@ -39,6 +39,18 @@ const setConsoleState = (state: ConsoleStateFixture) => {
|
||||
}
|
||||
}
|
||||
|
||||
function renderHook<Result, Props = void>(
|
||||
callback: (props: Props) => Result,
|
||||
options: Parameters<typeof renderHookWithConsoleQuery<Result, Props>>[1] = {},
|
||||
) {
|
||||
return renderHookWithConsoleQuery(callback, {
|
||||
...options,
|
||||
accountProfileMeta: {
|
||||
currentVersion: mockConsoleState.langGeniusVersionInfo?.current_version ?? null,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
vi.mock('@/context/workspace-state', async () => {
|
||||
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createWorkspaceStateModuleMock(() => mockConsoleState)
|
||||
@@ -47,11 +59,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createPermissionStateModuleMock(() => mockConsoleState)
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => mockConsoleState)
|
||||
})
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
usePluginAutoUpgradeSettings: vi.fn(),
|
||||
usePluginPermissionSettings: vi.fn(),
|
||||
|
||||
@@ -8,12 +8,12 @@ import {
|
||||
workspacePermissionKeysAtom,
|
||||
workspacePermissionKeysLoadingAtom,
|
||||
} from '@/context/permission-state'
|
||||
import { langGeniusVersionInfoAtom } from '@/context/version-state'
|
||||
import {
|
||||
currentWorkspaceLoadingAtom,
|
||||
isCurrentWorkspaceManagerAtom,
|
||||
isCurrentWorkspaceOwnerAtom,
|
||||
} from '@/context/workspace-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import {
|
||||
useInvalidateReferenceSettings,
|
||||
@@ -49,10 +49,13 @@ export const usePluginSettingsAccess = () => {
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const { data: rbacEnabled } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: (s) => s.rbac_enabled,
|
||||
select: (data) => data.rbac_enabled,
|
||||
})
|
||||
const { data: currentVersion } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.meta.currentVersion ?? '',
|
||||
})
|
||||
const { canSetPermissions, canSetPluginPreferences } = useCanSetPluginSettings()
|
||||
const permissionQuery = usePluginPermissionSettings()
|
||||
@@ -95,7 +98,7 @@ export const usePluginSettingsAccess = () => {
|
||||
canManagement: canInstallPlugin,
|
||||
canDebugger: canDebugPlugin,
|
||||
canSetPermissions,
|
||||
currentDifyVersion: langGeniusVersionInfo?.current_version,
|
||||
currentDifyVersion: currentVersion,
|
||||
isPermissionLoading:
|
||||
permissionQuery.isLoading ||
|
||||
permissionQuery.isFetching ||
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
workspacePermissionKeysAtom,
|
||||
workspacePermissionKeysLoadingAtom,
|
||||
} from '../permission-state'
|
||||
import { langGeniusVersionInfoAtom } from '../version-state'
|
||||
import {
|
||||
currentWorkspaceAtom,
|
||||
currentWorkspaceLoadingAtom,
|
||||
@@ -207,7 +206,6 @@ function ConsoleBootstrapProbe() {
|
||||
const datasetDefaultPermissionKeys = useAtomValue(datasetDefaultPermissionKeysAtom)
|
||||
const isLoadingWorkspacePermissionKeys = useAtomValue(workspacePermissionKeysLoadingAtom)
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
const langGeniusVersionInfo = useAtomValue(langGeniusVersionInfoAtom)
|
||||
const refreshPermissionsAfterMutationDenial = useSetAtom(
|
||||
refreshWorkspacePermissionKeysAfterMutationDenialAtom,
|
||||
)
|
||||
@@ -254,11 +252,6 @@ function ConsoleBootstrapProbe() {
|
||||
dataset operator:
|
||||
{String(isCurrentWorkspaceDatasetOperator)}
|
||||
</span>
|
||||
<span>
|
||||
version:
|
||||
{langGeniusVersionInfo.current_version}/{langGeniusVersionInfo.latest_version}/
|
||||
{langGeniusVersionInfo.current_env}
|
||||
</span>
|
||||
<button type="button" onClick={() => void refreshPermissionsAfterMutationDenial()}>
|
||||
refresh permissions after denial
|
||||
</button>
|
||||
@@ -368,7 +361,7 @@ describe('Console bootstrap', () => {
|
||||
})
|
||||
|
||||
describe('Bootstrap atoms', () => {
|
||||
it('should provide profile, workspace, permissions, loading state, and version metadata', async () => {
|
||||
it('should provide workspace, permissions, and loading state', async () => {
|
||||
renderConsoleBootstrap()
|
||||
|
||||
expect(await screen.findByText('workspace:Workspace')).toBeInTheDocument()
|
||||
@@ -376,10 +369,9 @@ describe('Console bootstrap', () => {
|
||||
expect(screen.getByText('dataset keys:dataset.acl.edit')).toBeInTheDocument()
|
||||
expect(screen.getByText('permission loading:false')).toBeInTheDocument()
|
||||
expect(screen.getByText('workspace loading:false')).toBeInTheDocument()
|
||||
expect(await screen.findByText('version:1.0.0/1.0.1/cloud')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should fall back to placeholder values when workspace, permission, or version data is missing', async () => {
|
||||
it('should fall back to placeholder values when workspace or permission data is missing', async () => {
|
||||
mockCurrentWorkspaceQueryState.data = undefined
|
||||
mockPermissionKeysState.datasetPermissionKeys = []
|
||||
mockPermissionKeysState.permissionKeys = []
|
||||
@@ -393,7 +385,6 @@ describe('Console bootstrap', () => {
|
||||
expect(screen.getByText(`role:${initialWorkspaceSummary.role}`)).toBeInTheDocument()
|
||||
expect(screen.getByText('keys:')).toBeInTheDocument()
|
||||
expect(screen.getByText('dataset keys:')).toBeInTheDocument()
|
||||
expect(screen.getByText('version://')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should normalize invalid workspace roles to the initial workspace role', async () => {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { atom } from 'jotai'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { atomWithResolvedSuspenseQuery } from '@/utils/query-atoms'
|
||||
|
||||
const accountProfileQueryAtom = atomWithResolvedSuspenseQuery(() => userProfileQueryOptions())
|
||||
|
||||
export const accountProfileMetaAtom = atom((get) => {
|
||||
return get(accountProfileQueryAtom).data.meta
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { atom } from 'jotai'
|
||||
import { atomWithQuery } from 'jotai-tanstack-query'
|
||||
import { brandingEnabledAtom } from '@/features/system-features/state'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { accountProfileMetaAtom } from './account-state'
|
||||
import { initialLangGeniusVersionInfo } from './app-context-defaults'
|
||||
import { getLangGeniusVersionInfo } from './app-context-normalizers'
|
||||
|
||||
const versionQueryAtom = atomWithQuery((get) => {
|
||||
const meta = get(accountProfileMetaAtom)
|
||||
const brandingEnabled = get(brandingEnabledAtom)
|
||||
const enabled = Boolean(meta.currentVersion && !brandingEnabled)
|
||||
|
||||
return consoleQuery.version.get.queryOptions({
|
||||
input: {
|
||||
query: {
|
||||
current_version: meta.currentVersion ?? '',
|
||||
},
|
||||
},
|
||||
enabled,
|
||||
})
|
||||
})
|
||||
|
||||
export const langGeniusVersionInfoAtom = atom((get) => {
|
||||
const meta = get(accountProfileMetaAtom)
|
||||
const versionData = get(versionQueryAtom).data
|
||||
|
||||
if (!versionData) return initialLangGeniusVersionInfo
|
||||
|
||||
return getLangGeniusVersionInfo({
|
||||
meta,
|
||||
versionData,
|
||||
})
|
||||
})
|
||||
|
||||
export const langGeniusCurrentVersionAtom = atom((get) => {
|
||||
return get(langGeniusVersionInfoAtom).current_version
|
||||
})
|
||||
-16
@@ -75,22 +75,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
},
|
||||
}))
|
||||
})
|
||||
vi.mock('@/context/version-state', async () => {
|
||||
const { createVersionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createVersionStateModuleMock(() => ({
|
||||
userProfile: { id: 'user-1' },
|
||||
currentWorkspace: { id: 'workspace-1' },
|
||||
workspacePermissionKeys: ['app.acl.edit'],
|
||||
langGeniusVersionInfo: {
|
||||
current_env: 'PRODUCTION',
|
||||
current_version: '',
|
||||
latest_version: '',
|
||||
version: '',
|
||||
release_notes: '',
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
account: {
|
||||
|
||||
@@ -13,7 +13,3 @@ const systemFeaturesAtom = atom((get) => {
|
||||
export const deploymentEditionAtom = atom((get) => {
|
||||
return get(systemFeaturesAtom).deployment_edition
|
||||
})
|
||||
|
||||
export const brandingEnabledAtom = atom((get) => {
|
||||
return get(systemFeaturesAtom).branding.enabled
|
||||
})
|
||||
|
||||
@@ -7,6 +7,8 @@ import { render } from '@testing-library/react'
|
||||
import { createQueryClientWrapper } from '@/test/console/query-client'
|
||||
import { createTestQueryClient } from '@/test/query-client'
|
||||
|
||||
type AccountProfileMeta = UserProfileWithMeta['meta']
|
||||
|
||||
const accountProfileQueryKey = [
|
||||
['console', 'account', 'profile', 'get'],
|
||||
{ type: 'query' },
|
||||
@@ -27,19 +29,22 @@ const createAccountProfileFixture = (
|
||||
|
||||
const createMockUserProfileResponse = (
|
||||
profile: Partial<GetAccountProfileResponse> = {},
|
||||
meta: Partial<AccountProfileMeta> = {},
|
||||
): UserProfileWithMeta => ({
|
||||
profile: createAccountProfileFixture(profile),
|
||||
meta: {
|
||||
currentVersion: null,
|
||||
currentEnv: null,
|
||||
...meta,
|
||||
},
|
||||
})
|
||||
|
||||
export const seedAccountProfileQuery = (
|
||||
queryClient: QueryClient,
|
||||
profile: Partial<GetAccountProfileResponse> = {},
|
||||
meta: Partial<AccountProfileMeta> = {},
|
||||
) => {
|
||||
const data = createMockUserProfileResponse(profile)
|
||||
const data = createMockUserProfileResponse(profile, meta)
|
||||
queryClient.setQueryData(accountProfileQueryKey, data)
|
||||
return data
|
||||
}
|
||||
@@ -47,27 +52,30 @@ export const seedAccountProfileQuery = (
|
||||
export const ensureAccountProfileQuery = (
|
||||
queryClient: QueryClient,
|
||||
profile: Partial<GetAccountProfileResponse> = {},
|
||||
meta: Partial<AccountProfileMeta> = {},
|
||||
) => {
|
||||
const existingProfile = queryClient.getQueryData<UserProfileWithMeta>(accountProfileQueryKey)
|
||||
if (existingProfile === undefined) return seedAccountProfileQuery(queryClient, profile)
|
||||
if (existingProfile === undefined) return seedAccountProfileQuery(queryClient, profile, meta)
|
||||
|
||||
return existingProfile
|
||||
}
|
||||
|
||||
export const createAccountProfileQueryClient = (
|
||||
profile: Partial<GetAccountProfileResponse> = {},
|
||||
meta: Partial<AccountProfileMeta> = {},
|
||||
) => {
|
||||
const queryClient = createTestQueryClient()
|
||||
|
||||
seedAccountProfileQuery(queryClient, profile)
|
||||
seedAccountProfileQuery(queryClient, profile, meta)
|
||||
|
||||
return queryClient
|
||||
}
|
||||
|
||||
export const createAccountProfileQueryWrapper = (
|
||||
profile: Partial<GetAccountProfileResponse> = {},
|
||||
meta: Partial<AccountProfileMeta> = {},
|
||||
) => {
|
||||
const queryClient = createAccountProfileQueryClient(profile)
|
||||
const queryClient = createAccountProfileQueryClient(profile, meta)
|
||||
return createQueryClientWrapper(queryClient)
|
||||
}
|
||||
|
||||
@@ -75,11 +83,12 @@ export const renderWithAccountProfile = (
|
||||
ui: ReactElement,
|
||||
options: Omit<RenderOptions, 'wrapper'> & {
|
||||
accountProfile?: Partial<GetAccountProfileResponse>
|
||||
accountProfileMeta?: Partial<AccountProfileMeta>
|
||||
} = {},
|
||||
) => {
|
||||
const { accountProfile, ...renderOptions } = options
|
||||
const { accountProfile, accountProfileMeta, ...renderOptions } = options
|
||||
return render(ui, {
|
||||
...renderOptions,
|
||||
wrapper: createAccountProfileQueryWrapper(accountProfile),
|
||||
wrapper: createAccountProfileQueryWrapper(accountProfile, accountProfileMeta),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
RenderResult,
|
||||
} from '@testing-library/react'
|
||||
import type { ReactElement, ReactNode } from 'react'
|
||||
import type { UserProfileWithMeta } from '@/features/account-profile/client'
|
||||
import type { DeepPartial } from '@/test/console/system-features'
|
||||
import { zGetFeaturesResponse } from '@dify/contracts/api/console/features/zod.gen'
|
||||
import { render, renderHook } from '@testing-library/react'
|
||||
@@ -170,6 +171,7 @@ export type ConsoleQueryTestOptions = {
|
||||
*/
|
||||
systemFeatures?: DeepPartial<GetSystemFeaturesResponse> | null
|
||||
accountProfile?: Partial<GetAccountProfileResponse> | null
|
||||
accountProfileMeta?: Partial<UserProfileWithMeta['meta']>
|
||||
educationStatus?: Partial<EducationStatusResponse>
|
||||
currentWorkspace?: Partial<GetWorkspacesCurrentSummaryResponse> | null
|
||||
trialModels?: readonly string[] | null
|
||||
@@ -193,8 +195,9 @@ export const createConsoleQueryWrapper = (
|
||||
): ConsoleQueryWrapper => {
|
||||
const queryClient = options.queryClient ?? createConsoleQueryClient()
|
||||
if (options.accountProfile !== null) {
|
||||
if (options.accountProfile) seedAccountProfileQuery(queryClient, options.accountProfile)
|
||||
else ensureAccountProfileQuery(queryClient, { timezone: 'UTC' })
|
||||
if (options.accountProfile)
|
||||
seedAccountProfileQuery(queryClient, options.accountProfile, options.accountProfileMeta)
|
||||
else ensureAccountProfileQuery(queryClient, { timezone: 'UTC' }, options.accountProfileMeta)
|
||||
}
|
||||
if (options.educationStatus) seedEducationStatus(queryClient, options.educationStatus)
|
||||
if (options.currentWorkspace !== null) {
|
||||
@@ -235,6 +238,7 @@ export const renderWithConsoleQuery = (
|
||||
const {
|
||||
systemFeatures: sf,
|
||||
accountProfile,
|
||||
accountProfileMeta,
|
||||
educationStatus,
|
||||
currentWorkspace,
|
||||
trialModels,
|
||||
@@ -246,6 +250,7 @@ export const renderWithConsoleQuery = (
|
||||
const { wrapper, queryClient, systemFeatures } = createConsoleQueryWrapper({
|
||||
systemFeatures: sf,
|
||||
accountProfile,
|
||||
accountProfileMeta,
|
||||
educationStatus,
|
||||
currentWorkspace,
|
||||
trialModels,
|
||||
@@ -267,6 +272,7 @@ export const renderHookWithConsoleQuery = <Result, Props = void>(
|
||||
const {
|
||||
systemFeatures: sf,
|
||||
accountProfile,
|
||||
accountProfileMeta,
|
||||
educationStatus,
|
||||
currentWorkspace,
|
||||
trialModels,
|
||||
@@ -278,6 +284,7 @@ export const renderHookWithConsoleQuery = <Result, Props = void>(
|
||||
const { wrapper, queryClient, systemFeatures } = createConsoleQueryWrapper({
|
||||
systemFeatures: sf,
|
||||
accountProfile,
|
||||
accountProfileMeta,
|
||||
educationStatus,
|
||||
currentWorkspace,
|
||||
trialModels,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { GetWorkspacesCurrentSummaryResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { createStore } from 'jotai'
|
||||
import type { LangGeniusVersionInfo } from '@/context/app-context-types'
|
||||
import { atom } from 'jotai'
|
||||
import { createSystemFeaturesFixture } from '@/test/console/system-features'
|
||||
|
||||
@@ -25,13 +24,12 @@ export type ConsoleStateFixture = {
|
||||
knowledgeFsEnabled?: boolean
|
||||
deploymentEdition?: 'COMMUNITY' | 'ENTERPRISE' | 'CLOUD'
|
||||
brandingEnabled?: boolean
|
||||
langGeniusVersionInfo?: Partial<LangGeniusVersionInfo>
|
||||
refreshCurrentWorkspace?: () => void
|
||||
}
|
||||
|
||||
type ConsoleStateFixtureResolver = () => ConsoleStateFixture
|
||||
type JotaiStore = ReturnType<typeof createStore>
|
||||
type ConsoleStateOwner = 'workspace' | 'permission' | 'systemFeatures' | 'version'
|
||||
type ConsoleStateOwner = 'workspace' | 'permission' | 'systemFeatures'
|
||||
|
||||
const defaultCurrentWorkspace = {
|
||||
id: 'workspace-1',
|
||||
@@ -41,14 +39,6 @@ const defaultCurrentWorkspace = {
|
||||
role: 'owner',
|
||||
} satisfies GetWorkspacesCurrentSummaryResponse
|
||||
|
||||
const defaultLangGeniusVersionInfo = {
|
||||
current_env: 'CLOUD',
|
||||
current_version: '',
|
||||
latest_version: '',
|
||||
version: '',
|
||||
release_notes: '',
|
||||
} satisfies LangGeniusVersionInfo
|
||||
|
||||
const currentWorkspaceAtom = atom<GetWorkspacesCurrentSummaryResponse>(defaultCurrentWorkspace)
|
||||
const currentWorkspaceIdAtom = atom((get) => get(currentWorkspaceAtom).id)
|
||||
const isCurrentWorkspaceManagerAtom = atom(false)
|
||||
@@ -68,9 +58,6 @@ const systemFeaturesAtom = atom(createSystemFeaturesFixture())
|
||||
const deploymentEditionAtom = atom((get) => get(systemFeaturesAtom).deployment_edition)
|
||||
const brandingEnabledAtom = atom((get) => get(systemFeaturesAtom).branding.enabled)
|
||||
|
||||
const langGeniusVersionInfoAtom = atom<LangGeniusVersionInfo>(defaultLangGeniusVersionInfo)
|
||||
const langGeniusCurrentVersionAtom = atom((get) => get(langGeniusVersionInfoAtom).current_version)
|
||||
|
||||
const consoleStateFixtureResolvers: Partial<
|
||||
Record<ConsoleStateOwner, ConsoleStateFixtureResolver>
|
||||
> = {}
|
||||
@@ -109,10 +96,6 @@ export const seedRegisteredConsoleStateFixture = (store: JotaiStore) => {
|
||||
},
|
||||
}),
|
||||
)
|
||||
store.set(langGeniusVersionInfoAtom, {
|
||||
...defaultLangGeniusVersionInfo,
|
||||
...state.langGeniusVersionInfo,
|
||||
})
|
||||
store.set(refreshCurrentWorkspaceCallbackAtom, {
|
||||
callback: state.refreshCurrentWorkspace ?? (() => {}),
|
||||
})
|
||||
@@ -169,13 +152,3 @@ export const createSystemFeaturesStateModuleMock = (getState: ConsoleStateFixtur
|
||||
brandingEnabledAtom,
|
||||
}
|
||||
}
|
||||
|
||||
export const createVersionStateModuleMock = (getState: ConsoleStateFixtureResolver) => {
|
||||
registerConsoleStateFixture('version', () => ({
|
||||
langGeniusVersionInfo: getState().langGeniusVersionInfo,
|
||||
}))
|
||||
return {
|
||||
langGeniusVersionInfoAtom,
|
||||
langGeniusCurrentVersionAtom,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user