-
Notifications
You must be signed in to change notification settings - Fork 4
Feat/set row collapsing threshold #1127
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import React, { ReactElement, useState } from 'react' | ||
| import style from './account.module.css' | ||
| import { DEFAULT_CSV_COLLAPSE_THRESHOLD } from 'constants/index' | ||
|
|
||
| interface IProps { | ||
| csvCollapseThreshold: number | ||
| } | ||
|
|
||
| export default function ChangeCsvCollapseThreshold ({ csvCollapseThreshold }: IProps): ReactElement { | ||
| const [threshold, setThreshold] = useState(csvCollapseThreshold ?? DEFAULT_CSV_COLLAPSE_THRESHOLD) | ||
| const [inputValue, setInputValue] = useState(String(csvCollapseThreshold ?? DEFAULT_CSV_COLLAPSE_THRESHOLD)) | ||
| const [error, setError] = useState('') | ||
| const [success, setSuccess] = useState('') | ||
| const [disabled, setDisabled] = useState(true) | ||
|
|
||
| const onSubmit = async (e: React.FormEvent): Promise<void> => { | ||
| e.preventDefault() | ||
| const newThreshold = parseInt(inputValue, 10) | ||
| if (isNaN(newThreshold) || newThreshold < 0) { | ||
| setError('Please enter a valid non-negative number') | ||
| return | ||
| } | ||
|
|
||
| const oldThreshold = threshold | ||
| setThreshold(newThreshold) | ||
| setDisabled(true) | ||
|
|
||
| try { | ||
| const res = await fetch('/api/user/csvCollapseThreshold', { | ||
| method: 'PUT', | ||
| headers: { | ||
| 'Content-Type': 'application/json' | ||
| }, | ||
| body: JSON.stringify({ csvCollapseThreshold: newThreshold }) | ||
| }) | ||
| if (res.status === 200) { | ||
| setError('') | ||
| setSuccess('Updated successfully.') | ||
| setTimeout(() => { | ||
| setSuccess('') | ||
| }, 3000) | ||
| } else { | ||
| throw new Error('Failed to update threshold') | ||
| } | ||
| } catch (err: any) { | ||
| setSuccess('') | ||
| setError(err.message ?? 'Failed to update threshold') | ||
| setThreshold(oldThreshold) | ||
| setInputValue(String(oldThreshold)) | ||
| } | ||
| } | ||
|
|
||
| const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => { | ||
| const value = e.target.value | ||
| setInputValue(value) | ||
| const numValue = parseInt(value, 10) | ||
| if (!isNaN(numValue) && numValue >= 0 && numValue !== threshold) { | ||
| setDisabled(false) | ||
| setError('') | ||
| } else if (numValue === threshold) { | ||
| setDisabled(true) | ||
| } else { | ||
| setDisabled(true) | ||
| if (value !== '') { | ||
| setError('Please enter a valid non-negative number') | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <div className={style.threshold_ctn}> | ||
| <form onSubmit={(e) => { void onSubmit(e) }}> | ||
| <div className={style.threshold_row}> | ||
| <input | ||
| id="csvCollapseThreshold" | ||
| type="text" | ||
| min="0" | ||
| required | ||
| value={inputValue} | ||
| onChange={handleInputChange} | ||
|
Comment on lines
+74
to
+80
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: fd "ChangeCsvCollapseThreshold.tsx" --type fRepository: PayButton/paybutton-server Length of output: 119 🏁 Script executed: wc -l components/Account/ChangeCsvCollapseThreshold.tsxRepository: PayButton/paybutton-server Length of output: 122 🏁 Script executed: cat -n components/Account/ChangeCsvCollapseThreshold.tsxRepository: PayButton/paybutton-server Length of output: 3610 Add an accessible label and use a numeric input type. The input currently lacks an accessible label and Proposed fix <div className={style.threshold_row}>
+ <label htmlFor="csvCollapseThreshold">CSV collapse threshold</label>
<input
id="csvCollapseThreshold"
- type="text"
+ type="number"
min="0"
+ step="1"
+ inputMode="numeric"
required
value={inputValue}
onChange={handleInputChange}🤖 Prompt for AI Agents |
||
| className={style.threshold_input} | ||
| /> | ||
| <button disabled={disabled} className='button_main' type='submit'>Update</button> | ||
| </div> | ||
| {error !== '' && <div className={style.error_message}>{error}</div>} | ||
| {success !== '' && <div className={style.success_message}>{success}</div>} | ||
| </form> | ||
| </div> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { setSession } from 'utils/setSession' | ||||||||||||||||||||||||||||||||||||||||||||||
| import * as userService from 'services/userService' | ||||||||||||||||||||||||||||||||||||||||||||||
| import { RESPONSE_MESSAGES } from 'constants/index' | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export default async ( | ||||||||||||||||||||||||||||||||||||||||||||||
| req: any, | ||||||||||||||||||||||||||||||||||||||||||||||
| res: any | ||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<void> => { | ||||||||||||||||||||||||||||||||||||||||||||||
| await setSession(req, res, true) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (req.method === 'PUT') { | ||||||||||||||||||||||||||||||||||||||||||||||
| const session = req.session | ||||||||||||||||||||||||||||||||||||||||||||||
| const { csvCollapseThreshold } = req.body | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (csvCollapseThreshold === undefined || csvCollapseThreshold === null) { | ||||||||||||||||||||||||||||||||||||||||||||||
| res.status(400).json({ message: 'csvCollapseThreshold is required' }) | ||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const threshold = Number(csvCollapseThreshold) | ||||||||||||||||||||||||||||||||||||||||||||||
| if (isNaN(threshold) || threshold < 0) { | ||||||||||||||||||||||||||||||||||||||||||||||
| res.status(400).json({ message: 'csvCollapseThreshold must be a non-negative number' }) | ||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify JS coercion edge-cases that affect current validation logic.
node - <<'NODE'
const inputs = ['', ' ', '1.5', 'Infinity', null, undefined, '12abc'];
for (const v of inputs) {
console.log(JSON.stringify(v), '=> Number:', Number(v), 'isFinite:', Number.isFinite(Number(v)));
}
NODERepository: PayButton/paybutton-server Length of output: 330 🏁 Script executed: find . -type f -name "*.ts" -path "*/pages/api/user/csvCollapseThreshold*" | head -20Repository: PayButton/paybutton-server Length of output: 116 🏁 Script executed: cat -n pages/api/user/csvCollapseThreshold/index.tsRepository: PayButton/paybutton-server Length of output: 1301 🏁 Script executed: # Check the userService to understand what updateCsvCollapseThreshold expects
find . -type f -name "userService.ts" | head -5Repository: PayButton/paybutton-server Length of output: 95 🏁 Script executed: cat -n services/userService.ts | head -100Repository: PayButton/paybutton-server Length of output: 4017 🏁 Script executed: # Search for the updateCsvCollapseThreshold function specifically
rg -A 15 "updateCsvCollapseThreshold" services/userService.tsRepository: PayButton/paybutton-server Length of output: 520 🏁 Script executed: # Check if there's a Prisma schema that defines csvCollapseThreshold type
find . -type f \( -name "schema.prisma" -o -name "*.prisma" \)Repository: PayButton/paybutton-server Length of output: 98 🏁 Script executed: rg -A 3 -B 3 "csvCollapseThreshold" ./prisma-local/schema.prismaRepository: PayButton/paybutton-server Length of output: 275 🏁 Script executed: # Also check the UserProfile model in the schema
rg -A 50 "model UserProfile" ./prisma-local/schema.prisma | head -80Repository: PayButton/paybutton-server Length of output: 1930 Harden threshold validation to reject blank, non-finite, and non-integer values. Current coercion accepts invalid inputs like empty strings, whitespace, decimals, and Infinity. Since csvCollapseThreshold is defined as an Proposed fix- if (csvCollapseThreshold === undefined || csvCollapseThreshold === null) {
+ if (
+ csvCollapseThreshold === undefined ||
+ csvCollapseThreshold === null ||
+ (typeof csvCollapseThreshold === 'string' && csvCollapseThreshold.trim() === '')
+ ) {
res.status(400).json({ message: 'csvCollapseThreshold is required' })
return
}
- const threshold = Number(csvCollapseThreshold)
- if (isNaN(threshold) || threshold < 0) {
- res.status(400).json({ message: 'csvCollapseThreshold must be a non-negative number' })
+ const threshold = Number(csvCollapseThreshold)
+ if (!Number.isFinite(threshold) || !Number.isInteger(threshold) || threshold < 0) {
+ res.status(400).json({ message: 'csvCollapseThreshold must be a non-negative integer' })
return
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| await userService.updateCsvCollapseThreshold(session.userId, threshold) | ||||||||||||||||||||||||||||||||||||||||||||||
| res.status(200).json({ success: true }) | ||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||
| res.status(RESPONSE_MESSAGES.METHOD_NOT_ALLOWED_405.statusCode) | ||||||||||||||||||||||||||||||||||||||||||||||
| .json(RESPONSE_MESSAGES.METHOD_NOT_ALLOWED_405) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| -- AlterTable | ||
| ALTER TABLE `UserProfile` ADD COLUMN `csvCollapseThreshold` INTEGER NOT NULL DEFAULT 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: PayButton/paybutton-server
Length of output: 119
🏁 Script executed:
fd -i "changecsvcollapsethreshold" --type fRepository: PayButton/paybutton-server
Length of output: 119
🏁 Script executed:
Repository: PayButton/paybutton-server
Length of output: 3610
🏁 Script executed:
Repository: PayButton/paybutton-server
Length of output: 286
parseIntsilently truncates decimal input; use strict integer validation instead.Values like
1.5are accepted byisNaN()and truncated to1without warning. UseNumber()withNumber.isInteger()to enforce strict integer validation and reject decimal input explicitly.Proposed fix
Also applies to: lines 56-57
🤖 Prompt for AI Agents