Skip to content

Commit

Permalink
Merge pull request #705 from The-Commit-Company/rav-69-server-scripts…
Browse files Browse the repository at this point in the history
…-integrations

Rav 69 server scripts integrations
  • Loading branch information
yjane99 authored Feb 23, 2024
2 parents eb41335 + 18d949a commit 6ab7306
Show file tree
Hide file tree
Showing 16 changed files with 1,252 additions and 6 deletions.
30 changes: 26 additions & 4 deletions raven-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FrappeProvider } from 'frappe-react-sdk'
import { Route, RouterProvider, createBrowserRouter, createRoutesFromElements } from 'react-router-dom'
import { Outlet, Route, RouterProvider, createBrowserRouter, createRoutesFromElements } from 'react-router-dom'
import { MainPage } from './pages/MainPage'
import { ProtectedRoute } from './utils/auth/ProtectedRoute'
import { UserProvider } from './utils/auth/UserProvider'
Expand All @@ -10,6 +10,15 @@ import { Toaster } from './components/common/Toast/Toaster'
import { FullPageLoader } from './components/layout/Loaders'
import { useStickyState } from './hooks/useStickyState'
import { Settings } from './pages/settings/Settings'
import { DocTypeEvents } from './pages/settings/ServerScripts/DocTypeEvents'
import { CreateDocTypeEvent } from './pages/settings/ServerScripts/CreateDocTypeEvent'
import { ViewDocTypeEvent } from './pages/settings/ServerScripts/ViewDocTypeEvent'
import { APIEvents } from './pages/settings/ServerScripts/APIEvents/APIEvents'
import { CreateAPIEvent } from './pages/settings/ServerScripts/APIEvents/CreateAPIEvents'
import { ViewAPIEvent } from './pages/settings/ServerScripts/APIEvents/ViewAPIEvents'
import { TemporalEvents } from './pages/settings/ServerScripts/TemporalEvents/TemporalEvents'
import { ViewTemporalEvent } from './pages/settings/ServerScripts/TemporalEvents/ViewTemporalEvent'
import { CreateTemporalEvent } from './pages/settings/ServerScripts/TemporalEvents/CreateTemporalEvent'


const router = createBrowserRouter(
Expand All @@ -25,11 +34,24 @@ const router = createBrowserRouter(
</Route>
<Route path='settings' element={<Settings />}>
<Route path='integrations'>
<Route path='webhooks' element={<p>Webhooks</p>} />
<Route path='server-scripts' element={<p>SS</p>} />
<Route path='doctype-events' element={<Outlet />}>
<Route index element={<DocTypeEvents />} />
<Route path='create' element={<CreateDocTypeEvent />} />
<Route path=':eventID' element={<ViewDocTypeEvent />} />
</Route>
<Route path='scheduled-scripts' element={<Outlet />}>
<Route index element={<TemporalEvents />} />
<Route path='create' element={<CreateTemporalEvent />} />
<Route path=':scriptID' element={<ViewTemporalEvent />} />
</Route>
<Route path='api-events' element={<Outlet />}>
<Route index element={<APIEvents />} />
<Route path='create' element={<CreateAPIEvent />} />
<Route path=':apiID' element={<ViewAPIEvent />} />
</Route>
</Route>
</Route>
</Route>
</Route >
</>
), {
basename: `/${import.meta.env.VITE_BASE_NAME}` ?? '',
Expand Down
4 changes: 3 additions & 1 deletion raven-app/src/components/feature/settings/Integrations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export const Integrations = (props: Props) => {
<SidebarGroup>
<SidebarGroupList>
<IntegrationsItem route='/settings/integrations/webhooks' label='Webhooks' />
<IntegrationsItem route='/settings/integrations/server-scripts' label='Server Scripts' />
<IntegrationsItem route='/settings/integrations/doctype-events' label='DocType Events' />
<IntegrationsItem route='/settings/integrations/scheduled-scripts' label='Temporal Events' />
<IntegrationsItem route='/settings/integrations/api-events' label='API Events' />
</SidebarGroupList>
</SidebarGroup>
</SidebarGroup>
Expand Down
105 changes: 105 additions & 0 deletions raven-app/src/components/feature/settings/api-events/APIEventsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Label, HelperText } from "@/components/common/Form"
import { Flex, Box, TextField, TextArea, Checkbox, Grid, Code } from "@radix-ui/themes"
import { Controller, useFormContext } from "react-hook-form"

export interface Props {
edit?: boolean
}

export const APIEventsForm = ({ edit = false }: Props) => {

const { register, watch, control } = useFormContext()

const enableRateLimit = watch('enable_rate_limit')

return (
<Flex direction="column" gap={'5'}>
{!edit && <Box>
<Label htmlFor="name" isRequired>Name</Label>
<TextField.Input
{...register('name', { required: "Name is required.", maxLength: { value: 140, message: "Name cannot be more than 140 characters." } })}
id="name"
placeholder="e.g. Sales Invoice - delete_note"
autoFocus
/>
</Box>}

<Box>
<Label htmlFor="api_method" isRequired>API Method</Label>
<TextField.Input
{...register('api_method', { required: "API Method is required." })}
id="api_method"
placeholder="e.g. delete_note"
autoFocus={edit}
/>
<HelperText>The API endpoint for which this event will be triggered (automatically prefixed with <Code>/api/method</Code>).</HelperText>
</Box>

<Box>
<Controller
control={control}
name="allow_guest"
render={({ field }) => (
<Flex align={'center'} gap={'2'}>
<Checkbox
checked={field.value ? true : false}
onClick={() => field.onChange(!field.value)} />
<Label htmlFor="allow_guest">Allow anyone to use without logging in</Label>
</Flex>
)}
/>
</Box>

<Box>
{/* TODO: Add a script editor here (maybe use Monaco Editor) */}
<Label htmlFor="script" isRequired>Script</Label>
<TextArea
{...register('script', { required: "Script is required." })}
rows={14}
placeholder={"Your script goes here"}
/>
<HelperText>Your custom script to be be called via this API event.</HelperText>
</Box>

<Box>
<Controller
control={control}
name="enable_rate_limit"
render={({ field }) => (
<Flex align={'center'} gap={'2'}>
<Checkbox
checked={field.value ? true : false}
onClick={() => field.onChange(!field.value)} />
<Label htmlFor="enable_rate_limit">Enable Rate Limit</Label>
</Flex>
)}
/>
</Box>

{enableRateLimit ? <Grid columns={"2"} gap={'5'}>
<Box>
<Label htmlFor="rate_limit_count">Rate Limit Count</Label>
<TextField.Input
{...register('rate_limit_count')}
id="rate_limit_count"
type="number"
placeholder="e.g. 5"
autoFocus={enableRateLimit}
/>
<HelperText>Number of requests allowed in the specified time.</HelperText>
</Box>

<Box>
<Label htmlFor="rate_limit_seconds">Rate Limit Seconds</Label>
<TextField.Input
{...register('rate_limit_seconds')}
id="rate_limit_seconds"
type="number"
placeholder="e.g. 86400"
/>
<HelperText>Time period in seconds.</HelperText>
</Box>
</Grid> : null}
</Flex>
)
}
109 changes: 109 additions & 0 deletions raven-app/src/components/feature/settings/common/DeleteAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Loader } from "@/components/common/Loader"
import { ErrorBanner } from "@/components/layout/AlertBanner"
import { useToast } from "@/hooks/useToast"
import { AlertDialog, Button, Callout, Checkbox, Flex, Text } from "@radix-ui/themes"
import { useFrappeDeleteDoc } from "frappe-react-sdk"
import { useState } from "react"
import { FiAlertTriangle } from "react-icons/fi"
import { useNavigate } from "react-router-dom"

export interface Props {
isOpen: boolean,
onClose: () => void
docname: string
path: string
}

export const DeleteAlert = ({ isOpen, onClose, docname, path }: Props) => {
return (
<AlertDialog.Root open={isOpen} onOpenChange={onClose}>
<AlertDialog.Content style={{ maxWidth: 450 }}>
{/* Hii */}
<AlertContent onClose={onClose} docname={docname} path={path} />
</AlertDialog.Content>
</AlertDialog.Root>
)
}


type DeleteDocModalProps = {
onClose: () => void,
docname: string
path: string
}

const AlertContent = ({ onClose, docname, path }: DeleteDocModalProps) => {

const { deleteDoc, error, loading: deletingDoc, reset } = useFrappeDeleteDoc()

const handleClose = () => {
onClose()
reset()
}

const { toast } = useToast()
const navigate = useNavigate()

const onSubmit = () => {
if (docname) {
deleteDoc('Server Script', docname)
.then(() => {
onClose()
navigate(path)
toast({
title: `${docname} deleted`,
variant: 'success',
})
})
}
}

const [allowDelete, setAllowDelete] = useState(false)

return (
<>
<AlertDialog.Title>
Delete {docname}?
</AlertDialog.Title>

<Flex direction='column' gap='4'>
<ErrorBanner error={error} />
<Callout.Root color="red" size='1'>
<Callout.Icon>
<FiAlertTriangle size='18' />
</Callout.Icon>
<Callout.Text>
This action is permanent and cannot be undone.
</Callout.Text>
</Callout.Root>
{/* <Text size='2'>When you delete a channel, all messages from this channel will be removed immediately.</Text> */}
{/* <Flex direction='column'>
<ul className={'list-inside'}>
<li><Text as='span' size='2'>All messages, including files and images will be removed</Text></li>
<li><Text as='span' size='2'>You can archive this channel instead to preserve your messages</Text></li>
</ul>
</Flex> */}
<Text size='2' as='label'>
<Flex gap="2" align={'center'}>
<Checkbox onClick={() => setAllowDelete(!allowDelete)} color='red' />
Yes, I understand, permanently delete this channel
</Flex>
</Text>
</Flex>

<Flex gap="3" mt="4" justify="end">
<AlertDialog.Cancel>
<Button variant="soft" color="gray" onClick={handleClose}>
Cancel
</Button>
</AlertDialog.Cancel>
<AlertDialog.Action>
<Button variant="solid" color="red" onClick={onSubmit} disabled={!allowDelete || deletingDoc}>
{deletingDoc && <Loader />}
{deletingDoc ? "Deleting" : "Delete"}
</Button>
</AlertDialog.Action>
</Flex>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { HelperText, Label } from "@/components/common/Form"
import { Box, Flex, Grid, Link, Select, TextArea, TextField } from "@radix-ui/themes"
import { useFormContext, Controller } from "react-hook-form"

export interface Props {
edit?: boolean
}

export const DocTypeEventsForm = ({ edit = false }: Props) => {

const { register, control } = useFormContext()

return (
<Flex direction="column" gap={'5'}>
{!edit && <Box>
<Label htmlFor="name" isRequired>Name</Label>
<TextField.Input
{...register('name', { required: "Name is required.", maxLength: { value: 140, message: "Name cannot be more than 140 characters." } })}
id="name"
placeholder="e.g. Sales Invoice Before Save"
autoFocus
/>
</Box>}

<Grid columns={"2"} gap={'5'} p={'0'}>
<Box>
<Label htmlFor="reference_document_type" isRequired>Reference Document Type</Label>
<TextField.Input
{...register('reference_document_type', { required: "Reference Document Type is required." })}
id="reference_document_type"
placeholder="e.g. Sales Invoice"
autoFocus={edit}
/>
<HelperText>The event will be triggered for this DocType.</HelperText>
</Box>

<Box>
<Label htmlFor="document_event">DocType Event</Label>
<Controller
control={control}
name="document_event"
render={({ field }) => (
<Select.Root {...field} onValueChange={(value) => field.onChange(value)} defaultValue="Before Insert">
<Select.Trigger style={{ width: "100%" }} placeholder="Select Event" />
<Select.Content>
<Select.Group>
<Select.Label>DocType Event</Select.Label>
<Select.Item value='Before Insert'>Before Insert</Select.Item>
<Select.Item value='Before Validate'>Before Validate</Select.Item>
<Select.Item value='Before Save'>Before Save</Select.Item>
<Select.Item value='After Insert'>After Insert</Select.Item>
<Select.Item value='After Save'>After Save</Select.Item>
<Select.Item value='Before Submit'>Before Submit</Select.Item>
<Select.Item value='After Submit'>After Submit</Select.Item>
<Select.Item value='Before Cancel'>Before Cancel</Select.Item>
<Select.Item value='After Cancel'>After Cancel</Select.Item>
<Select.Item value='Before Save (Submitted Documents)'>Before Save (Submitted Documents)</Select.Item>
<Select.Item value='After Save (Submitted Documents)'>After Save (Submitted Documents)</Select.Item>
<Select.Item value='On Payment Authorization'>On Payment Authorization</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
)}
/>
<HelperText>The event on which you want to run this script. <Link href="https://frappeframework.com/docs/user/en/basics/doctypes/controllers" target="_blank">Learn more</Link></HelperText>
</Box>
</Grid>

<Box>
{/* TODO: Add a script editor here (maybe use Monaco Editor) */}
<Label htmlFor="script" isRequired>Script</Label>
<TextArea
{...register('script', { required: "Script is required." })}
rows={14}
placeholder={"Your script goes here"}
/>
<HelperText>Your custom script to be be called via this document event.</HelperText>
</Box>
</Flex>
)
}
Loading

0 comments on commit 6ab7306

Please sign in to comment.