Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Unify generator store slices creation #39

Merged
merged 2 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/hooks/useGeneratorStore/slices/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './loadProfile'
export * from './rules'
export * from './testData'
export * from './testOptions'
91 changes: 0 additions & 91 deletions src/hooks/useGeneratorStore/slices/loadProfile.ts

This file was deleted.

58 changes: 58 additions & 0 deletions src/hooks/useGeneratorStore/slices/recording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ProxyData } from '@/types'
import { ImmerStateCreator } from '@/utils/typescript'

interface State {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any specific reason for dropping the prefix to the name ?
As it will be used outside it might make sense to keep it 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not exported, so the prefix is dropped for readability

requests: ProxyData[]
recordingPath: string
allowList: string[]
filteredRequests: ProxyData[]
showAllowListDialog: boolean
}

interface Actions {
setRecording: (recording: ProxyData[], path: string) => void
resetRecording: () => void
setAllowList: (value: string[]) => void
setFilteredRequests: (requests: ProxyData[]) => void
setShowAllowListDialog: (value: boolean) => void
}

export type RecordingSliceStore = State & Actions

export const createRecordingSlice: ImmerStateCreator<RecordingSliceStore> = (
set
) => ({
requests: [],
recordingPath: '',
allowList: [],
filteredRequests: [],
Comment on lines +27 to +28
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filteredRequests is essentially a computed state, which means storing both allowList and filteredRequests is a bit error prone. Perhaps we can use a memoized selector instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be a little complicated 🤔

When saving the State to the file we do no care about filteredRequests.
The interface for State will need validation so it might become a zod type object.
Should we have the State and the extra State (filteredRequests..) as separated state objects or will it make things even more complex 😕

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to write some adapter functions that will turn the state into a file: it's not just that the state has some runtime properties (like filteredRequests or showAllowListDialog), but also some slices has extra properties (like load profile)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there will be 2 separate types of state: State for app use and GeneratorFile type, which won't include internal props like selectedRunId. And 2 functions to convert between them.

As for filteredRequests - agree that it should be a memoized selector 👍

showAllowListDialog: false,

setRecording: (requests: ProxyData[], path: string) =>
set((state) => {
state.requests = requests
state.allowList = []
state.filteredRequests = []
state.recordingPath = path
state.showAllowListDialog = true
}),
resetRecording: () =>
set((state) => {
state.requests = []
state.allowList = []
state.filteredRequests = []
state.recordingPath = ''
}),
setAllowList: (value) =>
set((state) => {
state.allowList = value
}),
setFilteredRequests: (requests) =>
set((state) => {
state.filteredRequests = requests
}),
setShowAllowListDialog: (value) =>
set((state) => {
state.showAllowListDialog = value
}),
})
18 changes: 16 additions & 2 deletions src/hooks/useGeneratorStore/slices/rules.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { TestRule } from '@/types/rules'
import { ImmerStateCreator } from '@/utils/typescript'
import { rules } from '../fixtures'
import { RulesState } from '../types'

export const createRulesSlice: ImmerStateCreator<RulesState> = (set) => ({
interface State {
rules: TestRule[]
selectedRuleId: string | null
}

interface Actions {
createRule: (type: TestRule['type']) => void
updateRule: (rule: TestRule) => void
cloneRule: (id: string) => void
deleteRule: (id: string) => void
selectRule: (id: string) => void
}

export type RulesSliceStore = State & Actions
Comment on lines +5 to +18
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like having internal State and Actions improves readability, while exporting a store type means we can use it elsewhere as before

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RulesSliceStore vs RulesSliceState vs RulesSliceType - what's your choice?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might have to discuss the requirements more before deciding 🤔


export const createRulesSlice: ImmerStateCreator<RulesSliceStore> = (set) => ({
rules,
selectedRuleId: null,
createRule: (type: TestRule['type']) =>
Expand Down
21 changes: 21 additions & 0 deletions src/hooks/useGeneratorStore/slices/testData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ImmerStateCreator } from '@/utils/typescript'
import { Variable } from '@/types'

interface State {
variables: Variable[]
}

interface Actions {
setVariables: (variables: Variable[]) => void
}

export type TestDataStore = State & Actions

export const createTestDataSlice: ImmerStateCreator<TestDataStore> = (set) => ({
variables: [],

setVariables: (variables: Variable[]) =>
set((state) => {
state.variables = variables
}),
})
12 changes: 12 additions & 0 deletions src/hooks/useGeneratorStore/slices/testOptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ImmerStateCreator } from '@/utils/typescript'
import { LoadProfileStore, createLoadProfileSlice } from './loadProfile'
import { ThinkTimeStore, createThinkTimeSlice } from './thinkTime'

export type TestOptionsStore = LoadProfileStore & ThinkTimeStore

export const createTestOptionsSlice: ImmerStateCreator<
LoadProfileStore & ThinkTimeStore
> = (...args) => ({
...createLoadProfileSlice(...args),
...createThinkTimeSlice(...args),
})
133 changes: 133 additions & 0 deletions src/hooks/useGeneratorStore/slices/testOptions/loadProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { ExecutorType } from '@/constants/generator'
import {
RampingStage,
RampingVUsOptions,
SharedIterationsOptions,
} from '@/types/testOptions'
import { ImmerStateCreator } from '@/utils/typescript'

interface SharedIterationsState
extends Omit<SharedIterationsOptions, 'executor'> {
setVus: (value: SharedIterationsOptions['vus']) => void
setIterations: (value: SharedIterationsOptions['iterations']) => void
setMaxDuration: (value: SharedIterationsOptions['maxDuration']) => void
}

interface CommonState {
executor: ExecutorType
gracefulStop: string | undefined
startTime: string | undefined
}

interface CommonActions {
setExecutor: (value: ExecutorType) => void
setGracefulStop: (value: string | undefined) => void
setStartTime: (value: string | undefined) => void
}

type CommonStore = CommonState & CommonActions

const createCommonSlice: ImmerStateCreator<CommonStore> = (set) => ({
executor: ExecutorType.RampingVUs,
gracefulStop: undefined,
startTime: undefined,

setExecutor: (value) =>
set((state) => {
state.executor = value
}),
setGracefulStop: (value) =>
set((state) => {
state.gracefulStop = value
}),
setStartTime: (value) =>
set((state) => {
state.startTime = value
}),
})

interface RampingActions {
addStage: () => void
removeStage: (index: number) => void
updateStage: (index: number, value: RampingStage) => void
setGracefulRampDown: (value: RampingVUsOptions['gracefulRampDown']) => void
setStartVUs: (value: RampingVUsOptions['startVUs']) => void
}

type RampingStore = Omit<RampingVUsOptions, 'executor'> & RampingActions

const createRampingSlice: ImmerStateCreator<RampingStore> = (set) => ({
gracefulRampDown: undefined,
stages: [],
startVUs: undefined,

setGracefulRampDown: (value) =>
set((state) => {
state.gracefulRampDown = value
}),
addStage: () =>
set((state) => {
state.stages.push(createStage())
}),
removeStage: (index) =>
set((state) => {
state.stages.splice(index, 1)
}),
updateStage: (index, value) =>
set((state) => {
state.stages[index] = value
}),
setStartVUs: (value) =>
set((state) => {
state.startVUs = value
}),
})

interface SharedIterationsActions {
setIterations: (value: number | undefined) => void
setMaxDuration: (value: string | undefined) => void
setVus: (value: number | undefined) => void
}

type SharedIterationsStore = Omit<SharedIterationsState, 'executor'> &
SharedIterationsActions

const createSharedIterationsSlice: ImmerStateCreator<SharedIterationsStore> = (
set
) => ({
iterations: undefined,
maxDuration: undefined,
vus: undefined,

setIterations: (value) =>
set((state) => {
state.iterations = value
}),
setMaxDuration: (value) =>
set((state) => {
state.maxDuration = value
}),
setVus: (value) =>
set((state) => {
state.vus = value
}),
})

export type LoadProfileStore = CommonStore &
RampingStore &
SharedIterationsStore

export const createLoadProfileSlice: ImmerStateCreator<LoadProfileStore> = (
...args
) => ({
...createCommonSlice(...args),
...createRampingSlice(...args),
...createSharedIterationsSlice(...args),
})

function createStage() {
return {
target: '',
duration: '',
}
}
32 changes: 32 additions & 0 deletions src/hooks/useGeneratorStore/slices/testOptions/thinkTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ImmerStateCreator } from '@/utils/typescript'
import { SleepType, Timing } from '@/types/testOptions'
import { createFixedTiming } from '@/utils/thinkTime'

interface State {
sleepType: SleepType
timing: Timing
}

interface Actions {
setSleepType: (value: SleepType) => void
setTiming: (timing: Timing) => void
}

export type ThinkTimeStore = State & Actions

export const createThinkTimeSlice: ImmerStateCreator<ThinkTimeStore> = (
set
) => ({
sleepType: 'groups',
timing: createFixedTiming(),

setSleepType: (value: SleepType) =>
set((state) => {
state.sleepType = value
}),

setTiming: (timing: Timing) =>
set((state) => {
state.timing = timing
}),
})
Loading