Skip to content
Snippets Groups Projects
Unverified Commit 4cd818b6 authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

Show empty schema in upload settings (#30629)

* use syncable_schemas endpoint for upload settings

* add syncable_schemas endpoint to mock

* add e2e test for empty schema
parent bcc4bee1
No related branches found
No related tags found
No related merge requests found
import { restore, queryWritableDB, resyncDatabase } from "e2e/support/helpers";
import {
restore,
queryWritableDB,
resyncDatabase,
popover,
} from "e2e/support/helpers";
import { WRITABLE_DB_ID } from "e2e/support/cypress_data";
......@@ -18,6 +23,58 @@ const testFiles = [
];
describe("CSV Uploading", { tags: ["@external", "@actions"] }, () => {
it("Can upload a CSV file to an empty postgres schema", () => {
const testFile = testFiles[0];
const EMPTY_SCHEMA_NAME = "empty_uploads";
cy.intercept("PUT", "/api/setting").as("saveSettings");
restore("postgres-writable");
cy.signInAsAdmin();
queryWritableDB("DROP SCHEMA IF EXISTS empty_uploads CASCADE;", "postgres");
queryWritableDB("CREATE SCHEMA IF NOT EXISTS empty_uploads;", "postgres");
cy.request("POST", "/api/collection", {
name: `Uploads Collection`,
color: "#000000", // shockingly, this unused field is required
parent_id: null,
}).then(({ body: { id: collectionId } }) => {
cy.wrap(collectionId).as("collectionId");
});
resyncDatabase({ dbId: WRITABLE_DB_ID });
cy.visit("/admin/settings/uploads");
cy.findByLabelText("Upload Settings Form")
.findByText("Select a database")
.click();
popover().findByText("Writable Postgres12").click();
cy.findByLabelText("Upload Settings Form")
.findByText("Select a schema")
.click();
popover().findByText(EMPTY_SCHEMA_NAME).click();
cy.findByLabelText("Upload Settings Form").button("Enable uploads").click();
cy.wait("@saveSettings");
uploadFile(testFile, "postgres");
const tableQuery = `SELECT * FROM information_schema.tables WHERE table_name LIKE '%${testFile.tableName}_%' ORDER BY table_name DESC LIMIT 1;`;
queryWritableDB(tableQuery, "postgres").then(result => {
expect(result.rows.length).to.equal(1);
const tableName = result.rows[0].table_name;
queryWritableDB(
`SELECT count(*) FROM ${EMPTY_SCHEMA_NAME}.${tableName};`,
"postgres",
).then(result => {
expect(Number(result.rows[0].count)).to.equal(testFile.rowCount);
});
});
});
["postgres"].forEach(dialect => {
describe(`CSV Uploading (${dialect})`, () => {
beforeEach(() => {
......@@ -37,42 +94,7 @@ describe("CSV Uploading", { tags: ["@external", "@actions"] }, () => {
testFiles.forEach(testFile => {
it(`Can upload ${testFile.fileName} to a collection`, () => {
cy.get("@collectionId").then(collectionId =>
cy.visit(`/collection/${collectionId}`),
);
cy.fixture(`${FIXTURE_PATH}/${testFile.fileName}`).then(file => {
cy.get("#upload-csv").selectFile(
{
contents: Cypress.Buffer.from(file),
fileName: testFile.fileName,
mimeType: "text/csv",
},
{ force: true },
);
});
cy.findByRole("status").within(() => {
cy.findByText(/Uploading/i);
cy.findByText(testFile.fileName);
cy.findByText("Data added to Uploads Collection", {
timeout: 10 * 1000,
});
});
cy.get("main").within(() => cy.findByText("Uploads Collection"));
cy.findByTestId("collection-table").within(() => {
cy.findByText(testFile.tableName); // TODO: we should humanize model names
});
cy.findByRole("status").within(() => {
cy.findByText("Start exploring").click();
});
cy.url().should("include", `/model/4`);
cy.findByTestId("TableInteractive-root");
uploadFile(testFile, dialect);
const tableQuery = `SELECT * FROM information_schema.tables WHERE table_name LIKE '%${testFile.tableName}_%' ORDER BY table_name DESC LIMIT 1;`;
......@@ -93,6 +115,45 @@ describe("CSV Uploading", { tags: ["@external", "@actions"] }, () => {
});
});
function uploadFile(testFile, dialect) {
cy.get("@collectionId").then(collectionId =>
cy.visit(`/collection/${collectionId}`),
);
cy.fixture(`${FIXTURE_PATH}/${testFile.fileName}`).then(file => {
cy.get("#upload-csv").selectFile(
{
contents: Cypress.Buffer.from(file),
fileName: testFile.fileName,
mimeType: "text/csv",
},
{ force: true },
);
});
cy.findByRole("status").within(() => {
cy.findByText(/Uploading/i);
cy.findByText(testFile.fileName);
cy.findByText("Data added to Uploads Collection", {
timeout: 10 * 1000,
});
});
cy.get("main").within(() => cy.findByText("Uploads Collection"));
cy.findByTestId("collection-table").within(() => {
cy.findByText(testFile.tableName); // TODO: we should humanize model names
});
cy.findByRole("status").within(() => {
cy.findByText("Start exploring").click();
});
cy.url().should("include", `/model/4`);
cy.findByTestId("TableInteractive-root");
}
function enableUploads(dialect) {
const settings = {
"uploads-enabled": true,
......
......@@ -190,7 +190,7 @@ export function UploadSettingsView({
/>
</div>
{!!showSchema && (
<Schemas.ListLoader query={{ dbId }}>
<Schemas.ListLoader query={{ dbId, getAll: true }}>
{({ list: schemaList }: { list: Schema[] }) => (
<div>
<SectionTitle>{t`Schema`}</SectionTitle>
......
import { updateIn } from "icepick";
import { createEntity } from "metabase/lib/entities";
import { GET } from "metabase/lib/api";
import { MetabaseApi } from "metabase/services";
import { SchemaSchema } from "metabase/schema";
import Questions from "metabase/entities/questions";
......@@ -16,19 +15,18 @@ import {
// This is a weird entity because we don't have actual schema objects
const listDatabaseSchemas = GET("/api/database/:dbId/schemas");
const getSchemaTables = GET("/api/database/:dbId/schema/:schemaName");
const getVirtualDatasetTables = GET("/api/database/:dbId/datasets/:schemaName");
export default createEntity({
name: "schemas",
schema: SchemaSchema,
api: {
list: async ({ dbId, ...args }) => {
list: async ({ dbId, getAll = false, ...args }) => {
if (!dbId) {
throw new Error("Schemas can only be listed for a particular dbId");
}
const schemaNames = await listDatabaseSchemas({ dbId, ...args });
const schemaNames = await (getAll
? MetabaseApi.db_syncable_schemas({ dbId, ...args }) // includes empty schema
: MetabaseApi.db_schemas({ dbId, ...args }));
return schemaNames.map(schemaName => ({
// NOTE: needs unique IDs for entities to work correctly
id: generateSchemaId(dbId, schemaName),
......@@ -42,8 +40,12 @@ export default createEntity({
throw new Error("Schemas ID is of the form dbId:schemaName");
}
const tables = opts?.isDatasets
? await getVirtualDatasetTables({ dbId, schemaName, ...args })
: await getSchemaTables({ dbId, schemaName, ...args });
? await MetabaseApi.db_virtual_dataset_tables({
dbId,
schemaName,
...args,
})
: await MetabaseApi.db_schema_tables({ dbId, schemaName, ...args });
return {
id,
name: schemaName,
......
......@@ -311,7 +311,9 @@ export const MetabaseApi = {
db_delete: DELETE("/api/database/:dbId"),
db_metadata: GET("/api/database/:dbId/metadata"),
db_schemas: GET("/api/database/:dbId/schemas"),
db_syncable_schemas: GET("/api/database/:dbId/syncable_schemas"),
db_schema_tables: GET("/api/database/:dbId/schema/:schemaName"),
db_virtual_dataset_tables: GET("/api/database/:dbId/datasets/:schemaName"),
//db_tables: GET("/api/database/:dbId/tables"),
db_fields: GET("/api/database/:dbId/fields"),
db_idfields: GET("/api/database/:dbId/idfields"),
......
......@@ -34,6 +34,7 @@ export const setupSchemaEndpoints = (db: Database) => {
const schemas = _.groupBy(db.tables ?? [], table => table.schema);
const schemaNames = Object.keys(schemas);
fetchMock.get(`path:/api/database/${db.id}/schemas`, schemaNames);
fetchMock.get(`path:/api/database/${db.id}/syncable_schemas`, schemaNames);
schemaNames.forEach(schema => {
fetchMock.get(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment