Skip to content

Commit

Permalink
feat: Create Generator from Recorder
Browse files Browse the repository at this point in the history
  • Loading branch information
going-confetti committed Jun 27, 2024
1 parent 60deaed commit c61e3e6
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 135 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@radix-ui/themes": "^3.0.5",
"allotment": "^1.20.2",
"electron-squirrel-startup": "^1.0.1",
"immer": "^10.1.1",
"lodash-es": "^4.17.21",
"node-forge": "^1.3.1",
"prettier": "^3.3.2",
Expand Down
37 changes: 37 additions & 0 deletions src/hooks/useGeneratorStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

import { GroupedProxyData } from '@/types'
import { TestRule } from '@/types/rules'

interface GeneratorState {
recording: GroupedProxyData
rules: TestRule[]
requestFilters: string[]
setRecording: (recording: GroupedProxyData) => void
addRequestFilter: (filter: string) => void
}

export const useGeneratorStore = create<GeneratorState>()(
immer((set) => ({
recording: {},
rules: [
{
type: 'customCode',
filter: { path: '' },
snippet: 'console.log("Hello, world!")',
placement: 'before',
},
],
requestFilters: [],

addRequestFilter: (filter: string) =>
set((state) => {
state.requestFilters.push(filter)
}),
setRecording: (recording: GroupedProxyData) =>
set((state) => {
state.recording = recording
}),
}))
)
19 changes: 6 additions & 13 deletions src/hooks/useListenProxyData.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ProxyData } from '@/types'
import { mergeRequestsById } from '@/views/Recorder/Recorder.utils'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef } from 'react'

import { useRecorderStore } from './useRecorderStore'

export function useListenProxyData(group?: string) {
const [proxyData, setProxyData] = useState<ProxyData[]>([])
const { addRequest } = useRecorderStore()
const groupRef = useRef(group)

useEffect(() => {
Expand All @@ -14,14 +14,7 @@ export function useListenProxyData(group?: string) {

useEffect(() => {
return window.studio.proxy.onProxyData((data) => {
setProxyData((prev) => {
return mergeRequestsById(prev, { ...data, group: groupRef.current })
})
addRequest(data, groupRef.current ?? 'default')
})
}, [])

return {
proxyData,
resetProxyData: () => setProxyData([]),
}
}, [addRequest])
}
41 changes: 41 additions & 0 deletions src/hooks/useRecorderStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

import { ProxyData } from '@/types'
import { mergeRequestsById } from '@/views/Recorder/Recorder.utils'

interface RecorderState {
isRecording: boolean
proxyData: ProxyData[]
addRequest: (request: ProxyData, currentGroup: string) => void
setIsRecording: (isRecording: boolean) => void
setProxyData: (proxyData: ProxyData[]) => void
resetProxyData: () => void
}

export const useRecorderStore = create<RecorderState>()(
immer((set) => ({
isRecording: false,
proxyData: [],

addRequest: (request: ProxyData, currentGroup: string) =>
set((state) => {
state.proxyData = mergeRequestsById(state.proxyData, {
...request,
group: currentGroup,
})
}),
setIsRecording: (isRecording: boolean) =>
set((state) => {
state.isRecording = isRecording
}),
setProxyData: (proxyData: ProxyData[]) =>
set((state) => {
state.proxyData = proxyData
}),
resetProxyData: () =>
set((state) => {
state.proxyData = []
}),
}))
)
33 changes: 10 additions & 23 deletions src/views/Generator/Generator.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
import { useState } from 'react'
import { Allotment } from 'allotment'
import { Box, Button } from '@radix-ui/themes'

import { GroupedProxyData } from '@/types'
import { exportScript, saveScript } from './Generator.utils'
import { PageHeading } from '@/components/Layout/PageHeading'
import { harToGroupedProxyData } from '@/utils/harToProxyData'
import { GeneratorDrawer } from './GeneratorDrawer'
import { Allotment } from 'allotment'
import { GeneratorSidebar } from './GeneratorSidebar'
import { useGeneratorStore } from '@/hooks/useGeneratorStore'

export function Generator() {
const [requests, setRequests] = useState<GroupedProxyData>({})
const [filter, setFilter] = useState('')
const hasRecording = Object.entries(requests).length > 0
const { recording, requestFilters, rules, setRecording } = useGeneratorStore()
const hasRecording = Object.entries(recording).length > 0

const handleImport = async () => {
const har = await window.studio.har.openFile()
if (!har) return

const groupedProxyData = harToGroupedProxyData(har)
setRequests(groupedProxyData)
setRecording(groupedProxyData)
}

const handleExport = async () => {
const script = await exportScript(
requests,
[
{
type: 'customCode',
filter: { path: '' },
snippet: 'console.log("Hello, world!")',
placement: 'before',
},
],
[filter]
)
const script = await exportScript(recording, rules, requestFilters)

saveScript(script)
}
Expand All @@ -50,16 +37,16 @@ export function Generator() {
<Allotment defaultSizes={[3, 1]}>
<Allotment.Pane minSize={400}>
<Allotment vertical defaultSizes={[2, 1]}>
<Allotment.Pane>
<Allotment.Pane minSize={300}>
<Box height="100%">Rules:</Box>
</Allotment.Pane>
<Allotment.Pane>
<GeneratorDrawer filter={filter} onFilterChange={setFilter} />
<Allotment.Pane minSize={200}>
<GeneratorDrawer />
</Allotment.Pane>
</Allotment>
</Allotment.Pane>
<Allotment.Pane minSize={300}>
<GeneratorSidebar requests={requests} />
<GeneratorSidebar requests={recording} />
</Allotment.Pane>
</Allotment>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import * as Label from '@radix-ui/react-label'
import { Box, Flex, Tabs, TextField } from '@radix-ui/themes'
import { Box, Tabs } from '@radix-ui/themes'
import { RequestFilters } from './RequestFilters'

interface GeneratorDrawerProps {
filter: string
onFilterChange: (filters: string) => void
}

export function GeneratorDrawer({
filter,
onFilterChange,
}: GeneratorDrawerProps) {
export function GeneratorDrawer() {
return (
<Box height="100%">
<Tabs.Root defaultValue="requestFilters">
Expand All @@ -27,18 +19,7 @@ export function GeneratorDrawer({
<Tabs.Content value="thinkTime">Think time content</Tabs.Content>
<Tabs.Content value="testData">Test data content</Tabs.Content>
<Tabs.Content value="requestFilters">
<Flex gap="2" p="2" align="center" width="100%">
<Label.Root htmlFor="requestFilterInput">
Allow requests containing
</Label.Root>
<TextField.Root
style={{ flex: 1 }}
id="requestFilterInput"
value={filter}
onChange={(e) => onFilterChange(e.target.value)}
placeholder="Type part of the request URL to filter requests"
/>
</Flex>
<RequestFilters />
</Tabs.Content>
</Tabs.Root>
</Box>
Expand Down
39 changes: 39 additions & 0 deletions src/views/Generator/GeneratorDrawer/RequestFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as Label from '@radix-ui/react-label'
import { Box, Button, Flex, TextField } from '@radix-ui/themes'
import { useState } from 'react'

import { useGeneratorStore } from '@/hooks/useGeneratorStore'

export function RequestFilters() {
const [newFilter, setNewFilter] = useState('')
const { requestFilters, addRequestFilter } = useGeneratorStore()

return (
<Box p="2">
<Flex gap="2" p="2" align="center" width="100%">
<Label.Root htmlFor="requestFilterInput">
Allow requests containing
</Label.Root>
<TextField.Root
style={{ flex: 1 }}
id="requestFilterInput"
value={newFilter}
onChange={(e) => setNewFilter(e.target.value)}
placeholder="Type part of the request URL to filter requests"
/>
<Button
onClick={() => {
addRequestFilter(newFilter)
}}
>
Add
</Button>
</Flex>
<ul>
{requestFilters.map((filter) => (
<li key={filter}>{filter}</li>
))}
</ul>
</Box>
)
}
1 change: 1 addition & 0 deletions src/views/Generator/GeneratorDrawer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './GeneratorDrawer'
27 changes: 7 additions & 20 deletions src/views/Recorder/Recorder.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
import { useState } from 'react'
import { Flex, Heading, ScrollArea } from '@radix-ui/themes'
import { groupBy } from 'lodash-es'

import { WebLogView } from '@/components/WebLogView'
import { GroupForm } from './GroupForm'
import { proxyDataToHar } from '@/utils/proxyDataToHar'
import { DebugControls } from './DebugControls'
import { RecordingButton } from './RecordingButton'
import { SaveHarDialog } from './SaveHarDialog'
import { RecordingControls } from './RecordingButton'
import { useListenProxyData } from '@/hooks/useListenProxyData'
import { PageHeading } from '@/components/Layout/PageHeading'
import { groupBy } from 'lodash-es'
import { useRecorderStore } from '@/hooks/useRecorderStore'

export function Recorder() {
const { proxyData } = useRecorderStore()
const [group, setGroup] = useState<string>('Default')
const [showHarSaveDialog, setShowHarSaveDialog] = useState(false)
const { proxyData, resetProxyData } = useListenProxyData(group)
useListenProxyData(group)
const groupedProxyData = groupBy(proxyData, 'group')

function saveHarToFile() {
const har = proxyDataToHar(groupedProxyData)
window.studio.har.saveFile(JSON.stringify(har, null, 4))
}

return (
<>
<PageHeading text="Recorder">
<RecordingButton
onStop={() => setShowHarSaveDialog(true)}
onStart={resetProxyData}
/>
<RecordingControls requests={groupedProxyData} />
</PageHeading>
<Flex justify="between" wrap="wrap" gap="2">
<GroupForm onChange={setGroup} value={group} />
Expand All @@ -40,11 +32,6 @@ export function Recorder() {
<ScrollArea scrollbars="vertical">
<WebLogView requests={groupedProxyData} />
</ScrollArea>
<SaveHarDialog
onConfirm={saveHarToFile}
open={showHarSaveDialog}
onOpenChange={setShowHarSaveDialog}
/>
</>
)
}
Loading

0 comments on commit c61e3e6

Please sign in to comment.