Skip to content

Commit c5f0257

Browse files
authored
Merge pull request #10 from NGO-Algorithm-Audit/feature/dropdown-additional-changes
Feature/dropdown additional changes
2 parents 16b6759 + e701330 commit c5f0257

File tree

7 files changed

+121
-46
lines changed

7 files changed

+121
-46
lines changed

src/assets/synthetic-data.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,9 +592,9 @@ def run():
592592
print("Original Data (first 5 rows):", real_data.head())
593593
print("Synthetic Data (first 5 rows):", synthetic_data.head())
594594
595+
# Store synthetic data for export
595596
setOutputData("syntheticData", synthetic_data.to_json(orient='records'))
596597
597-
598598
results = run_diagnostic(real_data, synthetic_data, target_column='gpa')
599599
print('Results:', results)
600600
setResult(json.dumps(
@@ -616,7 +616,7 @@ def run():
616616
setResult(json.dumps(
617617
{'type': 'heading', 'data': 'Output file:'}
618618
))
619-
setResult(json.dumps({'type': 'table', 'data': synthetic_data.to_json(orient="records")}))
619+
setResult(json.dumps({'type': 'table', 'data': synthetic_data.head().to_json(orient="records")}))
620620
621621
np.random.seed(42)
622622
heatmap = np.random.rand(100, 10)

src/components/ui/dropdown-menu.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@ import { cn } from '@/lib/utils';
66

77
const DropdownMenu = DropdownMenuPrimitive.Root;
88

9-
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
9+
const DropdownMenuTrigger = React.forwardRef<
10+
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
11+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
12+
>(({ className, ...props }, ref) => (
13+
<DropdownMenuPrimitive.Trigger
14+
ref={ref}
15+
className={cn('group', className)}
16+
{...props}
17+
/>
18+
));
19+
DropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName;
1020

1121
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
1222

src/lib/utils.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,35 @@ import { twMerge } from 'tailwind-merge';
44
export function cn(...inputs: ClassValue[]) {
55
return twMerge(clsx(inputs));
66
}
7+
8+
export function exportToCSV(data: any[], filename: string) {
9+
// Convert data to CSV format
10+
const csvContent = data.map(row => {
11+
return Object.values(row)
12+
.map(value => {
13+
// Handle special characters and wrap in quotes if needed
14+
const stringValue = String(value);
15+
return stringValue.includes(',') ||
16+
stringValue.includes('"') ||
17+
stringValue.includes('\n')
18+
? `"${stringValue.replace(/"/g, '""')}"`
19+
: stringValue;
20+
})
21+
.join(',');
22+
});
23+
24+
// Add headers
25+
const headers = Object.keys(data[0]).join(',');
26+
const csvString = [headers, ...csvContent].join('\n');
27+
28+
// Create and trigger download
29+
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
30+
const link = document.createElement('a');
31+
const url = URL.createObjectURL(blob);
32+
link.setAttribute('href', url);
33+
link.setAttribute('download', `${filename}.csv`);
34+
link.style.visibility = 'hidden';
35+
document.body.appendChild(link);
36+
link.click();
37+
document.body.removeChild(link);
38+
}

src/locales/en.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"exportToJSON": "Export synthetic data to JSON",
1616
"downloadButton": "Download",
1717
"biasSettings": {
18-
"exportToPDF": "Download bias detection report as pdf",
18+
"exportToPDF": "Download bias detection report as PDF",
1919
"exportToJSON": "Export clusters as JSON",
2020
"form": {
2121
"fieldsets": {
@@ -57,8 +57,9 @@
5757
}
5858
},
5959
"syntheticData": {
60-
"exportToPDF": "Download evaluation report as pdf",
60+
"exportToPDF": "Download evaluation report as PDF",
6161
"exportToJSON": "Download synthetic data as JSON",
62+
"exportToCSV": "Download synthetic data as CSV",
6263
"form": {
6364
"errors": {
6465
"csvRequired": "Please upload a CSV file.",

src/locales/nl.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
"downloadButton": "Download",
1515

1616
"biasSettings": {
17-
"exportToPDF": "Download bias detection rapport als pdf",
17+
"exportToPDF": "Download bias detection rapport als PDF",
1818
"exportToJSON": "Export clusters als JSON",
19+
1920
"form": {
2021
"fieldsets": {
2122
"data": {
@@ -56,8 +57,9 @@
5657
}
5758
},
5859
"syntheticData": {
59-
"exportToPDF": "Download evulatie rapport als pdf",
60+
"exportToPDF": "Download evulatie rapport als PDF",
6061
"exportToJSON": "Download synthetische data als JSON",
62+
"exportToCSV": "Download synthetische data als CSV",
6163
"form": {
6264
"errors": {
6365
"csvRequired": "Upload een CSV-bestand.",

src/routes/BiasDetection.tsx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react';
33
import { pythonCode } from '@/assets/bias-detection-python-code';
44
import { usePython } from '@/components/pyodide/use-python';
55
import BiasSettings from '@/components/BiasSettings';
6-
import { Share } from 'lucide-react';
6+
import { ChevronDown, Share } from 'lucide-react';
77
import { csvReader } from '@/components/CSVReader';
88
import { cn } from '@/lib/utils';
99
import ComponentMapper from '@/components/componentMapper';
@@ -20,7 +20,7 @@ import {
2020
DropdownMenuContent,
2121
DropdownMenuItem,
2222
DropdownMenuTrigger,
23-
} from "@/components/ui/dropdown-menu";
23+
} from '@/components/ui/dropdown-menu';
2424

2525
const PAGE_STYLE = `
2626
@page {
@@ -165,31 +165,40 @@ export default function BiasDetection() {
165165
<div className="ml-auto flex flex-row gap-2 hideonprint">
166166
<DropdownMenu>
167167
<DropdownMenuTrigger asChild>
168-
<Button variant="outline" size="sm" className="p-4 text-sm">
168+
<Button
169+
variant="outline"
170+
size="sm"
171+
className="p-4 text-sm"
172+
>
169173
{t('downloadButton')}
174+
<ChevronDown className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180" />
170175
</Button>
171176
</DropdownMenuTrigger>
172177
<DropdownMenuContent align="end">
173-
<DropdownMenuItem onClick={() => reactToPrintFn()}>
178+
<DropdownMenuItem
179+
onClick={() => reactToPrintFn()}
180+
>
174181
<Share className="size-3.5 mr-2" />
175182
{t('biasSettings.exportToPDF')}
176183
</DropdownMenuItem>
177184
{clusterInfo && (
178-
<DropdownMenuItem onClick={() => {
179-
downloadFile(
180-
JSON.stringify(
181-
{
182-
fileName: data.fileName,
183-
...clusterInfo,
184-
},
185-
null,
186-
2
187-
),
188-
`${data.fileName.replace('.csv', '') || 'cluster-info'}-${clusterInfo.date.toISOString()}.json`,
189-
'application/json'
190-
);
191-
}}>
192-
<Share className="size-3.5 mr-2" />
185+
<DropdownMenuItem
186+
onClick={() => {
187+
downloadFile(
188+
JSON.stringify(
189+
{
190+
fileName: data.fileName,
191+
...clusterInfo,
192+
},
193+
null,
194+
2
195+
),
196+
`${data.fileName.replace('.csv', '') || 'cluster-info'}-${clusterInfo.date.toISOString()}.json`,
197+
'application/json'
198+
);
199+
}}
200+
>
201+
<Share className="size-3.5 mr-2" />
193202
{t('biasSettings.exportToJSON')}
194203
</DropdownMenuItem>
195204
)}

src/routes/SyntheticData.tsx

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Button } from '@/components/ui/button';
22
import { useEffect, useRef, useState } from 'react';
33
import { pythonCode } from '@/assets/synthetic-data';
44
import { usePython } from '@/components/pyodide/use-python';
5-
import { Share } from 'lucide-react';
5+
import { Share, ChevronDown } from 'lucide-react';
66
import { csvReader } from '@/components/CSVReader';
77
import { cn } from '@/lib/utils';
88
import ComponentMapper from '@/components/componentMapper';
@@ -20,6 +20,7 @@ import {
2020
} from '@/components/ui/dropdown-menu';
2121
import { downloadFile } from '@/lib/download-file';
2222
import { SyntheticDataParameters } from '@/components/synthetic-data-interfaces/SyntheticDataParameters';
23+
import { exportToCSV } from '@/lib/utils';
2324

2425
const PAGE_STYLE = `
2526
@page {
@@ -132,6 +133,12 @@ export default function SyntheticDataGeneration() {
132133
});
133134
};
134135

136+
const handleExport = (syntheticData: object[]) => {
137+
if (syntheticData.length > 0) {
138+
exportToCSV(syntheticData, 'synthetic_data');
139+
}
140+
};
141+
135142
return (
136143
<main ref={contentRef} className="gap-4 p-4 flex flex-col">
137144
{!lang && <LanguageSwitcher />}
@@ -161,6 +168,7 @@ export default function SyntheticDataGeneration() {
161168
className="p-4 text-sm"
162169
>
163170
{t('downloadButton')}
171+
<ChevronDown className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180" />
164172
</Button>
165173
</DropdownMenuTrigger>
166174
<DropdownMenuContent align="end">
@@ -171,25 +179,38 @@ export default function SyntheticDataGeneration() {
171179
{t('syntheticData.exportToPDF')}
172180
</DropdownMenuItem>
173181
{clusterInfo && (
174-
<DropdownMenuItem
175-
onClick={() => {
176-
downloadFile(
177-
JSON.stringify(
178-
{
179-
fileName: data.fileName,
180-
...clusterInfo,
181-
},
182-
null,
183-
2
184-
),
185-
`${data.fileName.replace('.csv', '') || 'cluster-info'}-${clusterInfo.date.toISOString()}.json`,
186-
'application/json'
187-
);
188-
}}
189-
>
190-
<Share className="size-3.5 mr-2" />
191-
{t('syntheticData.exportToJSON')}
192-
</DropdownMenuItem>
182+
<>
183+
<DropdownMenuItem
184+
onClick={() => {
185+
downloadFile(
186+
JSON.stringify(
187+
{
188+
fileName:
189+
data.fileName,
190+
...clusterInfo,
191+
},
192+
null,
193+
2
194+
),
195+
`${data.fileName.replace('.csv', '') || 'cluster-info'}-${clusterInfo.date.toISOString()}.json`,
196+
'application/json'
197+
);
198+
}}
199+
>
200+
<Share className="size-3.5 mr-2" />
201+
{t('syntheticData.exportToJSON')}
202+
</DropdownMenuItem>
203+
<DropdownMenuItem
204+
onClick={() => {
205+
handleExport(
206+
clusterInfo.syntheticData as object[]
207+
);
208+
}}
209+
>
210+
<Share className="size-3.5 mr-2" />
211+
{t('syntheticData.exportToCSV')}
212+
</DropdownMenuItem>
213+
</>
193214
)}
194215
</DropdownMenuContent>
195216
</DropdownMenu>

0 commit comments

Comments
 (0)