Skip to content
Snippets Groups Projects
Unverified Commit b8f3da62 authored by Paul Rosenzweig's avatar Paul Rosenzweig Committed by GitHub
Browse files

Add schemas to data reference sidebar (#11030)

parents 93037b36 4cd255ff
Branches
Tags
No related merge requests found
......@@ -5,6 +5,7 @@ import { t } from "ttag";
import MainPane from "./MainPane";
import DatabasePane from "./DatabasePane";
import SchemaPane from "./SchemaPane";
import TablePane from "./TablePane";
import FieldPane from "./FieldPane";
import SegmentPane from "./SegmentPane";
......@@ -13,8 +14,9 @@ import MetricPane from "./MetricPane";
import SidebarContent from "metabase/query_builder/components/SidebarContent";
const PANES = {
database: DatabasePane,
table: TablePane,
database: DatabasePane, // displays either schemas or tables in a database
schema: SchemaPane, // displays tables in a schema
table: TablePane, // displays fields in a table
field: FieldPane,
segment: SegmentPane,
metric: MetricPane,
......
/* eslint "react/prop-types": "warn" */
import React from "react";
import PropTypes from "prop-types";
import { isQueryable } from "metabase/lib/table";
import Icon from "metabase/components/Icon";
import DatabaseSchemasPane from "./DatabaseSchemasPane";
import DatabaseTablesPane from "./DatabaseTablesPane";
const DatabasePane = ({ database, show, ...props }) => (
<div>
<div className="ml1 my2 flex align-center justify-between border-bottom pb1">
<div className="flex align-center">
<Icon name="database" className="text-medium pr1" size={14} />
<h3 className="text-wrap">{database.name}</h3>
</div>
<div className="flex align-center">
<Icon name="table2" className="text-light pr1" size={12} />
<span className="text-medium">{database.tables.length}</span>
</div>
</div>
<ul>
{database.tables.filter(isQueryable).map((table, index) => (
<li key={table.id}>
<a
className="flex-full flex p1 text-bold text-brand text-wrap no-decoration bg-medium-hover"
onClick={() => show("table", table)}
>
{table.name}
</a>
</li>
))}
</ul>
</div>
);
const DatabasePane = props => {
const schemas = new Set(props.database.tables.map(t => t.schema));
const Component = schemas.size > 1 ? DatabaseSchemasPane : DatabaseTablesPane;
return <Component {...props} />;
};
DatabasePane.propTypes = {
show: PropTypes.func.isRequired,
......
/* eslint "react/prop-types": "warn" */
import React from "react";
import PropTypes from "prop-types";
import Icon from "metabase/components/Icon";
const DatabaseSchemasPane = ({ database, show, ...props }) => {
const schemaNames = Array.from(
new Set(database.tables.map(t => t.schema)),
).sort((a, b) => a.localeCompare(b));
return (
<div>
<div className="ml1 my2 flex align-center justify-between border-bottom pb1">
<div className="flex align-center">
<Icon name="database" className="text-medium pr1" size={14} />
<h3 className="text-wrap">{database.name}</h3>
</div>
<div className="flex align-center">
<Icon name="folder" className="text-light pr1" size={12} />
<span className="text-medium">{schemaNames.length}</span>
</div>
</div>
<ul>
{schemaNames.map(schema => (
<li key={schema}>
<a
className="flex-full flex p1 text-bold text-brand text-wrap no-decoration bg-medium-hover"
onClick={() => show("schema", { database, schema })}
>
{schema}
</a>
</li>
))}
</ul>
</div>
);
};
DatabaseSchemasPane.propTypes = {
show: PropTypes.func.isRequired,
database: PropTypes.object.isRequired,
};
export default DatabaseSchemasPane;
/* eslint "react/prop-types": "warn" */
import React from "react";
import PropTypes from "prop-types";
import { isQueryable } from "metabase/lib/table";
import Icon from "metabase/components/Icon";
const DatabaseTablesPane = ({ database, show, ...props }) => {
const tables = database.tables
.filter(isQueryable)
.sort((a, b) => a.name.localeCompare(b.name));
return (
<div>
<div className="ml1 my2 flex align-center justify-between border-bottom pb1">
<div className="flex align-center">
<Icon name="database" className="text-medium pr1" size={14} />
<h3 className="text-wrap">{database.name}</h3>
</div>
<div className="flex align-center">
<Icon name="table2" className="text-light pr1" size={12} />
<span className="text-medium">{tables.length}</span>
</div>
</div>
<ul>
{tables.map(table => (
<li key={table.id}>
<a
className="flex-full flex p1 text-bold text-brand text-wrap no-decoration bg-medium-hover"
onClick={() => show("table", table)}
>
{table.name}
</a>
</li>
))}
</ul>
</div>
);
};
DatabaseTablesPane.propTypes = {
show: PropTypes.func.isRequired,
database: PropTypes.object.isRequired,
};
export default DatabaseTablesPane;
......@@ -12,6 +12,7 @@ const MainPane = ({ databases, show }) => (
<ul>
{databases &&
databases
.filter(db => !db.is_saved_questions)
.filter(db => db.tables && db.tables.length > 0)
.map(database => (
<li className="mb2" key={database.id}>
......
/* eslint "react/prop-types": "warn" */
import React from "react";
import PropTypes from "prop-types";
import { isQueryable } from "metabase/lib/table";
import Icon from "metabase/components/Icon";
const SchemaPane = ({ schema: { database, schema }, show, ...props }) => {
const tables = database.tables
.filter(t => t.schema === schema)
.filter(isQueryable)
.sort((a, b) => a.name.localeCompare(b.name));
return (
<div>
<div className="ml1 my2 flex align-center justify-between border-bottom pb1">
<div className="flex align-center">
<Icon name="folder" className="text-medium pr1" size={14} />
<h3 className="text-wrap">{schema}</h3>
</div>
<div className="flex align-center">
<Icon name="table2" className="text-light pr1" size={12} />
<span className="text-medium">{tables.length}</span>
</div>
</div>
<ul>
{tables.map(table => (
<li key={table.id}>
<a
className="flex-full flex p1 text-bold text-brand text-wrap no-decoration bg-medium-hover"
onClick={() => show("table", table)}
>
{table.name}
</a>
</li>
))}
</ul>
</div>
);
};
SchemaPane.propTypes = {
show: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
};
export default SchemaPane;
import React from "react";
import { render, cleanup, fireEvent } from "@testing-library/react";
import DataReference from "metabase/query_builder/components/dataref/DataReference";
const databases = [
{
name: "db1",
id: 1,
tables: [
{ name: "t1", id: 1, schema: "s1" },
{ name: "t2", id: 2, schema: "s2" },
{ name: "t3", id: 3, schema: "s2", visibility_type: "hidden" },
],
},
{ name: "db2", id: 2, tables: [{ name: "t4", id: 4 }] },
{
name: "saved questions",
is_saved_questions: true,
tables: [{ name: "t5", id: 5 }],
},
{ name: "empty", tables: [] },
];
describe("DatabasePane", () => {
afterEach(cleanup);
it("should show databases except empty databases and saved questions db", () => {
const { getByText, queryByText } = render(
<DataReference databases={databases} />,
);
getByText("db1");
getByText("db2");
expect(queryByText("saved questions")).toBe(null);
expect(queryByText("empty")).toBe(null);
});
it("should show tables in db without multple schemas", () => {
const { getByText } = render(<DataReference databases={databases} />);
fireEvent.click(getByText("db2"));
getByText("t4");
});
it("should show schemas in db with multple schemas", () => {
const { getByText } = render(<DataReference databases={databases} />);
fireEvent.click(getByText("db1"));
getByText("s1");
getByText("s2");
});
it("should only show visible tables", () => {
const { getByText, queryByText } = render(
<DataReference databases={databases} />,
);
fireEvent.click(getByText("db1"));
fireEvent.click(getByText("s2"));
getByText("1"); // table count with filtered tables
getByText("t2");
expect(queryByText("t3")).toBe(null);
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment