mirror of
https://github.com/langgenius/dify.git
synced 2026-06-03 08:16:37 +08:00
chore: not request system-features for cloud edition (#36891)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
@@ -92,3 +92,18 @@ NEXT_PUBLIC_AMPLITUDE_API_KEY=
|
||||
|
||||
# number of concurrency
|
||||
NEXT_PUBLIC_BATCH_CONCURRENCY=5
|
||||
|
||||
# Cloud system-features frontend defaults.
|
||||
# These values are only used when NEXT_PUBLIC_EDITION=CLOUD (IS_CLOUD_EDITION).
|
||||
NEXT_PUBLIC_ENABLE_MARKETPLACE=true
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_CODE_LOGIN=true
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN=false
|
||||
NEXT_PUBLIC_ENABLE_SOCIAL_OAUTH_LOGIN=true
|
||||
NEXT_PUBLIC_ENABLE_COLLABORATION_MODE=false
|
||||
NEXT_PUBLIC_ALLOW_REGISTER=true
|
||||
NEXT_PUBLIC_ALLOW_CREATE_WORKSPACE=true
|
||||
NEXT_PUBLIC_IS_EMAIL_SETUP=true
|
||||
NEXT_PUBLIC_ENABLE_CHANGE_EMAIL=true
|
||||
NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED=true
|
||||
NEXT_PUBLIC_ENABLE_TRIAL_APP=true
|
||||
NEXT_PUBLIC_ENABLE_EXPLORE_BANNER=true
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { env } from '@/env'
|
||||
import { defaultSystemFeatures, InstallationScope, LicenseStatus } from '@/types/feature'
|
||||
|
||||
export const cloudSystemFeatures: SystemFeatures = {
|
||||
...defaultSystemFeatures,
|
||||
sso_enforced_for_signin: false,
|
||||
sso_enforced_for_signin_protocol: '',
|
||||
|
||||
enable_marketplace: env.NEXT_PUBLIC_ENABLE_MARKETPLACE,
|
||||
enable_email_code_login: env.NEXT_PUBLIC_ENABLE_EMAIL_CODE_LOGIN,
|
||||
enable_email_password_login: env.NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN,
|
||||
enable_social_oauth_login: env.NEXT_PUBLIC_ENABLE_SOCIAL_OAUTH_LOGIN,
|
||||
enable_collaboration_mode: env.NEXT_PUBLIC_ENABLE_COLLABORATION_MODE,
|
||||
is_allow_register: env.NEXT_PUBLIC_ALLOW_REGISTER,
|
||||
is_allow_create_workspace: env.NEXT_PUBLIC_ALLOW_CREATE_WORKSPACE,
|
||||
is_email_setup: env.NEXT_PUBLIC_IS_EMAIL_SETUP,
|
||||
enable_change_email: env.NEXT_PUBLIC_ENABLE_CHANGE_EMAIL,
|
||||
|
||||
license: {
|
||||
...defaultSystemFeatures.license,
|
||||
status: LicenseStatus.NONE,
|
||||
expired_at: '',
|
||||
},
|
||||
|
||||
branding: {
|
||||
enabled: false,
|
||||
application_title: '',
|
||||
login_page_logo: '',
|
||||
workspace_logo: '',
|
||||
favicon: '',
|
||||
},
|
||||
|
||||
webapp_auth: {
|
||||
enabled: false,
|
||||
allow_sso: false,
|
||||
sso_config: {
|
||||
protocol: '',
|
||||
},
|
||||
allow_email_code_login: false,
|
||||
allow_email_password_login: false,
|
||||
},
|
||||
|
||||
plugin_installation_permission: {
|
||||
plugin_installation_scope: InstallationScope.ALL,
|
||||
restrict_to_marketplace_only: false,
|
||||
},
|
||||
|
||||
enable_creators_platform: env.NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED,
|
||||
enable_trial_app: env.NEXT_PUBLIC_ENABLE_TRIAL_APP,
|
||||
enable_explore_banner: env.NEXT_PUBLIC_ENABLE_EXPLORE_BANNER,
|
||||
}
|
||||
@@ -28,6 +28,21 @@ export NEXT_TELEMETRY_DISABLED=${NEXT_TELEMETRY_DISABLED}
|
||||
|
||||
export NEXT_PUBLIC_AMPLITUDE_API_KEY=${AMPLITUDE_API_KEY}
|
||||
|
||||
# Cloud system-features frontend defaults.
|
||||
# These values are only used when EDITION=CLOUD (IS_CLOUD_EDITION).
|
||||
export NEXT_PUBLIC_ENABLE_MARKETPLACE=${NEXT_PUBLIC_ENABLE_MARKETPLACE:-${MARKETPLACE_ENABLED}}
|
||||
export NEXT_PUBLIC_ENABLE_EMAIL_CODE_LOGIN=${NEXT_PUBLIC_ENABLE_EMAIL_CODE_LOGIN:-${ENABLE_EMAIL_CODE_LOGIN}}
|
||||
export NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN=${NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN:-${ENABLE_EMAIL_PASSWORD_LOGIN}}
|
||||
export NEXT_PUBLIC_ENABLE_SOCIAL_OAUTH_LOGIN=${NEXT_PUBLIC_ENABLE_SOCIAL_OAUTH_LOGIN:-${ENABLE_SOCIAL_OAUTH_LOGIN}}
|
||||
export NEXT_PUBLIC_ENABLE_COLLABORATION_MODE=${NEXT_PUBLIC_ENABLE_COLLABORATION_MODE:-${ENABLE_COLLABORATION_MODE}}
|
||||
export NEXT_PUBLIC_ALLOW_REGISTER=${NEXT_PUBLIC_ALLOW_REGISTER:-${ALLOW_REGISTER}}
|
||||
export NEXT_PUBLIC_ALLOW_CREATE_WORKSPACE=${NEXT_PUBLIC_ALLOW_CREATE_WORKSPACE:-${ALLOW_CREATE_WORKSPACE}}
|
||||
export NEXT_PUBLIC_IS_EMAIL_SETUP=${NEXT_PUBLIC_IS_EMAIL_SETUP:-${IS_EMAIL_SETUP}}
|
||||
export NEXT_PUBLIC_ENABLE_CHANGE_EMAIL=${NEXT_PUBLIC_ENABLE_CHANGE_EMAIL:-${ENABLE_CHANGE_EMAIL}}
|
||||
export NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED=${NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED:-${CREATORS_PLATFORM_FEATURES_ENABLED}}
|
||||
export NEXT_PUBLIC_ENABLE_TRIAL_APP=${NEXT_PUBLIC_ENABLE_TRIAL_APP:-${ENABLE_TRIAL_APP}}
|
||||
export NEXT_PUBLIC_ENABLE_EXPLORE_BANNER=${NEXT_PUBLIC_ENABLE_EXPLORE_BANNER:-${ENABLE_EXPLORE_BANNER}}
|
||||
|
||||
export NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS=${TEXT_GENERATION_TIMEOUT_MS}
|
||||
export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST}
|
||||
export NEXT_PUBLIC_ALLOW_EMBED=${ALLOW_EMBED}
|
||||
|
||||
+37
-1
@@ -13,7 +13,7 @@ const coercedBoolean = z.string()
|
||||
.transform(s => s === 'true' || s === '1')
|
||||
const coercedNumber = z.coerce.number().int().positive()
|
||||
|
||||
/// keep-sorted
|
||||
/// Keep keys sorted except grouped feature-specific blocks.
|
||||
const clientSchema = {
|
||||
/**
|
||||
* Default is not allow to embed into iframe to prevent Clickjacking: https://owasp.org/www-community/attacks/Clickjacking
|
||||
@@ -63,6 +63,24 @@ const clientSchema = {
|
||||
* The deployment edition, SELF_HOSTED
|
||||
*/
|
||||
NEXT_PUBLIC_EDITION: z.enum(['SELF_HOSTED', 'CLOUD']).default('SELF_HOSTED'),
|
||||
|
||||
/**
|
||||
* Cloud-only system-features defaults.
|
||||
* These values are only used when NEXT_PUBLIC_EDITION=CLOUD (IS_CLOUD_EDITION).
|
||||
*/
|
||||
NEXT_PUBLIC_ENABLE_MARKETPLACE: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_CODE_LOGIN: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN: coercedBoolean.default(false),
|
||||
NEXT_PUBLIC_ENABLE_SOCIAL_OAUTH_LOGIN: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_ENABLE_COLLABORATION_MODE: coercedBoolean.default(false),
|
||||
NEXT_PUBLIC_ALLOW_REGISTER: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_ALLOW_CREATE_WORKSPACE: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_IS_EMAIL_SETUP: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_ENABLE_CHANGE_EMAIL: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_ENABLE_TRIAL_APP: coercedBoolean.default(true),
|
||||
NEXT_PUBLIC_ENABLE_EXPLORE_BANNER: coercedBoolean.default(true),
|
||||
|
||||
/**
|
||||
* Enable inline LaTeX rendering with single dollar signs ($...$)
|
||||
* Default is false for security reasons to prevent conflicts with regular text
|
||||
@@ -171,6 +189,24 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_DEPLOY_ENV: isServer ? process.env.NEXT_PUBLIC_DEPLOY_ENV : getRuntimeEnvFromBody('deployEnv'),
|
||||
NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON: isServer ? process.env.NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON : getRuntimeEnvFromBody('disableUploadImageAsIcon'),
|
||||
NEXT_PUBLIC_EDITION: isServer ? process.env.NEXT_PUBLIC_EDITION : getRuntimeEnvFromBody('edition'),
|
||||
|
||||
/**
|
||||
* Cloud-only system-features defaults.
|
||||
* These values are only used when NEXT_PUBLIC_EDITION=CLOUD (IS_CLOUD_EDITION).
|
||||
*/
|
||||
NEXT_PUBLIC_ENABLE_MARKETPLACE: isServer ? process.env.NEXT_PUBLIC_ENABLE_MARKETPLACE : getRuntimeEnvFromBody('enableMarketplace'),
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_CODE_LOGIN: isServer ? process.env.NEXT_PUBLIC_ENABLE_EMAIL_CODE_LOGIN : getRuntimeEnvFromBody('enableEmailCodeLogin'),
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN: isServer ? process.env.NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN : getRuntimeEnvFromBody('enableEmailPasswordLogin'),
|
||||
NEXT_PUBLIC_ENABLE_SOCIAL_OAUTH_LOGIN: isServer ? process.env.NEXT_PUBLIC_ENABLE_SOCIAL_OAUTH_LOGIN : getRuntimeEnvFromBody('enableSocialOauthLogin'),
|
||||
NEXT_PUBLIC_ENABLE_COLLABORATION_MODE: isServer ? process.env.NEXT_PUBLIC_ENABLE_COLLABORATION_MODE : getRuntimeEnvFromBody('enableCollaborationMode'),
|
||||
NEXT_PUBLIC_ALLOW_REGISTER: isServer ? process.env.NEXT_PUBLIC_ALLOW_REGISTER : getRuntimeEnvFromBody('allowRegister'),
|
||||
NEXT_PUBLIC_ALLOW_CREATE_WORKSPACE: isServer ? process.env.NEXT_PUBLIC_ALLOW_CREATE_WORKSPACE : getRuntimeEnvFromBody('allowCreateWorkspace'),
|
||||
NEXT_PUBLIC_IS_EMAIL_SETUP: isServer ? process.env.NEXT_PUBLIC_IS_EMAIL_SETUP : getRuntimeEnvFromBody('isEmailSetup'),
|
||||
NEXT_PUBLIC_ENABLE_CHANGE_EMAIL: isServer ? process.env.NEXT_PUBLIC_ENABLE_CHANGE_EMAIL : getRuntimeEnvFromBody('enableChangeEmail'),
|
||||
NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED: isServer ? process.env.NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED : getRuntimeEnvFromBody('creatorsPlatformFeaturesEnabled'),
|
||||
NEXT_PUBLIC_ENABLE_TRIAL_APP: isServer ? process.env.NEXT_PUBLIC_ENABLE_TRIAL_APP : getRuntimeEnvFromBody('enableTrialApp'),
|
||||
NEXT_PUBLIC_ENABLE_EXPLORE_BANNER: isServer ? process.env.NEXT_PUBLIC_ENABLE_EXPLORE_BANNER : getRuntimeEnvFromBody('enableExploreBanner'),
|
||||
|
||||
NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX: isServer ? process.env.NEXT_PUBLIC_ENABLE_SINGLE_DOLLAR_LATEX : getRuntimeEnvFromBody('enableSingleDollarLatex'),
|
||||
NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL: isServer ? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL : getRuntimeEnvFromBody('enableWebsiteFirecrawl'),
|
||||
NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER: isServer ? process.env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER : getRuntimeEnvFromBody('enableWebsiteJinareader'),
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { defaultSystemFeatures } from '@/types/feature'
|
||||
|
||||
type LoadOptions = {
|
||||
cloudEnv?: Partial<typeof defaultCloudEnv>
|
||||
isCloudEdition: boolean
|
||||
systemFeaturesResult?: SystemFeatures
|
||||
systemFeaturesError?: Error
|
||||
}
|
||||
|
||||
const defaultCloudEnv = {
|
||||
NEXT_PUBLIC_ALLOW_CREATE_WORKSPACE: true,
|
||||
NEXT_PUBLIC_ALLOW_REGISTER: true,
|
||||
NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED: true,
|
||||
NEXT_PUBLIC_ENABLE_CHANGE_EMAIL: true,
|
||||
NEXT_PUBLIC_ENABLE_COLLABORATION_MODE: false,
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_CODE_LOGIN: true,
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN: false,
|
||||
NEXT_PUBLIC_ENABLE_EXPLORE_BANNER: true,
|
||||
NEXT_PUBLIC_ENABLE_MARKETPLACE: true,
|
||||
NEXT_PUBLIC_ENABLE_SOCIAL_OAUTH_LOGIN: true,
|
||||
NEXT_PUBLIC_ENABLE_TRIAL_APP: true,
|
||||
NEXT_PUBLIC_IS_EMAIL_SETUP: true,
|
||||
}
|
||||
|
||||
const queryKey = ['console', 'systemFeatures'] as const
|
||||
const queryContext = {
|
||||
queryKey,
|
||||
signal: new AbortController().signal,
|
||||
meta: undefined,
|
||||
} as never
|
||||
|
||||
const loadSystemFeaturesModule = async ({
|
||||
cloudEnv,
|
||||
isCloudEdition,
|
||||
systemFeaturesResult = defaultSystemFeatures,
|
||||
systemFeaturesError,
|
||||
}: LoadOptions) => {
|
||||
vi.resetModules()
|
||||
|
||||
const systemFeatures = systemFeaturesError
|
||||
? vi.fn().mockRejectedValue(systemFeaturesError)
|
||||
: vi.fn().mockResolvedValue(systemFeaturesResult)
|
||||
|
||||
vi.doMock('@/config', () => ({
|
||||
IS_CLOUD_EDITION: isCloudEdition,
|
||||
}))
|
||||
vi.doMock('@/env', () => ({
|
||||
env: {
|
||||
...defaultCloudEnv,
|
||||
...cloudEnv,
|
||||
},
|
||||
}))
|
||||
vi.doMock('../client', () => ({
|
||||
consoleClient: {
|
||||
systemFeatures,
|
||||
},
|
||||
consoleQuery: {
|
||||
systemFeatures: {
|
||||
queryKey: () => queryKey,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const module = await import('../system-features')
|
||||
|
||||
return {
|
||||
module,
|
||||
systemFeatures,
|
||||
}
|
||||
}
|
||||
|
||||
const loadServerSystemFeaturesModule = async ({
|
||||
cloudEnv,
|
||||
isCloudEdition,
|
||||
systemFeaturesResult = defaultSystemFeatures,
|
||||
systemFeaturesError,
|
||||
}: LoadOptions) => {
|
||||
vi.resetModules()
|
||||
|
||||
const getServerConsoleClientContext = vi.fn().mockResolvedValue({ cookie: 'session=1' })
|
||||
const systemFeatures = systemFeaturesError
|
||||
? vi.fn().mockRejectedValue(systemFeaturesError)
|
||||
: vi.fn().mockResolvedValue(systemFeaturesResult)
|
||||
|
||||
vi.doMock('@/config', () => ({
|
||||
IS_CLOUD_EDITION: isCloudEdition,
|
||||
}))
|
||||
vi.doMock('@/env', () => ({
|
||||
env: {
|
||||
...defaultCloudEnv,
|
||||
...cloudEnv,
|
||||
},
|
||||
}))
|
||||
vi.doMock('../server', () => ({
|
||||
getServerConsoleClientContext,
|
||||
serverConsoleClient: {
|
||||
systemFeatures,
|
||||
},
|
||||
serverConsoleQuery: {
|
||||
systemFeatures: {
|
||||
queryKey: () => queryKey,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const module = await import('../server-system-features')
|
||||
|
||||
return {
|
||||
getServerConsoleClientContext,
|
||||
module,
|
||||
systemFeatures,
|
||||
}
|
||||
}
|
||||
|
||||
describe('systemFeaturesQueryOptions', () => {
|
||||
it('should return Cloud defaults without calling system-features when Cloud edition is enabled', async () => {
|
||||
const { module, systemFeatures } = await loadSystemFeaturesModule({
|
||||
isCloudEdition: true,
|
||||
})
|
||||
|
||||
const options = module.systemFeaturesQueryOptions()
|
||||
const data = await options.queryFn?.(queryContext)
|
||||
|
||||
expect(systemFeatures).not.toHaveBeenCalled()
|
||||
expect(options.staleTime).toBe(Infinity)
|
||||
expect(data).toMatchObject({
|
||||
enable_marketplace: true,
|
||||
enable_email_code_login: true,
|
||||
enable_email_password_login: false,
|
||||
enable_social_oauth_login: true,
|
||||
enable_trial_app: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('should use Cloud environment flags with local defaults for fixed fields', async () => {
|
||||
const { module } = await loadSystemFeaturesModule({
|
||||
isCloudEdition: true,
|
||||
cloudEnv: {
|
||||
NEXT_PUBLIC_ENABLE_MARKETPLACE: false,
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN: true,
|
||||
NEXT_PUBLIC_ENABLE_COLLABORATION_MODE: true,
|
||||
NEXT_PUBLIC_ALLOW_REGISTER: false,
|
||||
NEXT_PUBLIC_ENABLE_EXPLORE_BANNER: false,
|
||||
},
|
||||
})
|
||||
|
||||
const options = module.systemFeaturesQueryOptions()
|
||||
const data = await options.queryFn?.(queryContext)
|
||||
|
||||
expect(data).toMatchObject({
|
||||
enable_marketplace: false,
|
||||
enable_email_password_login: true,
|
||||
enable_collaboration_mode: true,
|
||||
is_allow_register: false,
|
||||
enable_explore_banner: false,
|
||||
branding: {
|
||||
enabled: false,
|
||||
application_title: '',
|
||||
favicon: '',
|
||||
},
|
||||
license: {
|
||||
status: 'none',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should fetch system-features when Cloud edition is disabled', async () => {
|
||||
const systemFeaturesResult = {
|
||||
...defaultSystemFeatures,
|
||||
enable_marketplace: true,
|
||||
}
|
||||
const { module, systemFeatures } = await loadSystemFeaturesModule({
|
||||
isCloudEdition: false,
|
||||
systemFeaturesResult,
|
||||
})
|
||||
|
||||
const options = module.systemFeaturesQueryOptions()
|
||||
const data = await options.queryFn?.(queryContext)
|
||||
|
||||
expect(systemFeatures).toHaveBeenCalledTimes(1)
|
||||
expect(data).toBe(systemFeaturesResult)
|
||||
})
|
||||
|
||||
it('should fall back to defaults when the non-Cloud request fails', async () => {
|
||||
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const { module, systemFeatures } = await loadSystemFeaturesModule({
|
||||
isCloudEdition: false,
|
||||
systemFeaturesError: new Error('network failed'),
|
||||
})
|
||||
|
||||
const options = module.systemFeaturesQueryOptions()
|
||||
const data = await options.queryFn?.(queryContext)
|
||||
|
||||
expect(systemFeatures).toHaveBeenCalledTimes(1)
|
||||
expect(data).toEqual(defaultSystemFeatures)
|
||||
|
||||
errorSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('serverSystemFeaturesQueryOptions', () => {
|
||||
it('should prefetch Cloud defaults without calling server system-features when Cloud edition is enabled', async () => {
|
||||
const { getServerConsoleClientContext, module, systemFeatures } = await loadServerSystemFeaturesModule({
|
||||
isCloudEdition: true,
|
||||
cloudEnv: {
|
||||
NEXT_PUBLIC_ENABLE_MARKETPLACE: false,
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_PASSWORD_LOGIN: true,
|
||||
},
|
||||
})
|
||||
|
||||
const options = module.serverSystemFeaturesQueryOptions()
|
||||
const data = await options.queryFn?.(queryContext)
|
||||
|
||||
expect(getServerConsoleClientContext).not.toHaveBeenCalled()
|
||||
expect(systemFeatures).not.toHaveBeenCalled()
|
||||
expect(options.staleTime).toBe(Infinity)
|
||||
expect(data).toMatchObject({
|
||||
enable_marketplace: false,
|
||||
enable_email_password_login: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('should fetch server system-features when Cloud edition is disabled', async () => {
|
||||
const systemFeaturesResult = {
|
||||
...defaultSystemFeatures,
|
||||
enable_marketplace: true,
|
||||
}
|
||||
const { getServerConsoleClientContext, module, systemFeatures } = await loadServerSystemFeaturesModule({
|
||||
isCloudEdition: false,
|
||||
systemFeaturesResult,
|
||||
})
|
||||
|
||||
const options = module.serverSystemFeaturesQueryOptions()
|
||||
const data = await options.queryFn?.(queryContext)
|
||||
|
||||
expect(getServerConsoleClientContext).toHaveBeenCalledTimes(1)
|
||||
expect(systemFeatures).toHaveBeenCalledTimes(1)
|
||||
expect(systemFeatures).toHaveBeenCalledWith(undefined, {
|
||||
context: { cookie: 'session=1' },
|
||||
})
|
||||
expect(data).toBe(systemFeaturesResult)
|
||||
})
|
||||
|
||||
it('should fall back to defaults when the non-Cloud server request fails', async () => {
|
||||
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const { module, systemFeatures } = await loadServerSystemFeaturesModule({
|
||||
isCloudEdition: false,
|
||||
systemFeaturesError: new Error('server failed'),
|
||||
})
|
||||
|
||||
const options = module.serverSystemFeaturesQueryOptions()
|
||||
const data = await options.queryFn?.(queryContext)
|
||||
|
||||
expect(systemFeatures).toHaveBeenCalledTimes(1)
|
||||
expect(data).toEqual(defaultSystemFeatures)
|
||||
|
||||
errorSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { cloudSystemFeatures } from '@/config/cloud-system-features'
|
||||
import { defaultSystemFeatures } from '@/types/feature'
|
||||
import {
|
||||
getServerConsoleClientContext,
|
||||
@@ -7,9 +9,19 @@ import {
|
||||
serverConsoleQuery,
|
||||
} from './server'
|
||||
|
||||
export const serverSystemFeaturesQueryOptions = () =>
|
||||
queryOptions<SystemFeatures>({
|
||||
queryKey: serverConsoleQuery.systemFeatures.queryKey(),
|
||||
export const serverSystemFeaturesQueryOptions = () => {
|
||||
const queryKey = serverConsoleQuery.systemFeatures.queryKey()
|
||||
|
||||
if (IS_CLOUD_EDITION) {
|
||||
return queryOptions<SystemFeatures>({
|
||||
queryKey,
|
||||
queryFn: async () => cloudSystemFeatures,
|
||||
staleTime: Infinity,
|
||||
})
|
||||
}
|
||||
|
||||
return queryOptions<SystemFeatures>({
|
||||
queryKey,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
return await serverConsoleClient.systemFeatures(undefined, {
|
||||
@@ -22,3 +34,4 @@ export const serverSystemFeaturesQueryOptions = () =>
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { cloudSystemFeatures } from '@/config/cloud-system-features'
|
||||
import { defaultSystemFeatures } from '@/types/feature'
|
||||
import { consoleClient, consoleQuery } from './client'
|
||||
|
||||
@@ -12,15 +14,24 @@ import { consoleClient, consoleQuery } from './client'
|
||||
* availability via defaults; the trade-off is acceptable because /system-features
|
||||
* is a small, dependency-free endpoint in the community edition.
|
||||
*
|
||||
* No `staleTime` override either: inherit the 5-minute default from
|
||||
* query-client-server.ts. Combined with `refetchOnWindowFocus`, this lets us
|
||||
* recover from a transient startup failure (which got cached as "successful
|
||||
* defaults") within ~5 minutes or on tab focus. `staleTime: Infinity` would
|
||||
* pin the whole tab to defaults until reload — strictly worse than main.
|
||||
* For Cloud, this query is intentionally local-only and uses `staleTime:
|
||||
* Infinity`: the payload comes from frontend config/defaults, so refetching
|
||||
* would only re-run the same local merge. For non-Cloud, do not override
|
||||
* `staleTime`: inherit the 5-minute default from query-client-server.ts.
|
||||
*/
|
||||
export const systemFeaturesQueryOptions = () =>
|
||||
queryOptions<SystemFeatures>({
|
||||
queryKey: consoleQuery.systemFeatures.queryKey(),
|
||||
export const systemFeaturesQueryOptions = () => {
|
||||
const queryKey = consoleQuery.systemFeatures.queryKey()
|
||||
|
||||
if (IS_CLOUD_EDITION) {
|
||||
return queryOptions<SystemFeatures>({
|
||||
queryKey,
|
||||
queryFn: async () => cloudSystemFeatures,
|
||||
staleTime: Infinity,
|
||||
})
|
||||
}
|
||||
|
||||
return queryOptions<SystemFeatures>({
|
||||
queryKey,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
return await consoleClient.systemFeatures()
|
||||
@@ -31,3 +42,4 @@ export const systemFeaturesQueryOptions = () =>
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user