diff --git a/.changeset/swift-starfishes-learn.md b/.changeset/swift-starfishes-learn.md new file mode 100644 index 00000000..7341dca5 --- /dev/null +++ b/.changeset/swift-starfishes-learn.md @@ -0,0 +1,6 @@ +--- +'@boostv/process-optimizer-frontend-core': minor +'@boostv/process-optimizer-frontend-ui': minor +--- + +Cap suggestion count diff --git a/package-lock.json b/package-lock.json index c3a5ec7e..890903e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8396,6 +8396,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "node_modules/lodash.get": { "version": "4.4.2", "dev": true, @@ -12709,7 +12714,7 @@ }, "packages/core": { "name": "@boostv/process-optimizer-frontend-core", - "version": "2.9.0", + "version": "2.9.2", "license": "BSD-3-Clause", "dependencies": { "@boostv/process-optimizer-frontend-api": "*", @@ -12862,11 +12867,12 @@ }, "packages/ui": { "name": "@boostv/process-optimizer-frontend-ui", - "version": "2.8.0", + "version": "2.9.0", "license": "BSD-3-Clause", "dependencies": { "@boostv/process-optimizer-frontend-core": "*", "@boostv/process-optimizer-frontend-plots": "*", + "lodash.debounce": "^4.0.8", "react-hook-form": "^7.33.0", "remeda": "^1.12.0", "tss-react": "^4.8.2" @@ -13563,6 +13569,7 @@ "@types/uuid": "^9.0.0", "@vitejs/plugin-react": "^3.1.0", "jsdom": "^21.1.0", + "lodash.debounce": "^4.0.8", "node-mocks-http": "^1.12.1", "react-devtools": "^4.27.1", "react-hook-form": "^7.33.0", @@ -18565,6 +18572,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "lodash.get": { "version": "4.4.2", "dev": true diff --git a/packages/core/src/context/experiment/experiment-reducers.ts b/packages/core/src/context/experiment/experiment-reducers.ts index f9a9a287..512388f7 100644 --- a/packages/core/src/context/experiment/experiment-reducers.ts +++ b/packages/core/src/context/experiment/experiment-reducers.ts @@ -140,7 +140,10 @@ export type ExperimentAction = } | { type: 'updateSuggestionCount' - payload: string + payload: { + suggestionCount: string + maxSuggestionCount?: number + } } | { type: 'copySuggestedToDataPoints' @@ -182,9 +185,17 @@ export const experimentReducer = produce( state.info.description = experimentSchema.shape.info.shape.description.parse(action.payload) break - case 'updateSuggestionCount': - state.extras.experimentSuggestionCount = Number(action.payload) + case 'updateSuggestionCount': { + const payloadVal = Number(action.payload.suggestionCount) + // TODO: Move max out of reducer if used in application settings + const maxSuggestionCount = + action.payload.maxSuggestionCount ?? Number.MAX_VALUE + state.extras.experimentSuggestionCount = Math.max( + 1, + Math.min(maxSuggestionCount, payloadVal) + ) break + } case 'copySuggestedToDataPoints': { const nextValues = selectNextValues(state) const variables = selectActiveVariablesFromExperiment(state) diff --git a/packages/core/src/context/experiment/reducers.test.ts b/packages/core/src/context/experiment/reducers.test.ts index 1ce136d1..bd04a546 100644 --- a/packages/core/src/context/experiment/reducers.test.ts +++ b/packages/core/src/context/experiment/reducers.test.ts @@ -197,16 +197,36 @@ describe('experiment reducer', () => { }) describe('updateSuggestionCount', () => { - it('should change suggestion count', () => { + it('should cap suggestion count to max', () => { const actual = rootReducer(initState, { type: 'updateSuggestionCount', - payload: '42', + payload: { suggestionCount: '42', maxSuggestionCount: 10 }, + }) + expect(actual.experiment.extras).toMatchObject({ + experimentSuggestionCount: 10, + }) + expect(actual.experiment.changedSinceLastEvaluation).toBeTruthy() + }) + it('should not cap suggestion count to max when unset', () => { + const actual = rootReducer(initState, { + type: 'updateSuggestionCount', + payload: { suggestionCount: '42' }, }) expect(actual.experiment.extras).toMatchObject({ experimentSuggestionCount: 42, }) expect(actual.experiment.changedSinceLastEvaluation).toBeTruthy() }) + it('should set suggestion count to min 1', () => { + const actual = rootReducer(initState, { + type: 'updateSuggestionCount', + payload: { suggestionCount: '0' }, + }) + expect(actual.experiment.extras).toMatchObject({ + experimentSuggestionCount: 1, + }) + expect(actual.experiment.changedSinceLastEvaluation).toBeTruthy() + }) }) describe('Constraints', () => { diff --git a/packages/core/src/context/experiment/test-utils.ts b/packages/core/src/context/experiment/test-utils.ts index f425f260..b2733af7 100644 --- a/packages/core/src/context/experiment/test-utils.ts +++ b/packages/core/src/context/experiment/test-utils.ts @@ -124,7 +124,9 @@ export const dummyPayloads: Payloads = { updateExperimentDescription: '', updateConfiguration: initialState.experiment.optimizerConfig, updateDataPoints: initialState.experiment.dataPoints, - updateSuggestionCount: '', + updateSuggestionCount: { + suggestionCount: '', + }, copySuggestedToDataPoints: [], 'experiment/toggleMultiObjective': undefined, 'experiment/setConstraintSum': 0, diff --git a/packages/ui/package.json b/packages/ui/package.json index d8449d4e..6b2894dc 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -37,6 +37,7 @@ "dependencies": { "@boostv/process-optimizer-frontend-core": "*", "@boostv/process-optimizer-frontend-plots": "*", + "lodash.debounce": "^4.0.8", "react-hook-form": "^7.33.0", "remeda": "^1.12.0", "tss-react": "^4.8.2" diff --git a/packages/ui/src/containers/result-data/experimentation-guide.tsx b/packages/ui/src/containers/result-data/experimentation-guide.tsx index ddf57d2a..dca4f1ca 100644 --- a/packages/ui/src/containers/result-data/experimentation-guide.tsx +++ b/packages/ui/src/containers/result-data/experimentation-guide.tsx @@ -29,6 +29,7 @@ import { ReactNode } from 'react' import { experimentResultSchema } from '@boostv/process-optimizer-frontend-core' import { z } from 'zod' import { isArray } from 'remeda' +import _ from 'lodash' interface ResultDataProps { id?: string @@ -39,6 +40,7 @@ interface ResultDataProps { warning?: string padding?: number allowIndividualSuggestionCopy?: boolean + maxSuggestionCount?: number toggleUISize?: () => void onMouseEnterExpand?: () => void onMouseLeaveExpand?: () => void @@ -54,6 +56,7 @@ export const ExperimentationGuide = (props: ResultDataProps) => { padding, loadingMode, allowIndividualSuggestionCopy = true, + maxSuggestionCount, toggleUISize, onMouseEnterExpand, onMouseLeaveExpand, @@ -112,6 +115,14 @@ export const ExperimentationGuide = (props: ResultDataProps) => { ) : ( Please run optimizer ) + + const debouncedUpdate = _.debounce(suggestionCount => { + dispatchExperiment({ + type: 'updateSuggestionCount', + payload: { suggestionCount, maxSuggestionCount }, + }) + }, 1000) + return ( { {!isInitializing && ( - dispatchExperiment({ - type: 'updateSuggestionCount', - payload: suggestionCount, - }) + debouncedUpdate(suggestionCount) } /> diff --git a/packages/ui/src/features/result-data/next-experiments.tsx b/packages/ui/src/features/result-data/next-experiments.tsx index 8a9b191a..7246eee4 100644 --- a/packages/ui/src/features/result-data/next-experiments.tsx +++ b/packages/ui/src/features/result-data/next-experiments.tsx @@ -1,5 +1,5 @@ import { TextField, Tooltip } from '@mui/material' -import { ChangeEvent, FC } from 'react' +import { ChangeEvent, FC, useState } from 'react' import { selectIsSuggestionCountEditable, selectCalculatedSuggestionCount, @@ -7,12 +7,17 @@ import { } from '@boostv/process-optimizer-frontend-core' type Props = { + maxSuggestionCount?: number onSuggestionChange: (suggestionCount: string) => void } -export const NextExperiments: FC = ({ onSuggestionChange }) => { +export const NextExperiments: FC = ({ + onSuggestionChange, + maxSuggestionCount, +}) => { const isSuggestionCountEditable = useSelector(selectIsSuggestionCountEditable) const suggestionCount = useSelector(selectCalculatedSuggestionCount) + const [suggestionCountUI, setSuggestionCountUI] = useState(suggestionCount) const handleSuggestionChange = ( e: ChangeEvent @@ -29,11 +34,16 @@ export const NextExperiments: FC = ({ onSuggestionChange }) => { > { + setSuggestionCountUI(Number(val.target.value)) + handleSuggestionChange(val) + }} disabled={!isSuggestionCountEditable} />