diff --git a/packages/browser-tests/cypress/fixtures/test.csv b/packages/browser-tests/cypress/fixtures/test.csv
new file mode 100644
index 000000000..3a0887ac6
--- /dev/null
+++ b/packages/browser-tests/cypress/fixtures/test.csv
@@ -0,0 +1,3 @@
+date,underlying,name,exchange,symbol,fut_expiration_date,bid,ask,open,high,low,close,volume,open_interest,strike_multiplier,option_price_multiplier
+2005-12-01T00:00:00.000000Z,AC,Denatured Fuel Ethanol Futures,CBOT,AC/05Z.CB,1134518400000,0.000000,0.000000,1.900000,1.900000,1.900000,1.900000,2,64,1.000000,1.000000
+2005-12-01T00:00:00.000000Z,AC,Denatured Fuel Ethanol Futures,CBOT,AC/06F.CB,1137110400000,0.000000,0.000000,0.000000,1.865000,1.865000,1.865000,0,83,1.000000,1.000000
diff --git a/packages/browser-tests/cypress/integration/console/import.spec.js b/packages/browser-tests/cypress/integration/console/import.spec.js
index 4283fde60..8e85d51e6 100644
--- a/packages/browser-tests/cypress/integration/console/import.spec.js
+++ b/packages/browser-tests/cypress/integration/console/import.spec.js
@@ -8,5 +8,10 @@ describe("questdb import", () => {
it("display import panel", () => {
cy.getByDataHook("import-panel-button").click();
cy.getByDataHook("import-dropbox").should("be.visible");
+ cy.getByDataHook("import-browse-from-disk").should("be.visible");
+
+ cy.get('input[type="file"]').selectFile("cypress/fixtures/test.csv", { force: true });
+ cy.getByDataHook("import-table-column-schema").should("be.visible");
+ cy.getByDataHook("import-table-column-owner").should("not.exist");
});
});
diff --git a/packages/browser-tests/cypress/integration/enterprise/import.spec.js b/packages/browser-tests/cypress/integration/enterprise/import.spec.js
new file mode 100644
index 000000000..278299bd6
--- /dev/null
+++ b/packages/browser-tests/cypress/integration/enterprise/import.spec.js
@@ -0,0 +1,18 @@
+///
+
+describe("CSV import in enterprise", () => {
+ before(() => {
+ cy.loadConsoleWithAuth();
+ });
+
+ it("display import panel", () => {
+ cy.getByDataHook("import-panel-button").click();
+ cy.getByDataHook("import-dropbox").should("be.visible");
+ cy.getByDataHook("import-browse-from-disk").should("be.visible");
+
+ cy.get('input[type="file"]').selectFile("cypress/fixtures/test.csv", { force: true });
+ cy.getByDataHook("import-table-column-schema").should("be.visible");
+ cy.getByDataHook("import-table-column-owner").should("be.visible");
+ cy.contains("option", "admin").should("exist");
+ });
+});
diff --git a/packages/browser-tests/cypress/integration/enterprise/oidc.spec.js b/packages/browser-tests/cypress/integration/enterprise/oidc.spec.js
index cd23d1579..c9e3c04af 100644
--- a/packages/browser-tests/cypress/integration/enterprise/oidc.spec.js
+++ b/packages/browser-tests/cypress/integration/enterprise/oidc.spec.js
@@ -96,4 +96,32 @@ describe("OIDC authentication", () => {
cy.getByDataHook("button-log-in").click()
cy.getEditor().should("be.visible");
});
+
+ it("display import panel", () => {
+ interceptAuthorizationCodeRequest(`${baseUrl}?code=abcdefgh`);
+ cy.getByDataHook("button-sso-login").click();
+ cy.wait("@authorizationCode");
+
+ interceptTokenRequest({
+ "access_token": "gslpJtzmmi6RwaPSx0dYGD4tEkom",
+ "refresh_token": "FUuAAqMp6LSTKmkUd5uZuodhiE4Kr6M7Eyv",
+ "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6I",
+ "token_type": "Bearer",
+ "expires_in": 300
+ });
+ cy.wait("@tokens");
+ cy.getEditor().should("be.visible");
+
+ cy.getByDataHook("import-panel-button").click();
+ cy.getByDataHook("import-dropbox").should("be.visible");
+ cy.getByDataHook("import-browse-from-disk").should("be.visible");
+
+ cy.get('input[type="file"]').selectFile("cypress/fixtures/test.csv", { force: true });
+ cy.getByDataHook("import-table-column-schema").should("be.visible");
+ cy.getByDataHook("import-table-column-owner").should("be.visible");
+ cy.contains("option", "user1").should("not.exist");
+ cy.contains("option", "group1").should("exist");
+
+ cy.logout();
+ });
});
diff --git a/packages/web-console/serve-dist.js b/packages/web-console/serve-dist.js
index 456e7301a..5cf46c7dc 100644
--- a/packages/web-console/serve-dist.js
+++ b/packages/web-console/serve-dist.js
@@ -10,7 +10,7 @@ const server = http.createServer((req, res) => {
res.statusCode = 403;
res.end();
return;
- }
+ }
const { method } = req
const baseUrl = "http://" + req.headers.host + contextPath;
const reqUrl = new url.URL(req.url, baseUrl);
@@ -20,7 +20,8 @@ const server = http.createServer((req, res) => {
if (
reqPathName.startsWith("/exec") ||
reqPathName.startsWith("/settings") ||
- reqPathName.startsWith("/warnings")
+ reqPathName.startsWith("/warnings") ||
+ reqPathName.startsWith("/chk")
) {
// proxy /exec requests to localhost:9000
const options = {
diff --git a/packages/web-console/src/scenes/Import/ImportCSVFiles/files-to-upload.tsx b/packages/web-console/src/scenes/Import/ImportCSVFiles/files-to-upload.tsx
index 759f81fbc..120cb1654 100644
--- a/packages/web-console/src/scenes/Import/ImportCSVFiles/files-to-upload.tsx
+++ b/packages/web-console/src/scenes/Import/ImportCSVFiles/files-to-upload.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useRef } from "react"
import styled from "styled-components"
import { Heading, Table, Select, Button } from "@questdb/react-components"
-import type { Props as TableProps } from "@questdb/react-components/dist/components/Table"
+import { Column, Props as TableProps } from "@questdb/react-components/dist/components/Table"
import { PopperHover, Text, Tooltip } from "../../../components"
import { Box } from "../../../components/Box"
import { bytesWithSuffix } from "../../../utils/bytesWithSuffix"
@@ -78,6 +78,7 @@ type Props = {
onFilesDropped: (files: File[]) => void
onViewData: (result: UploadResult) => void
dialogOpen: boolean
+ ownedByList: string[]
}
export const FilesToUpload = ({
@@ -89,6 +90,7 @@ export const FilesToUpload = ({
onFilesDropped,
onViewData,
dialogOpen,
+ ownedByList,
}: Props) => {
const uploadInputRef = useRef(null)
const [renameDialogOpen, setRenameDialogOpen] = React.useState<
@@ -105,6 +107,262 @@ export const FilesToUpload = ({
)
}, [renameDialogOpen, schemaDialogOpen])
+ const columns: Column[] = []
+ columns.push(
+ {
+ header: "File",
+ align: "flex-start",
+ ...(files.length > 0 && { width: "400px" }),
+ render: ({ data }) => {
+ const file = (
+
+
+ {shortenText(data.fileObject.name, 20)}
+
+
+
+ {bytesWithSuffix(data.fileObject.size)}
+
+
+ )
+ return (
+
+
+
+ {data.fileObject.name.length > 20 && (
+
+ {data.fileObject.name}
+
+ )}
+ {data.fileObject.name.length <= 20 && file}
+
+
+ {!data.isUploading &&
+ data.uploadResult !== undefined && (
+
+
+ }
+ onClick={() =>
+ onViewData(
+ data.uploadResult as UploadResult,
+ )
+ }
+ >
+ Result
+
+
+ )}
+
+ {(data.uploadResult &&
+ data.uploadResult.rowsRejected > 0) ||
+ (data.error && (
+
+ {data.uploadResult &&
+ data.uploadResult.rowsRejected > 0 && (
+
+ {data.uploadResult.rowsRejected.toLocaleString()}{" "}
+ row
+ {data.uploadResult.rowsRejected > 1
+ ? "s"
+ : ""}{" "}
+ rejected
+
+ )}
+ {data.error && (
+
+ {data.error}
+
+ )}
+
+ ))}
+
+
+ )
+ },
+ },
+ {
+ header: "Table name",
+ align: "flex-end",
+ width: "180px",
+ render: ({ data }) => {
+ return (
+ setRenameDialogOpen(f?.id)}
+ onNameChange={(name) => {
+ onFilePropertyChange(data.id, {
+ table_name: name,
+ })
+ }}
+ file={data}
+ />
+ )
+ },
+ },
+ )
+
+ if (ownedByList && ownedByList.length > 0) {
+ columns.push(
+ {
+ header: (
+
+ Table owner
+
+
+ }
+ >
+
+ Required for external (non-database) users.
+
+
+ ),
+ align: "center",
+ width: "150px",
+ render: ({ data }) => (
+