Skip to content

Commit

Permalink
Merge pull request #144 from briefercloud/unexecuted-blocks-setting
Browse files Browse the repository at this point in the history
add setting to control whether to run previous unexecuted blocks
  • Loading branch information
vieiralucas authored Oct 11, 2024
2 parents 6da9cff + 01ea4ea commit 4f9bb86
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import inputsRouter from './inputs.js'
import publishRouter from './publish.js'
import { canUpdateWorkspace } from '../../../../../auth/token.js'
import settingsRouter from './settings.js'

export default function documentRouter(socketServer: IOServer) {
const router = Router({ mergeParams: true })
Expand Down Expand Up @@ -256,6 +257,7 @@ export default function documentRouter(socketServer: IOServer) {
router.use('/icon', canUpdateWorkspace, iconRouter)
router.use('/inputs', canUpdateWorkspace, inputsRouter)
router.use('/publish', canUpdateWorkspace, publishRouter(socketServer))
router.use('/settings', canUpdateWorkspace, settingsRouter(socketServer))

return router
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Router } from 'express'
import { getParam } from '../../../../../utils/express.js'
import { z } from 'zod'
import { IOServer } from '../../../../../websocket/index.js'
import prisma, { recoverFromNotFound } from '@briefer/database'
import { broadcastDocument } from '../../../../../websocket/workspace/documents.js'

const DocumentSettings = z.object({
runUnexecutedBlocks: z.boolean(),
})

export default function settingsRouter(socketServer: IOServer) {
const settingsRouter = Router({ mergeParams: true })

settingsRouter.put('/', async (req, res) => {
const workspaceId = getParam(req, 'workspaceId')
const documentId = getParam(req, 'documentId')
const body = DocumentSettings.safeParse(req.body)
if (!body.success) {
res.status(400).json({ reason: 'invalid-payload' })
return
}

const runUnexecutedBlocks = body.data.runUnexecutedBlocks

try {
const doc = await recoverFromNotFound(
prisma().document.update({
where: { id: documentId },
data: { runUnexecutedBlocks },
})
)
if (!doc) {
res.status(404).end()
return
}

await broadcastDocument(socketServer, workspaceId, documentId)

res.sendStatus(204)
} catch (err) {
req.log.error(
{
workspaceId,
documentId,
err,
},
'Failed to update document settings'
)
res.status(500).end()
}
})

return settingsRouter
}
10 changes: 10 additions & 0 deletions apps/web/src/components/EllipsisDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
BookOpenIcon,
ClockIcon,
CodeBracketSquareIcon,
Cog6ToothIcon,
MapIcon,
} from '@heroicons/react/24/outline'
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid'
Expand All @@ -21,6 +22,7 @@ interface Props {
onToggleFiles?: () => void
onToggleSchemaExplorer?: () => void
onToggleShortcuts?: () => void
onTogglePageSettings?: () => void
onToggleReusableComponents?: () => void
isViewer: boolean
isDeleted: boolean
Expand Down Expand Up @@ -123,6 +125,14 @@ function EllipsisDropdown(props: Props) {
onClick={props.onToggleShortcuts}
/>
)}

{props.onTogglePageSettings && (
<MenuButton
icon={<Cog6ToothIcon className="h-4 w-4" />}
text="Page settings"
onClick={props.onTogglePageSettings}
/>
)}
</Menu.Items>
</Transition>
</Menu>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/Files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ file`
return
}

requestRun(pythonBlock, blocks, layout, environmentStartedAt, false)
requestRun(pythonBlock, blocks, layout, environmentStartedAt, true)
},
[props.yDoc, environmentStartedAt]
)
Expand Down Expand Up @@ -131,7 +131,7 @@ file`
return
}

requestRun(sqlBlock, blocks, layout, environmentStartedAt, false)
requestRun(sqlBlock, blocks, layout, environmentStartedAt, true)
},
[props.yDoc, environmentStartedAt]
)
Expand Down
110 changes: 110 additions & 0 deletions apps/web/src/components/PageSettingsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as Y from 'yjs'
import { useState } from 'react'
import { Switch } from '@headlessui/react'
import { ChevronDoubleRightIcon } from '@heroicons/react/24/outline'
import { Transition } from '@headlessui/react'
import clsx from 'clsx'
import useDocument from '@/hooks/useDocument'

interface Props {
workspaceId: string
documentId: string
visible: boolean
onHide: () => void
yDoc?: Y.Doc
}

export default function PageSettingsPanel(props: Props) {
const [{ document }, api] = useDocument(props.workspaceId, props.documentId)

console.log('runUnexecutedBlocks', document?.runUnexecutedBlocks)
return (
<>
<Transition
as="div"
show={props.visible}
className="top-0 right-0 h-full absolute bg-white z-30"
enter="transition-transform duration-300"
enterFrom="transform translate-x-full"
enterTo="transform translate-x-0"
leave="transition-transform duration-300"
leaveFrom="transform translate-x-0"
leaveTo="transform translate-x-full"
>
<button
className="absolute z-10 top-7 transform rounded-full border border-gray-300 text-gray-400 bg-white hover:bg-gray-100 w-6 h-6 flex justify-center items-center left-0 -translate-x-1/2"
onClick={props.onHide}
>
<ChevronDoubleRightIcon className="w-3 h-3" />
</button>
<div className="w-[324px] flex flex-col border-l border-gray-200 h-full bg-white">
<div className="flex justify-between border-b p-6 space-x-3">
<div>
<h3 className="text-lg font-medium leading-6 text-gray-900 pr-1.5">
Page settings
</h3>
<p className="text-gray-500 text-sm pt-1">
{
"Configure this page's behavior and default visualization mode."
}
</p>
</div>
</div>
<div className="h-full w-full px-6">
<PageSettingToggle
name="Auto-run pending blocks"
description="Whether Briefer should automatically run unexecuted preceding blocks when a block is executed."
enabled={document?.runUnexecutedBlocks ?? false}
onToggle={api.toggleRunUnexecutedBlocks}
/>
</div>
</div>
</Transition>
</>
)
}

type PageSettingToggleProps = {
name: string
description: string
enabled: boolean
onToggle: () => void
disabled?: boolean
}

export function PageSettingToggle(props: PageSettingToggleProps) {
return (
<Switch.Group
as="div"
className="flex flex-col items-center justify-between py-4 gap-x-16 gap-y-2"
>
<span className="flex flex-grow items-center justify-between w-full">
<Switch.Label
as="span"
className="text-sm font-medium leading-6 text-gray-900"
passive
>
{props.name}
</Switch.Label>
<Switch
checked={props.enabled}
onChange={props.onToggle}
className={clsx(
props.enabled ? 'bg-primary-600' : 'bg-gray-200',
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2'
)}
disabled={props.disabled}
>
<span
aria-hidden="true"
className={clsx(
props.enabled ? 'translate-x-5' : 'translate-x-0',
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'
)}
/>
</Switch>
</span>
<span className="text-sm text-gray-500">{props.description}</span>
</Switch.Group>
)
}
16 changes: 15 additions & 1 deletion apps/web/src/components/PrivateDocumentPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { EditorAwarenessProvider } from '@/hooks/useEditorAwareness'
import ShortcutsModal from './ShortcutsModal'
import { NEXT_PUBLIC_PUBLIC_URL } from '@/utils/env'
import ReusableComponents from './ReusableComponents'
import { SaveConfirmationModal } from './ReusableComponents'
import PageSettingsPanel from './PageSettingsPanel'

// this is needed because this component only works with the browser
const V2Editor = dynamic(() => import('@/components/v2Editor'), {
Expand Down Expand Up @@ -94,6 +94,7 @@ function PrivateDocumentPageInner(
| { _tag: 'schemaExplorer'; dataSourceId: string | null }
| { _tag: 'shortcuts' }
| { _tag: 'reusableComponents' }
| { _tag: 'pageSettings' }
| null
>(null)

Expand Down Expand Up @@ -156,6 +157,12 @@ function PrivateDocumentPageInner(
setSelectedSidebar((v) => (v?._tag === 'files' ? null : { _tag: 'files' }))
}, [setSelectedSidebar])

const onTogglePageSettings = useCallback(() => {
setSelectedSidebar((v) =>
v?._tag === 'pageSettings' ? null : { _tag: 'pageSettings' }
)
}, [setSelectedSidebar])

const router = useRouter()
const copyLink = useMemo(
() =>
Expand Down Expand Up @@ -308,6 +315,7 @@ function PrivateDocumentPageInner(
onToggleSchemaExplorer={onToggleSchemaExplorerEllipsis}
onToggleReusableComponents={onToggleReusableComponents}
onToggleShortcuts={onToggleShortcuts}
onTogglePageSettings={onTogglePageSettings}
isViewer={isViewer}
isDeleted={isDeleted}
isFullScreen={isFullScreen}
Expand Down Expand Up @@ -400,6 +408,12 @@ function PrivateDocumentPageInner(
onHide={onHideSidebar}
yDoc={yDoc}
/>
<PageSettingsPanel
workspaceId={props.workspaceId}
documentId={props.documentId}
visible={selectedSidebar?._tag === 'pageSettings'}
onHide={onHideSidebar}
/>
</>
)}
</div>
Expand Down
13 changes: 9 additions & 4 deletions apps/web/src/components/v2Editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -476,11 +476,16 @@ const DraggableTabbedBlock = (props: {
blocks.value,
layout.value,
environmentStartedAt,
false,
!props.document.runUnexecutedBlocks,
customCallback
)
},
[blocks.value, layout.value, environmentStartedAt]
[
blocks.value,
layout.value,
props.document.runUnexecutedBlocks,
environmentStartedAt,
]
)

const onTry = useCallback(
Expand Down Expand Up @@ -537,7 +542,7 @@ file`
blocks.value,
layout.value,
environmentStartedAt,
false
true
)
},
[blocks, layout, environmentStartedAt]
Expand Down Expand Up @@ -581,7 +586,7 @@ file`
blocks.value,
layout.value,
environmentStartedAt,
false
true
)
},
[]
Expand Down
25 changes: 22 additions & 3 deletions apps/web/src/hooks/useDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useDocuments } from './useDocuments'
type API = {
setIcon: (icon: string) => Promise<void>
publish: () => Promise<void>
toggleRunUnexecutedBlocks: () => Promise<void>
}

type UseDocument = [
Expand All @@ -13,7 +14,7 @@ type UseDocument = [
loading: boolean
publishing: boolean
},
API,
API
]

function useDocument(workspaceId: string, documentId: string): UseDocument {
Expand All @@ -23,6 +24,8 @@ function useDocument(workspaceId: string, documentId: string): UseDocument {
[documents, documentId]
)

const currRunUnexecutedBlocks = document?.runUnexecutedBlocks ?? false

const setIcon = useCallback(
(icon: string) => api.setIcon(documentId, icon),
[api, documentId]
Expand All @@ -40,12 +43,28 @@ function useDocument(workspaceId: string, documentId: string): UseDocument {
}
}, [workspaceId, documentId, api.publish])

const toggleRunUnexecutedBlocks = useCallback(async () => {
const newRunUnexecutedBlocks = !currRunUnexecutedBlocks
try {
await api.updateDocumentSettings(documentId, {
runUnexecutedBlocks: newRunUnexecutedBlocks,
})
} catch (err) {
alert('Failed to update document settings')
}
}, [
workspaceId,
documentId,
currRunUnexecutedBlocks,
api.updateDocumentSettings,
])

return useMemo(
() => [
{ document, loading, publishing },
{ setIcon, publish },
{ setIcon, publish, toggleRunUnexecutedBlocks },
],
[loading, setIcon, publish]
[loading, setIcon, publish, toggleRunUnexecutedBlocks]
)
}

Expand Down
Loading

0 comments on commit 4f9bb86

Please sign in to comment.