Skip to content

Commit 01e797c

Browse files
select all, table search, pkey disable
1 parent c2c8b2f commit 01e797c

File tree

6 files changed

+401
-307
lines changed

6 files changed

+401
-307
lines changed

flow/cmd/peer_data.go

+37-5
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,40 @@ func (h *FlowRequestHandler) GetColumns(
135135
}
136136

137137
defer peerPool.Close()
138-
rows, err := peerPool.Query(ctx, "SELECT column_name, data_type"+
139-
" FROM information_schema.columns"+
140-
" WHERE table_schema = $1 AND table_name = $2;", req.SchemaName, req.TableName)
138+
rows, err := peerPool.Query(ctx, `
139+
SELECT
140+
cols.column_name,
141+
cols.data_type,
142+
CASE
143+
WHEN constraint_type = 'PRIMARY KEY' THEN true
144+
ELSE false
145+
END AS is_primary_key
146+
FROM
147+
information_schema.columns cols
148+
LEFT JOIN
149+
(
150+
SELECT
151+
kcu.column_name,
152+
tc.constraint_type
153+
FROM
154+
information_schema.key_column_usage kcu
155+
JOIN
156+
information_schema.table_constraints tc
157+
ON
158+
kcu.constraint_name = tc.constraint_name
159+
AND kcu.constraint_schema = tc.constraint_schema
160+
AND kcu.constraint_name = tc.constraint_name
161+
WHERE
162+
tc.constraint_type = 'PRIMARY KEY'
163+
AND kcu.table_schema = $1
164+
AND kcu.table_name = $2
165+
) AS pk
166+
ON
167+
cols.column_name = pk.column_name
168+
WHERE
169+
cols.table_schema = $3
170+
AND cols.table_name = $4;
171+
`, req.SchemaName, req.TableName, req.SchemaName, req.TableName)
141172
if err != nil {
142173
return &protos.TableColumnsResponse{Columns: nil}, err
143174
}
@@ -147,11 +178,12 @@ func (h *FlowRequestHandler) GetColumns(
147178
for rows.Next() {
148179
var columnName string
149180
var datatype string
150-
err := rows.Scan(&columnName, &datatype)
181+
var isPkey bool
182+
err := rows.Scan(&columnName, &datatype, &isPkey)
151183
if err != nil {
152184
return &protos.TableColumnsResponse{Columns: nil}, err
153185
}
154-
column := fmt.Sprintf("%s:%s", columnName, datatype)
186+
column := fmt.Sprintf("%s:%s:%v", columnName, datatype, isPkey)
155187
columns = append(columns, column)
156188
}
157189
return &protos.TableColumnsResponse{Columns: columns}, nil
+326
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
'use client';
2+
import { TableMapRow } from '@/app/dto/MirrorsDTO';
3+
import { DBType } from '@/grpc_generated/peers';
4+
import { Checkbox } from '@/lib/Checkbox';
5+
import { Icon } from '@/lib/Icon';
6+
import { Label } from '@/lib/Label';
7+
import { RowWithCheckbox } from '@/lib/Layout';
8+
import { SearchField } from '@/lib/SearchField';
9+
import { TextField } from '@/lib/TextField';
10+
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
11+
import { BarLoader } from 'react-spinners/';
12+
import { fetchColumns, fetchTables } from '../handlers';
13+
import { expandableStyle, schemaBoxStyle, tableBoxStyle } from './styles';
14+
15+
interface SchemaBoxProps {
16+
sourcePeer: string;
17+
schema: string;
18+
rows: TableMapRow[];
19+
setRows: Dispatch<SetStateAction<TableMapRow[]>>;
20+
tableColumns: { tableName: string; columns: string[] }[];
21+
setTableColumns: Dispatch<
22+
SetStateAction<{ tableName: string; columns: string[] }[]>
23+
>;
24+
peerType?: DBType;
25+
}
26+
const SchemaBox = ({
27+
sourcePeer,
28+
peerType,
29+
schema,
30+
rows,
31+
setRows,
32+
tableColumns,
33+
setTableColumns,
34+
}: SchemaBoxProps) => {
35+
const [tablesLoading, setTablesLoading] = useState(false);
36+
const [columnsLoading, setColumnsLoading] = useState(false);
37+
const [expandedSchemas, setExpandedSchemas] = useState<string[]>([]);
38+
const [tableQuery, setTableQuery] = useState<string>('');
39+
40+
const schemaIsExpanded = useCallback(
41+
(schema: string) => {
42+
return !!expandedSchemas.find((schemaName) => schemaName === schema);
43+
},
44+
[expandedSchemas]
45+
);
46+
47+
const handleAddRow = (source: string) => {
48+
const newRows = [...rows];
49+
const index = newRows.findIndex((row) => row.source === source);
50+
if (index >= 0) newRows[index] = { ...newRows[index], selected: true };
51+
setRows(newRows);
52+
addTableColumns(source);
53+
};
54+
55+
const handleRemoveRow = (source: string) => {
56+
const newRows = [...rows];
57+
const index = newRows.findIndex((row) => row.source === source);
58+
if (index >= 0) newRows[index] = { ...newRows[index], selected: false };
59+
setRows(newRows);
60+
removeTableColumns(source);
61+
};
62+
63+
const handleTableSelect = (on: boolean, source: string) => {
64+
on ? handleAddRow(source) : handleRemoveRow(source);
65+
};
66+
67+
const updateDestination = (source: string, dest: string) => {
68+
const newRows = [...rows];
69+
const index = newRows.findIndex((row) => row.source === source);
70+
newRows[index] = { ...newRows[index], destination: dest };
71+
setRows(newRows);
72+
};
73+
74+
const addTableColumns = (table: string) => {
75+
const schemaName = table.split('.')[0];
76+
const tableName = table.split('.')[1];
77+
fetchColumns(sourcePeer, schemaName, tableName, setColumnsLoading).then(
78+
(res) =>
79+
setTableColumns((prev) => {
80+
return [...prev, { tableName: table, columns: res }];
81+
})
82+
);
83+
};
84+
85+
const removeTableColumns = (table: string) => {
86+
setTableColumns((prev) => {
87+
return prev.filter((column) => column.tableName !== table);
88+
});
89+
};
90+
91+
const getTableColumns = (tableName: string) => {
92+
return tableColumns?.find((column) => column.tableName === tableName)
93+
?.columns;
94+
};
95+
96+
const handleColumnExclusion = (
97+
source: string,
98+
column: string,
99+
include: boolean
100+
) => {
101+
const currRows = [...rows];
102+
const rowOfSource = currRows.find((row) => row.source === source);
103+
if (rowOfSource) {
104+
if (include) {
105+
const updatedExclude = rowOfSource.exclude.filter(
106+
(col) => col !== column
107+
);
108+
rowOfSource.exclude = updatedExclude;
109+
} else {
110+
rowOfSource.exclude.push(column);
111+
}
112+
}
113+
setRows(currRows);
114+
};
115+
116+
const handleSelectAll = (
117+
e: React.MouseEvent<HTMLInputElement, MouseEvent>
118+
) => {
119+
const newRows = [...rows];
120+
for (const row of newRows) {
121+
row.selected = e.currentTarget.checked;
122+
if (e.currentTarget.checked) addTableColumns(row.source);
123+
else removeTableColumns(row.source);
124+
}
125+
setRows(newRows);
126+
};
127+
128+
const handleSchemaClick = (schemaName: string) => {
129+
if (!schemaIsExpanded(schemaName)) {
130+
setTablesLoading(true);
131+
setExpandedSchemas((curr) => [...curr, schemaName]);
132+
fetchTables(sourcePeer, schemaName, peerType).then((tableRows) => {
133+
const newRows = [...rows, ...tableRows];
134+
setRows(newRows);
135+
setTablesLoading(false);
136+
});
137+
} else {
138+
setExpandedSchemas((curr) =>
139+
curr.filter((expandedSchema) => expandedSchema != schemaName)
140+
);
141+
setRows((curr) => curr.filter((row) => row.schema !== schemaName));
142+
}
143+
};
144+
145+
return (
146+
<div style={schemaBoxStyle}>
147+
<div>
148+
<div style={{ ...expandableStyle, cursor: 'auto' }}>
149+
<div
150+
style={{ display: 'flex', cursor: 'pointer' }}
151+
onClick={() => handleSchemaClick(schema)}
152+
>
153+
<Icon
154+
name={
155+
schemaIsExpanded(schema) ? 'arrow_drop_down' : 'arrow_right'
156+
}
157+
/>
158+
<p>{schema}</p>
159+
</div>
160+
<div style={{ display: schemaIsExpanded(schema) ? 'flex' : 'none' }}>
161+
<div style={{ display: 'flex' }}>
162+
<input type='checkbox' onClick={(e) => handleSelectAll(e)} />
163+
<Label as='label' style={{ fontSize: 14 }}>
164+
Select All
165+
</Label>
166+
</div>
167+
<SearchField
168+
style={{ fontSize: 13 }}
169+
placeholder='Search for tables'
170+
value={tableQuery}
171+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
172+
setTableQuery(e.target.value)
173+
}
174+
/>
175+
</div>
176+
</div>
177+
{schemaIsExpanded(schema) && (
178+
<div className='ml-5 mt-3' style={{ width: '90%' }}>
179+
{rows.filter((row) => row.schema === schema).length ? (
180+
rows
181+
.filter(
182+
(row) =>
183+
row.schema === schema &&
184+
row.source.toLowerCase().includes(tableQuery.toLowerCase())
185+
)
186+
.map((row, index) => {
187+
const columns = getTableColumns(row.source);
188+
return (
189+
<div key={index} style={tableBoxStyle}>
190+
<div
191+
style={{
192+
display: 'flex',
193+
alignItems: 'center',
194+
justifyContent: 'space-between',
195+
}}
196+
>
197+
<RowWithCheckbox
198+
label={
199+
<Label as='label' style={{ fontSize: 13 }}>
200+
{row.source}
201+
</Label>
202+
}
203+
action={
204+
<Checkbox
205+
checked={row.selected}
206+
onCheckedChange={(state: boolean) =>
207+
handleTableSelect(state, row.source)
208+
}
209+
/>
210+
}
211+
/>
212+
213+
<div
214+
style={{
215+
width: '40%',
216+
display: row.selected ? 'block' : 'none',
217+
}}
218+
key={row.source}
219+
>
220+
<p style={{ fontSize: 12 }}>Target Table:</p>
221+
<TextField
222+
key={row.source}
223+
style={{
224+
fontSize: 12,
225+
marginTop: '0.5rem',
226+
}}
227+
variant='simple'
228+
placeholder={'Enter target table'}
229+
defaultValue={row.destination}
230+
onChange={(
231+
e: React.ChangeEvent<HTMLInputElement>
232+
) => updateDestination(row.source, e.target.value)}
233+
/>
234+
</div>
235+
</div>
236+
{row.selected && (
237+
<div className='ml-5' style={{ width: '100%' }}>
238+
<Label
239+
as='label'
240+
colorName='lowContrast'
241+
style={{ fontSize: 13 }}
242+
>
243+
Columns
244+
</Label>
245+
{columns ? (
246+
columns.map((column, index) => {
247+
const columnName = column.split(':')[0];
248+
const columnType = column.split(':')[1];
249+
const isPkey = column.split(':')[2] === 'true';
250+
return (
251+
<RowWithCheckbox
252+
key={index}
253+
label={
254+
<Label
255+
as='label'
256+
style={{
257+
fontSize: 13,
258+
display: 'flex',
259+
}}
260+
>
261+
{columnName}{' '}
262+
<p
263+
style={{
264+
marginLeft: '0.5rem',
265+
color: 'gray',
266+
}}
267+
>
268+
{columnType}
269+
</p>
270+
</Label>
271+
}
272+
action={
273+
<Checkbox
274+
disabled={isPkey}
275+
checked={
276+
!row.exclude.find(
277+
(col) => col == columnName
278+
)
279+
}
280+
onCheckedChange={(state: boolean) =>
281+
handleColumnExclusion(
282+
row.source,
283+
columnName,
284+
state
285+
)
286+
}
287+
/>
288+
}
289+
/>
290+
);
291+
})
292+
) : columnsLoading ? (
293+
<BarLoader />
294+
) : (
295+
<Label
296+
as='label'
297+
colorName='lowContrast'
298+
style={{ fontSize: 13 }}
299+
>
300+
No columns in {row.source}
301+
</Label>
302+
)}
303+
</div>
304+
)}
305+
</div>
306+
);
307+
})
308+
) : tablesLoading ? (
309+
<BarLoader />
310+
) : (
311+
<Label
312+
as='label'
313+
colorName='lowContrast'
314+
style={{ fontSize: 13 }}
315+
>
316+
No tables in {schema}
317+
</Label>
318+
)}
319+
</div>
320+
)}
321+
</div>
322+
</div>
323+
);
324+
};
325+
326+
export default SchemaBox;

0 commit comments

Comments
 (0)