csv result copy#4091
Conversation
Signed-off-by: SOUISSI Maissa (Externe) <souissimai@gm0winl878.bureau.si.interne>
📝 WalkthroughWalkthroughChangesSecurity analysis results add a CSV copy action that extracts and converts CSV data from a ZIP blob, while export and copy share translated enum values supplied by the result tab. Security analysis actions
Sequence Diagram(s)sequenceDiagram
participant ResultTab
participant SecurityAnalysisCopyButton
participant downloadZipResult
participant fflate
participant Clipboard
ResultTab->>SecurityAnalysisCopyButton: provide translated values and download callback
SecurityAnalysisCopyButton->>downloadZipResult: request ZIP result
downloadZipResult-->>SecurityAnalysisCopyButton: return ZIP Blob
SecurityAnalysisCopyButton->>fflate: extract first CSV
fflate-->>SecurityAnalysisCopyButton: return CSV text
SecurityAnalysisCopyButton->>Clipboard: copy converted CSV
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/results/securityanalysis/security-analysis-copy-button.tsx`:
- Line 50: Remove the entryNames[0] fallback from the csvEntryName assignment in
the security analysis copy-button logic, leaving it undefined when no .csv file
exists so the existing !csvEntryName check fails loudly for unexpected archive
contents.
- Around line 63-71: The convertDelimiter function must preserve CSV quoting,
embedded separators, and newlines instead of using plain split operations.
Replace its line and delimiter splitting with a CSV-aware parser and serializer,
while retaining the early return when separators match and ensuring equivalent
valid CSV output with the requested delimiter.
- Around line 46-57: Replace synchronous unzipSync usage in
extractCsvTextFromZipBlob with fflate’s asynchronous unzip API, awaiting
decompression before selecting and decoding the CSV entry. Preserve the existing
fallback entry selection, missing-entry error, UTF-8 decoding, and BOM removal
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4723026b-ba1f-4a32-b600-5a0b6ac2891d
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
package.jsonsrc/components/results/securityanalysis/security-analysis-copy-button.tsxsrc/components/results/securityanalysis/security-analysis-export-button.tsxsrc/components/results/securityanalysis/security-analysis-result-tab.tsx
| async function extractCsvTextFromZipBlob(blob: Blob): Promise<string> { | ||
| const buffer = new Uint8Array(await blob.arrayBuffer()); | ||
| const unzipped = unzipSync(buffer); | ||
| const entryNames = Object.keys(unzipped); | ||
| const csvEntryName = entryNames.find((name) => name.toLowerCase().endsWith('.csv')) ?? entryNames[0]; | ||
| if (!csvEntryName) { | ||
| throw new Error('No CSV entry found in zip archive'); | ||
| } | ||
| // Decode as UTF-8 and strip a potential BOM (backend prefixes result.csv with one) | ||
| const text = new TextDecoder('utf-8').decode(unzipped[csvEntryName]); | ||
| return text.replace(/^\uFEFF/, ''); | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## target file\n'
git ls-files 'src/components/results/securityanalysis/security-analysis-copy-button.tsx'
printf '\n## package references\n'
rg -n '"fflate"|from '\''fflate'\''|from "fflate"' package.json pnpm-lock.yaml yarn.lock package-lock.json src -g '!**/node_modules/**'
printf '\n## inspect target file (bounded)\n'
wc -l src/components/results/securityanalysis/security-analysis-copy-button.tsx
cat -n src/components/results/securityanalysis/security-analysis-copy-button.tsx | sed -n '1,220p'
printf '\n## inspect any nearby zip/csv helpers\n'
rg -n "unzipSync|unzip\\(|arrayBuffer\\(|TextDecoder|result\\.csv|security analysis" src/components -g '!**/node_modules/**'
printf '\n## read fflate package metadata/docs if vendored in lockfile\n'
python3 - <<'PY'
import re, pathlib, json, sys
for p in ["package.json", "pnpm-lock.yaml", "yarn.lock", "package-lock.json"]:
path = pathlib.Path(p)
if path.exists():
print(f"\n--- {p} ---")
text = path.read_text(errors="ignore")
if p == "package.json":
try:
data = json.loads(text)
deps = {}
for k in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
deps.update(data.get(k, {}))
if "fflate" in deps:
print("fflate:", deps["fflate"])
except Exception as e:
print("package.json parse error:", e)
else:
for m in re.finditer(r'fflate[@:/ ]([^\s"\'\n]+)', text):
print("match:", m.group(0))
PY
printf '\n## if node_modules exists, inspect installed fflate docs without running repo code\n'
if [ -d node_modules/fflate ]; then
sed -n '1,220p' node_modules/fflate/README.md
else
echo "node_modules/fflate not present"
fiRepository: gridsuite/gridstudy-app
Length of output: 593
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## target file, bounded\n'
wc -l src/components/results/securityanalysis/security-analysis-copy-button.tsx
cat -n src/components/results/securityanalysis/security-analysis-copy-button.tsx | sed -n '1,220p'
printf '\n## related references in src\n'
rg -n "unzipSync|unzip\\(|security-analysis-copy-button|result\\.csv|TextDecoder" src -g '!**/node_modules/**'
printf '\n## fflate docs from installed package if available\n'
if [ -d node_modules/fflate ]; then
rg -n "worker|web worker|unzip\\(|unzipSync|async unzip|callback" node_modules/fflate/README.md node_modules/fflate/*.d.ts node_modules/fflate/*.js -g '!**/node_modules/**'
else
echo "node_modules/fflate not present"
fiRepository: gridsuite/gridstudy-app
Length of output: 9051
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## fflate README relevant excerpts\n'
sed -n '370,470p' node_modules/fflate/README.md
printf '\n## package.json dependency entry\n'
sed -n '1,70p' package.jsonRepository: gridsuite/gridstudy-app
Length of output: 6695
Avoid synchronous ZIP decompression on the UI thread. unzipSync blocks the copy action for large result ZIPs; switch to fflate.unzip here so decompression runs off-thread. The async API has startup overhead, but this path is user-triggered and better fits larger archives.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/results/securityanalysis/security-analysis-copy-button.tsx`
around lines 46 - 57, Replace synchronous unzipSync usage in
extractCsvTextFromZipBlob with fflate’s asynchronous unzip API, awaiting
decompression before selecting and decoding the CSV entry. Preserve the existing
fallback entry selection, missing-entry error, UTF-8 decoding, and BOM removal
behavior.
| const buffer = new Uint8Array(await blob.arrayBuffer()); | ||
| const unzipped = unzipSync(buffer); | ||
| const entryNames = Object.keys(unzipped); | ||
| const csvEntryName = entryNames.find((name) => name.toLowerCase().endsWith('.csv')) ?? entryNames[0]; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
csvEntryName fallback may silently process non-CSV files.
When no .csv entry is found, the code falls back to entryNames[0], which could be any file type (e.g., a metadata JSON or README). The subsequent if (!csvEntryName) check only catches empty archives. Remove the fallback to fail loudly on unexpected archive contents.
🛡️ Proposed fix
- const csvEntryName = entryNames.find((name) => name.toLowerCase().endsWith('.csv')) ?? entryNames[0];
+ const csvEntryName = entryNames.find((name) => name.toLowerCase().endsWith('.csv'));
if (!csvEntryName) {
throw new Error('No CSV entry found in zip archive');
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const csvEntryName = entryNames.find((name) => name.toLowerCase().endsWith('.csv')) ?? entryNames[0]; | |
| const csvEntryName = entryNames.find((name) => name.toLowerCase().endsWith('.csv')); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/results/securityanalysis/security-analysis-copy-button.tsx` at
line 50, Remove the entryNames[0] fallback from the csvEntryName assignment in
the security analysis copy-button logic, leaving it undefined when no .csv file
exists so the existing !csvEntryName check fails loudly for unexpected archive
contents.
| function convertDelimiter(csvText: string, fromSeparator: string, toSeparator: string): string { | ||
| if (fromSeparator === toSeparator) { | ||
| return csvText; | ||
| } | ||
| return csvText | ||
| .split('\n') | ||
| .map((line) => line.replace(/\r$/, '').split(fromSeparator).join(toSeparator)) | ||
| .join('\n'); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File outline ==\n'
ast-grep outline src/components/results/securityanalysis/security-analysis-copy-button.tsx --view expanded || true
printf '\n== Relevant file excerpt ==\n'
nl -ba src/components/results/securityanalysis/security-analysis-copy-button.tsx | sed -n '1,180p'
printf '\n== CSV-related dependencies ==\n'
if [ -f package.json ]; then
python3 - <<'PY'
import json
from pathlib import Path
pkg = json.loads(Path('package.json').read_text())
for section in ('dependencies','devDependencies','peerDependencies'):
deps = pkg.get(section, {})
hits = {k:v for k,v in deps.items() if 'csv' in k.lower() or 'papaparse' in k.lower() or 'parse' in k.lower()}
if hits:
print(section, hits)
PY
fi
printf '\n== Search for papaparse / csv helpers ==\n'
rg -n "papaparse|PapaParse|csv-?parse|csv-?stringify|fromSeparator|convertDelimiter" -S . || trueRepository: gridsuite/gridstudy-app
Length of output: 864
🏁 Script executed:
sed -n '1,180p' src/components/results/securityanalysis/security-analysis-copy-button.tsx | nl -baRepository: gridsuite/gridstudy-app
Length of output: 201
convertDelimiter can corrupt quoted CSV fields. split('\n') and split(fromSeparator) ignore CSV quoting, so separators or line breaks inside quoted values will be rewritten incorrectly before copying. Use a CSV-aware parser/serializer here instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/results/securityanalysis/security-analysis-copy-button.tsx`
around lines 63 - 71, The convertDelimiter function must preserve CSV quoting,
embedded separators, and newlines instead of using plain split operations.
Replace its line and delimiter splitting with a CSV-aware parser and serializer,
while retaining the early return when separators match and ensuring equivalent
valid CSV output with the requested delimiter.
PR Summary