diff --git a/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx b/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx
index 81e27381649..14c4cc37312 100644
--- a/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx
+++ b/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx
@@ -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()
+
+ 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 () => {
diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx
index f8c0bcd6ada..8efa3427f4a 100644
--- a/web/app/components/app/create-app-dialog/app-list/index.tsx
+++ b/web/app/components/app/create-app-dialog/app-list/index.tsx
@@ -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' }))
diff --git a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx
index fb58e964b75..008225e9963 100644
--- a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx
+++ b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx
@@ -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(
+ ,
+ )
+
+ 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 () => {
diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx
index 8414b7c6f4f..191e1727120 100644
--- a/web/app/components/app/create-from-dsl-modal/index.tsx
+++ b/web/app/components/app/create-from-dsl-modal/index.tsx
@@ -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' }))