mirror of
https://github.com/langgenius/dify.git
synced 2026-07-15 17:37:38 +08:00
fix(web): redirect imported apps with creator permissions (#38460)
This commit is contained in:
@ -17,6 +17,7 @@ const mockInvalidateAppList = vi.hoisted(() => vi.fn())
|
||||
let latestDebounceFn = () => {}
|
||||
let mockWorkspacePermissionKeys: string[] = ['app.create_and_management']
|
||||
let mockIsCurrentWorkspaceEditor = true
|
||||
const mockUserProfile = { id: 'user-1' }
|
||||
|
||||
vi.mock('ahooks', () => ({
|
||||
useDebounceFn: (fn: () => void) => {
|
||||
@ -31,6 +32,7 @@ vi.mock('ahooks', () => ({
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
isCurrentWorkspaceEditor: mockIsCurrentWorkspaceEditor,
|
||||
userProfile: mockUserProfile,
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
@ -296,7 +298,37 @@ describe('Apps', () => {
|
||||
id: 'created-app-id',
|
||||
mode: AppModeEnum.CHAT,
|
||||
permission_keys: ['app.acl.view_layout'],
|
||||
}, mockPush, { isRbacEnabled: false })
|
||||
}, mockPush, {
|
||||
currentUserId: 'user-1',
|
||||
resourceMaintainer: 'user-1',
|
||||
workspacePermissionKeys: ['app.create_and_management'],
|
||||
isRbacEnabled: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('passes creator context when template import response has no permission keys', async () => {
|
||||
mockImportDSL.mockResolvedValueOnce({
|
||||
app_id: 'created-without-permissions',
|
||||
app_mode: AppModeEnum.WORKFLOW,
|
||||
})
|
||||
|
||||
render(<Apps />)
|
||||
|
||||
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
||||
fireEvent.click(screen.getByTestId('confirm-create'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGetRedirection).toHaveBeenCalledWith({
|
||||
id: 'created-without-permissions',
|
||||
mode: AppModeEnum.WORKFLOW,
|
||||
permission_keys: undefined,
|
||||
}, mockPush, {
|
||||
currentUserId: 'user-1',
|
||||
resourceMaintainer: 'user-1',
|
||||
workspacePermissionKeys: ['app.create_and_management'],
|
||||
isRbacEnabled: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('shows an error toast when importing the template fails', async () => {
|
||||
|
||||
@ -48,7 +48,7 @@ const Apps = ({
|
||||
}: AppsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { workspacePermissionKeys } = useAppContext()
|
||||
const { userProfile, workspacePermissionKeys } = useAppContext()
|
||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||
const canCreateAppFromTemplate = hasPermission(workspacePermissionKeys, 'app.create_and_management')
|
||||
const { push } = useRouter()
|
||||
@ -163,8 +163,14 @@ const Apps = ({
|
||||
await handleCheckPluginDependencies(app.app_id)
|
||||
setNeedRefresh('1')
|
||||
invalidateAppList()
|
||||
if (app.app_id)
|
||||
getRedirection({ id: app.app_id, mode: app.app_mode, permission_keys: app.permission_keys }, push, { isRbacEnabled })
|
||||
if (app.app_id) {
|
||||
getRedirection({ id: app.app_id, mode: app.app_mode, permission_keys: app.permission_keys }, push, {
|
||||
currentUserId: userProfile?.id,
|
||||
resourceMaintainer: userProfile?.id,
|
||||
workspacePermissionKeys,
|
||||
isRbacEnabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
catch {
|
||||
toast.error(t('newApp.appCreateFailed', { ns: 'app' }))
|
||||
|
||||
@ -29,6 +29,8 @@ const hotkeyMocks = vi.hoisted(() => ({
|
||||
}))
|
||||
let mockPlanUsage = 0
|
||||
let mockPlanTotal = 10
|
||||
let mockWorkspacePermissionKeys: string[] = ['app.create_and_management']
|
||||
const mockUserProfile = { id: 'user-1' }
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
@ -86,6 +88,8 @@ vi.mock('@/app/components/workflow/plugin-dependency/hooks', () => ({
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
isCurrentWorkspaceEditor: true,
|
||||
userProfile: mockUserProfile,
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||
}),
|
||||
}))
|
||||
|
||||
@ -125,6 +129,7 @@ describe('CreateFromDSLModal', () => {
|
||||
hotkeyMocks.handlers.clear()
|
||||
mockPlanUsage = 0
|
||||
mockPlanTotal = 10
|
||||
mockWorkspacePermissionKeys = ['app.create_and_management']
|
||||
localStorage.clear()
|
||||
|
||||
class MockFileReader {
|
||||
@ -247,7 +252,45 @@ describe('CreateFromDSLModal', () => {
|
||||
expect(mockGetRedirection).toHaveBeenCalledWith(
|
||||
{ id: 'app-1', mode: 'chat', permission_keys: ['app.acl.view_layout'] },
|
||||
mockPush,
|
||||
{ isRbacEnabled: false },
|
||||
{
|
||||
currentUserId: 'user-1',
|
||||
resourceMaintainer: 'user-1',
|
||||
workspacePermissionKeys: ['app.create_and_management'],
|
||||
isRbacEnabled: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
it('should pass creator context when import response has no permission keys', async () => {
|
||||
mockImportDSL.mockResolvedValue({
|
||||
id: 'import-no-permissions',
|
||||
status: DSLImportStatus.COMPLETED,
|
||||
app_id: 'app-no-permissions',
|
||||
app_mode: AppModeEnum.WORKFLOW,
|
||||
})
|
||||
|
||||
render(
|
||||
<CreateFromDSLModal
|
||||
show
|
||||
onClose={vi.fn()}
|
||||
activeTab={CreateFromDSLModalTab.FROM_URL}
|
||||
dslUrl="https://example.com/app.yml"
|
||||
/>,
|
||||
)
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(getCreateButton())
|
||||
})
|
||||
|
||||
expect(mockGetRedirection).toHaveBeenCalledWith(
|
||||
{ id: 'app-no-permissions', mode: AppModeEnum.WORKFLOW, permission_keys: undefined },
|
||||
mockPush,
|
||||
{
|
||||
currentUserId: 'user-1',
|
||||
resourceMaintainer: 'user-1',
|
||||
workspacePermissionKeys: ['app.create_and_management'],
|
||||
isRbacEnabled: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@ -363,6 +406,16 @@ describe('CreateFromDSLModal', () => {
|
||||
source: 'studio_upload',
|
||||
appMode: AppModeEnum.WORKFLOW,
|
||||
})
|
||||
expect(mockGetRedirection).toHaveBeenCalledWith(
|
||||
{ id: 'app-3', mode: AppModeEnum.WORKFLOW, permission_keys: ['app.acl.view_layout'] },
|
||||
mockPush,
|
||||
{
|
||||
currentUserId: 'user-1',
|
||||
resourceMaintainer: 'user-1',
|
||||
workspacePermissionKeys: ['app.create_and_management'],
|
||||
isRbacEnabled: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
it('should close the DSL mismatch modal when dialog requests close', async () => {
|
||||
|
||||
@ -15,6 +15,7 @@ import { useSetNeedRefreshAppList } from '@/app/components/apps/storage'
|
||||
import Input from '@/app/components/base/input'
|
||||
import AppsFull from '@/app/components/billing/apps-full-in-dialog'
|
||||
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import {
|
||||
@ -59,6 +60,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
||||
const setNeedRefresh = useSetNeedRefreshAppList()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||
const { userProfile, workspacePermissionKeys } = useAppContext()
|
||||
|
||||
const readFile = useCallback((file: File) => {
|
||||
const reader = new FileReader()
|
||||
@ -133,7 +135,12 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
||||
invalidateAppList()
|
||||
if (app_id) {
|
||||
await handleCheckPluginDependencies(app_id)
|
||||
getRedirection({ id: app_id, mode: app_mode, permission_keys }, push, { isRbacEnabled })
|
||||
getRedirection({ id: app_id, mode: app_mode, permission_keys }, push, {
|
||||
currentUserId: userProfile?.id,
|
||||
resourceMaintainer: userProfile?.id,
|
||||
workspacePermissionKeys,
|
||||
isRbacEnabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (status === DSLImportStatus.PENDING) {
|
||||
@ -187,8 +194,14 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
|
||||
await handleCheckPluginDependencies(app_id)
|
||||
setNeedRefresh('1')
|
||||
invalidateAppList()
|
||||
if (app_id)
|
||||
getRedirection({ id: app_id, mode: app_mode, permission_keys }, push, { isRbacEnabled })
|
||||
if (app_id) {
|
||||
getRedirection({ id: app_id, mode: app_mode, permission_keys }, push, {
|
||||
currentUserId: userProfile?.id,
|
||||
resourceMaintainer: userProfile?.id,
|
||||
workspacePermissionKeys,
|
||||
isRbacEnabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (status === DSLImportStatus.FAILED) {
|
||||
toast.error(response.error || t('newApp.appCreateFailed', { ns: 'app' }))
|
||||
|
||||
Reference in New Issue
Block a user