feat(airtable-migration): clean up for pr

This commit is contained in:
Han Tuerker
2022-08-10 06:13:41 +03:00
parent c19b8df4d6
commit 4c22442379
8 changed files with 106 additions and 112 deletions

View File

@@ -100,16 +100,27 @@ export const tableModalAtom = atomWithHash<
| "export"
| "importExisting"
| "importCsv"
| "importAirtable"
| null
>("tableModal", null, { replaceState: true });
export type ImportCsvData = { columns: string[]; rows: Record<string, any>[] };
export type ImportAirtableData = { records: Record<string, any>[] };
/** Store import CSV popover and wizard state */
export const importCsvAtom = atom<{
importType: "csv" | "tsv";
csvData: ImportCsvData | null;
}>({ importType: "csv", csvData: null });
/** Store import Airtable popover and wizard state */
export const importAirtableAtom = atom<{
airtableData: ImportAirtableData | null;
apiKey: string;
baseId: string;
tableId: string;
}>({ airtableData: null, apiKey: "", baseId: "", tableId: "" });
/** Store side drawer open state */
export const sideDrawerOpenAtom = atom(false);

View File

@@ -53,13 +53,6 @@ export interface IStepProps {
export const airtableFieldParser = (fieldType: FieldType) => {
switch (fieldType) {
case FieldType.percentage:
return (v: string) => {
const numValue = parseFloat(v && v.includes("%") ? v.slice(0, -1) : v);
return isNaN(numValue) ? null : numValue / 100;
};
case FieldType.multiSelect:
return (v: string[]) => v;
case FieldType.date:
case FieldType.dateTime:
return (v: string) => {
@@ -67,7 +60,7 @@ export const airtableFieldParser = (fieldType: FieldType) => {
return isValidDate(date) ? date.getTime() : null;
};
default:
return null;
return (v: string) => v;
}
};
@@ -176,31 +169,31 @@ export default function ImportAirtableWizard({ onClose }: ITableModalProps) {
// Airtable Rate Limits: 5 req/sec
const RATE_LIMIT = { REQ_PER_SECOND: 5 };
const fetcher = async (i: number, offset?: string): Promise<void> => {
const fetcher = async (i: number = 0, offset?: string): Promise<void> => {
console.log(i, offset, promises);
if (offset) {
const { records, offset: nextPage } = await fetchRecords(offset);
snackbarProgressRef.current?.setTarget(
(prev) => prev + records.length
);
promises.push(
bulkAddRows({
rows: parseRecords(records),
collection: tableSettings.collection,
}).then(() => {
countRef.current += records.length;
snackbarProgressRef.current?.setProgress(
(prev) => prev + records.length
);
})
);
if (i < RATE_LIMIT.REQ_PER_SECOND - 1) {
promises.push(fetcher(++i, nextPage));
} else {
promises.push(timeout(1050).then(() => fetcher(0, nextPage)));
}
const { records, offset: nextPage } = await fetchRecords(offset);
snackbarProgressRef.current?.setTarget((prev) => prev + records.length);
promises.push(
bulkAddRows({
rows: parseRecords(records),
collection: tableSettings.collection,
}).then(() => {
countRef.current += records.length;
snackbarProgressRef.current?.setProgress(
(prev) => prev + records.length
);
})
);
if (!nextPage) {
return;
}
if (i < RATE_LIMIT.REQ_PER_SECOND - 1) {
promises.push(fetcher(++i, nextPage));
} else {
promises.push(timeout(1050).then(() => fetcher(0, nextPage)));
}
};
const resolveAll = async (): Promise<void[]> => {
return Promise.all(promises).then((result) => {
if (result.length === promises.length) {
@@ -215,10 +208,7 @@ export default function ImportAirtableWizard({ onClose }: ITableModalProps) {
for (const col of config.newColumns)
promises.push(addColumn({ config: col }));
const { records, offset: nextPage } = await fetchRecords();
snackbarProgressRef.current?.setTarget(records.length);
await fetcher(1, nextPage);
await fetcher();
await resolveAll();
enqueueSnackbar(

View File

@@ -95,8 +95,8 @@ export default function Step1Columns({
};
const handleChange = (fieldKey: string) => (value: string) => {
if (!value) return;
const columnKey = !!tableSchema.columns?.[value] ? value : camelCase(value);
console.log(config);
// Check if this pair already exists in config
const configIndex = findIndex(config.pairs, { fieldKey });
if (configIndex > -1) {

View File

@@ -136,11 +136,9 @@ export default function Step2NewColumns({
<Grid item xs style={{ overflow: "hidden" }}>
<Cell
field={config.newColumns[fieldToEdit].key}
value={
airtableFieldParser(config.newColumns[fieldToEdit].type)?.(
cell
) ?? cell
}
value={airtableFieldParser(
config.newColumns[fieldToEdit].type
)?.(cell)}
type={config.newColumns[fieldToEdit].type}
name={config.newColumns[fieldToEdit].name}
/>

View File

@@ -63,10 +63,7 @@ export default function Step3Preview({ airtableData, config }: IStepProps) {
<Cell
key={fieldKey + i}
field={columnKey}
value={
airtableFieldParser(type)?.(record.fields[fieldKey]) ??
record.fields[fieldKey]
}
value={airtableFieldParser(type)?.(record.fields[fieldKey])}
type={type}
name={name}
/>

View File

@@ -0,0 +1,2 @@
export * from "./ImportAirtableWizard";
export { default } from "./ImportAirtableWizard";

View File

@@ -15,6 +15,8 @@ const WebhooksModal = lazy(() => import("./WebhooksModal" /* webpackChunkName: "
const ImportExistingWizard = lazy(() => import("./ImportExistingWizard" /* webpackChunkName: "TableModals-ImportExistingWizard" */));
// prettier-ignore
const ImportCsvWizard = lazy(() => import("./ImportCsvWizard" /* webpackChunkName: "TableModals-ImportCsvWizard" */));
// prettier-ignore
const ImportAirtableWizard = lazy(() => import("./ImportAirtableWizard" /* webpackChunkName: "TableModals-ImportAirtableWizard" */));
export interface ITableModalProps {
onClose: () => void;
@@ -34,6 +36,8 @@ export default function TableModals() {
if (tableModal === "importExisting")
return <ImportExistingWizard onClose={onClose} />;
if (tableModal === "importCsv") return <ImportCsvWizard onClose={onClose} />;
if (tableModal === "importAirtable")
return <ImportAirtableWizard onClose={onClose} />;
return null;
}

View File

@@ -31,6 +31,7 @@ import {
tableSettingsAtom,
tableModalAtom,
importCsvAtom,
importAirtableAtom,
} from "@src/atoms/tableScope";
import { analytics, logEvent } from "@src/analytics";
@@ -51,19 +52,23 @@ export interface IImportCsvProps {
export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
const [userRoles] = useAtom(userRolesAtom, globalScope);
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
const [{ importType, csvData }, setImportCsv] = useAtom(
const [{ importType: importTypeCsv, csvData }, setImportCsv] = useAtom(
importCsvAtom,
tableScope
);
const [{ airtableData, baseId, tableId, apiKey }, setImportAirtable] =
useAtom(importAirtableAtom, tableScope);
const openTableModal = useSetAtom(tableModalAtom, tableScope);
const { enqueueSnackbar } = useSnackbar();
const importTypeRef = useRef(importType);
const importTypeRef = useRef(importTypeCsv);
const importMethodRef = useRef(ImportMethod.upload);
const [open, setOpen] = useState<HTMLButtonElement | null>(null);
const [tab, setTab] = useState("upload");
const [error, setError] = useState<string | any>("");
const [airtableError, setAirtableError] = useState<any>({});
const validCsv =
csvData !== null && csvData?.columns.length > 0 && csvData?.rows.length > 0;
@@ -171,42 +176,44 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
});
}, 1000);
const [airtable, setAirtable] = useState<any>({
apiKey: "",
baseID: "",
tableID: "",
});
const [data, setData] = useState<any>(null);
const [validConnection, setValidConnection] = useState(false);
const handleAirtableConnection = () => {
if (!airtable.apiKey) {
setError({ apiKey: { message: "API Key is missing!" } });
if (!apiKey) {
setAirtableError({ apiKey: { message: "API Key is missing!" } });
return;
}
if (!airtable.baseID) {
setError({ baseID: { message: "Base ID is missing!" } });
if (!baseId) {
setAirtableError({ baseId: { message: "Base ID is missing!" } });
return;
}
if (!airtable.tableID) {
setError({ tableID: { message: "Table ID is missing!" } });
if (!tableId) {
setAirtableError({ tableId: { message: "Table ID is missing!" } });
return;
}
setLoading(true);
fetch(
`https://api.airtable.com/v0/${airtable.baseID}/${airtable.tableID}?maxRecords=1`,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${airtable.apiKey}`,
},
}
)
fetch(`https://api.airtable.com/v0/${baseId}/${tableId}?maxRecords=20`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
})
.then((response) => response.json())
.then((body) => setData(body.records))
.then((body) => {
const { error } = body;
if (error) {
throw new Error(error);
}
console.log(body);
setImportAirtable((prev) => ({ ...prev, airtableData: body }));
openTableModal("importAirtable");
})
.then(() => {
setValidConnection(true);
setLoading(false);
setError("");
setAirtableError(null);
})
.catch((error) => {
console.error(error);
setLoading(false);
});
};
@@ -401,15 +408,15 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
fullWidth
label="Airtable API Key"
placeholder="Insert your API key here"
value={airtable.apiKey}
onChange={(e) => {
setAirtable((airtable: any) => ({
...airtable,
value={apiKey}
onChange={(e) =>
setImportAirtable((prev) => ({
...prev,
apiKey: e.currentTarget.value,
}));
}}
helperText={error?.apiKey?.message}
error={!!error?.apiKey?.message}
}))
}
helperText={airtableError?.apiKey?.message}
error={!!airtableError?.apiKey?.message}
/>
<TextField
variant="filled"
@@ -417,15 +424,15 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
fullWidth
label="Airtable Base ID"
placeholder="Insert your Base ID here"
value={airtable.baseID}
value={baseId}
onChange={(e) => {
setAirtable((airtable: any) => ({
...airtable,
baseID: e.currentTarget.value,
setImportAirtable((prev) => ({
...prev,
baseId: e.currentTarget.value,
}));
}}
helperText={error?.baseID?.message}
error={!!error?.baseID?.message}
helperText={airtableError?.baseId?.message}
error={!!airtableError?.baseId?.message}
/>
<TextField
variant="filled"
@@ -433,20 +440,16 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
fullWidth
label="Airtable Table Name or ID"
placeholder="Insert your Table Name or ID here"
value={airtable.tableID}
value={tableId}
onChange={(e) => {
setAirtable((prev: any) => ({
...airtable,
tableID: e.currentTarget.value,
setImportAirtable((prev) => ({
...prev,
tableId: e.currentTarget.value,
}));
}}
helperText={error?.tableID?.message}
error={!!error?.tableID?.message}
helperText={airtableError?.tableId?.message}
error={!!airtableError?.tableId?.message}
/>
{data &&
data.map((record: any) => (
<div>Airtable record: {record.id}</div>
))}
</TabPanel>
</TabContext>
@@ -454,9 +457,7 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
variant="contained"
color="primary"
disabled={
importMethodRef.current === "airtable"
? loading && validConnection
: !validCsv
importMethodRef.current === "airtable" ? loading : !validCsv
}
sx={{
mt: -4,
@@ -467,25 +468,16 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
}}
onClick={() => {
if (importMethodRef.current === "airtable") {
if (!data) {
handleAirtableConnection();
} else {
console.log("hello");
openTableModal("importCsv");
}
handleAirtableConnection();
} else {
openTableModal("importCsv");
logEvent(analytics, `import_${importMethodRef.current}`, {
type: importTypeRef.current,
});
}
logEvent(analytics, `import_${importMethodRef.current}`, {
type: importTypeRef.current,
});
}}
>
{importMethodRef.current === "airtable"
? data
? "Continue"
: "Test Connection"
: "Continue"}
Continue
</Button>
</Popover>
</>