add button to edit in workspace fork (#8119)

* Add button to edit in workspace fork

* Remove old fork from script row

* Remove fork button from approw

* fix frontend check
This commit is contained in:
wendrul
2026-02-26 16:43:30 +00:00
committed by GitHub
parent 82c31e2c30
commit 14b6aab902
11 changed files with 176 additions and 47 deletions
@@ -96,6 +96,8 @@
import type { FlowBuilderProps } from './flow_builder'
import { ModulesTestStates } from './modulesTest.svelte'
import FlowAssetsHandler, { initFlowGraphAssetsCtx } from './flows/FlowAssetsHandler.svelte'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
let {
initialPath = $bindable(''),
@@ -810,6 +812,13 @@
onClick: () => window.open(`/flows/add?template=${initialPath}`)
})
}
if (!newFlow && !isRuleActive('DisableWorkspaceForking')) {
dropdownItems.push({
label: 'Edit in workspace fork',
onClick: () => window.open(buildForkEditUrl('flow', initialPath))
})
}
}
let flowCopilotContext: FlowCopilotContext = $state({
@@ -103,6 +103,8 @@
import { inputSizeClasses } from './text_input/TextInput.svelte'
import type { ButtonType } from './common/button/model'
import DebounceLimit from './flows/DebounceLimit.svelte'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
import OnBehalfOfSelector, { type OnBehalfOfChoice } from './OnBehalfOfSelector.svelte'
let {
@@ -769,6 +771,16 @@
window.open(`/scripts/add?template=${initialPath}`)
}
},
...(!isRuleActive('DisableWorkspaceForking')
? [
{
label: 'Edit in workspace fork',
onClick: () => {
window.open(buildForkEditUrl('script', initialPath))
}
}
]
: []),
...(customUi?.topBar?.diff !== false && savedScript && diffDrawer
? [
{
@@ -63,6 +63,8 @@
import AppEditorHeaderDeployInitialDraft from './AppEditorHeaderDeployInitialDraft.svelte'
import { computeSecretUrl } from './appDeploy.svelte'
import { updatePolicy } from './appPolicy'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
interface Props {
policy: Policy
@@ -1118,7 +1120,17 @@
onClick: () => {
window.open(`/apps/add?template=${appPath}`)
}
}
},
...(!isRuleActive('DisableWorkspaceForking')
? [
{
label: 'Edit in workspace fork',
onClick: () => {
window.open(buildForkEditUrl('app', $appPath))
}
}
]
: [])
]
: undefined}
>
@@ -32,6 +32,8 @@
import AppDeploymentHistory from '$lib/components/apps/editor/AppDeploymentHistory.svelte'
import { isDeployable } from '$lib/utils_deployable'
import { getDeployUiSettings } from '$lib/components/home/deploy_ui'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
interface Props {
app: ListableApp & { has_draft?: boolean; draft_only?: boolean; canWrite: boolean }
@@ -97,8 +99,8 @@
{/snippet}
{#snippet actions()}
<span class="hidden md:inline-flex gap-x-1">
{#if !$userStore?.operator && showEditButton}
{#if app.canWrite}
{#if !$userStore?.operator}
{#if showEditButton && app.canWrite}
<div>
<Button
aiId={`edit-app-button-${app.summary?.length > 0 ? app.summary : app.path}`}
@@ -111,18 +113,17 @@
Edit
</Button>
</div>
{:else}
{/if}
{#if !isRuleActive('DisableWorkspaceForking') && (!showEditButton || !app.canWrite)}
<div>
<Button
aiId={`fork-app-button-${app.summary?.length > 0 ? app.summary : app.path}`}
aiDescription={`Fork the app ${app.summary?.length > 0 ? app.summary : app.path}`}
variant="subtle"
wrapperClasses="w-20"
variant={!showEditButton ? 'default' : 'subtle'}
wrapperClasses="w-32"
unifiedSize="md"
startIcon={{ icon: GitFork }}
href="{base}/apps{app.raw_app ? '_raw' : ''}/add?template={app.path}"
href={buildForkEditUrl(app.raw_app ? 'raw_app' : 'app', app.path)}
>
Fork
Edit in fork
</Button>
</div>
{/if}
@@ -174,6 +175,12 @@
disabled: !showEditButton,
hide: $userStore?.operator
},
{
displayName: 'Edit in workspace fork',
icon: GitFork,
href: buildForkEditUrl(app.raw_app ? 'raw_app' : 'app', path),
hide: $userStore?.operator || isRuleActive('DisableWorkspaceForking')
},
{
displayName: 'Move/Rename',
icon: FolderOpen,
@@ -34,6 +34,8 @@
} from 'lucide-svelte'
import FlowHistory from '$lib/components/flows/FlowHistory.svelte'
import { getDeployUiSettings } from '$lib/components/home/deploy_ui'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
interface Props {
flow: Flow & { has_draft?: boolean; draft_only?: boolean; canWrite: boolean }
@@ -120,8 +122,8 @@
{/snippet}
{#snippet actions()}
<span class="hidden md:inline-flex gap-x-1">
{#if !$userStore?.operator && showEditButton}
{#if flow.canWrite && !flow.archived}
{#if !$userStore?.operator}
{#if showEditButton && flow.canWrite && !flow.archived}
<div>
<Button
variant="subtle"
@@ -135,18 +137,17 @@
Edit
</Button>
</div>
{:else}
{/if}
{#if !isRuleActive('DisableWorkspaceForking') && (!showEditButton || !flow.canWrite)}
<div>
<Button
variant="subtle"
wrapperClasses="w-20"
variant={!showEditButton ? 'default' : 'subtle'}
wrapperClasses="w-32"
unifiedSize="md"
startIcon={{ icon: GitFork }}
href="{base}/flows/add?template={flow.path}"
aiId={`fork-flow-button-${flow.summary?.length > 0 ? flow.summary : flow.path}`}
aiDescription={`Fork the flow ${flow.summary?.length > 0 ? flow.summary : flow.path}`}
href={buildForkEditUrl('flow', flow.path)}
>
Fork
Edit in fork
</Button>
</div>
{/if}
@@ -194,6 +195,12 @@
disabled: !showEditButton,
hide: $userStore?.operator
},
{
displayName: 'Edit in workspace fork',
icon: GitFork,
href: buildForkEditUrl('flow', path),
hide: $userStore?.operator || isRuleActive('DisableWorkspaceForking')
},
{
displayName: 'Audit logs',
icon: Eye,
@@ -43,6 +43,8 @@
import Tooltip from '$lib/components/Tooltip.svelte'
import { getDeployUiSettings } from '$lib/components/home/deploy_ui'
import { scriptToHubUrl } from '$lib/hub'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
interface Props {
script: Script & { canWrite: boolean; use_codebase: boolean }
@@ -150,39 +152,40 @@
{#snippet actions()}
<span class="hidden md:inline-flex gap-x-1">
{#if !$userStore?.operator && showEditButton}
{#if script.use_codebase}
<Badge
>bundle<Tooltip
>This script is deployed as a bundle and can only be deployed from the CLI for now</Tooltip
></Badge
>
{:else if script.canWrite && !script.archived}
<div>
<Button
aiId={`edit-script-button-${script.summary?.length > 0 ? script.summary : script.path}`}
aiDescription={`Edits the script ${script.summary?.length > 0 ? script.summary : script.path}`}
variant="subtle"
wrapperClasses="w-20"
unifiedSize="md"
startIcon={{ icon: Pen }}
href="{base}/scripts/edit/{script.path}"
{#if !$userStore?.operator}
{#if showEditButton}
{#if script.use_codebase}
<Badge
>bundle<Tooltip
>This script is deployed as a bundle and can only be deployed from the CLI for now</Tooltip
></Badge
>
Edit
</Button>
</div>
{:else if !script.draft_only}
{:else if script.canWrite && !script.archived}
<div>
<Button
aiId={`edit-script-button-${script.summary?.length > 0 ? script.summary : script.path}`}
aiDescription={`Edits the script ${script.summary?.length > 0 ? script.summary : script.path}`}
variant="subtle"
wrapperClasses="w-20"
unifiedSize="md"
startIcon={{ icon: Pen }}
href="{base}/scripts/edit/{script.path}"
>
Edit
</Button>
</div>
{/if}
{/if}
{#if !isRuleActive('DisableWorkspaceForking') && (!showEditButton || !script.canWrite)}
<div>
<Button
aiId={`fork-script-button-${script.summary ?? script.path}`}
aiDescription={`Fork the script ${script.summary ?? script.path}`}
variant="subtle"
wrapperClasses="w-20"
variant={!showEditButton ? 'default' : 'subtle'}
wrapperClasses="w-32"
unifiedSize="md"
startIcon={{ icon: GitFork }}
href="{base}/scripts/add?template={script.path}"
href={buildForkEditUrl('script', script.path)}
>
Fork
Edit in fork
</Button>
</div>
{/if}
@@ -237,6 +240,12 @@
disabled: !showEditButton,
hide: $userStore?.operator
},
{
displayName: 'Edit in workspace fork',
icon: GitFork,
href: buildForkEditUrl('script', script.path),
hide: $userStore?.operator || isRuleActive('DisableWorkspaceForking')
},
{
displayName: 'Move/Rename',
icon: FolderOpen,
@@ -51,6 +51,8 @@
import { aiChatManager } from '../copilot/chat/AIChatManager.svelte'
import { AIBtnClasses } from '../copilot/chat/AIButtonStyle'
import type { RawAppData } from './dataTableRefUtils'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
// async function hash(message) {
// try {
@@ -931,7 +933,17 @@
onClick: () => {
window.open(`/apps/add?template=${appPath}`)
}
}
},
...(!isRuleActive('DisableWorkspaceForking')
? [
{
label: 'Edit in workspace fork',
onClick: () => {
window.open(buildForkEditUrl('raw_app', appPath))
}
}
]
: [])
]
: undefined}
>
+22
View File
@@ -0,0 +1,22 @@
import { base } from '$lib/base'
type ItemType = 'script' | 'flow' | 'app' | 'raw_app'
export function buildForkEditUrl(itemType: ItemType, itemPath: string): string {
let editPath: string
switch (itemType) {
case 'script':
editPath = `${base}/scripts/edit/${itemPath}`
break
case 'flow':
editPath = `${base}/flows/edit/${itemPath}?nodraft=true`
break
case 'app':
editPath = `${base}/apps/edit/${itemPath}?nodraft=true`
break
case 'raw_app':
editPath = `${base}/apps_raw/edit/${itemPath}?nodraft=true`
break
}
return `${base}/user/fork_workspace?rd=${encodeURIComponent(editPath)}`
}
@@ -67,6 +67,8 @@
import { slide } from 'svelte/transition'
import { twMerge } from 'tailwind-merge'
import NoDirectDeployAlert from '$lib/components/NoDirectDeployAlert.svelte'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
let flow: Flow | undefined = $state()
let can_write = $state(false)
@@ -253,6 +255,18 @@
})
}
if (flow && !$userStore?.operator && !isRuleActive('DisableWorkspaceForking')) {
buttons.push({
label: 'Edit in fork',
buttonProps: {
href: buildForkEditUrl('flow', flow.path),
unifiedSize: 'md',
variant: !showEditButtons ? 'default' : 'subtle',
startIcon: GitFork
}
})
}
if (!flow) {
return buttons
}
@@ -34,6 +34,7 @@
Code2,
ClipboardCopy,
GitBranch,
GitFork,
EllipsisVertical
} from 'lucide-svelte'
@@ -86,6 +87,7 @@
import FlowRestartButton from '$lib/components/FlowRestartButton.svelte'
import JobOtelTraces from '$lib/components/JobOtelTraces.svelte'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
let job: (Job & { result?: any; result_stream?: string }) | undefined = $state()
let jobUpdateLastFetch: Date | undefined = $state()
@@ -654,6 +656,15 @@
startIcon={{ icon: Pen }}>Edit</Button
>
{/if}
{#if !showEditButton && !isRuleActive('DisableWorkspaceForking')}
<Button
href={buildForkEditUrl(isScript ? 'script' : 'flow', job?.script_path ?? '')}
unifiedSize="md"
variant="default"
size="sm"
startIcon={{ icon: GitFork }}>Edit in fork</Button
>
{/if}
{/if}
{/if}
{#if job?.job_kind === 'script' || job?.job_kind === 'script_hub' || job?.job_kind === 'flow'}
@@ -86,6 +86,8 @@
import TriggersEditor from '$lib/components/triggers/TriggersEditor.svelte'
import { Triggers } from '$lib/components/triggers/triggers.svelte'
import { page } from '$app/state'
import { isRuleActive } from '$lib/workspaceProtectionRules.svelte'
import { buildForkEditUrl } from '$lib/utils/editInFork'
let script: Script | undefined = $state()
let topHash: string | undefined = $state()
@@ -349,6 +351,18 @@
})
}
if (script && !$userStore?.operator && !isRuleActive('DisableWorkspaceForking')) {
buttons.push({
label: 'Edit in fork',
buttonProps: {
href: buildForkEditUrl('script', script.path),
unifiedSize: 'md',
variant: !showEditButtons ? 'default' : 'subtle',
startIcon: GitFork
}
})
}
if (!script) {
return buttons
}