diff --git a/frontend/src/metabase-lib/Question.ts b/frontend/src/metabase-lib/Question.ts index d7f3c790b21f36db917003469e7a157cddc233bf..c02244fdc5a0a6ef74feb399a81ad7d672458f87 100644 --- a/frontend/src/metabase-lib/Question.ts +++ b/frontend/src/metabase-lib/Question.ts @@ -5,7 +5,6 @@ import { assoc, assocIn, chain, dissoc, getIn } from "icepick"; /* eslint-disable import/order */ // NOTE: the order of these matters due to circular dependency issues import slugg from "slugg"; -import * as MLv2 from "cljs/metabase.lib.js"; import StructuredQuery, { STRUCTURED_QUERY_TEMPLATE, } from "metabase-lib/queries/StructuredQuery"; @@ -85,7 +84,8 @@ import { } from "metabase-lib/Alert"; import { getBaseDimensionReference } from "metabase-lib/references"; -import { Query } from "./types"; +import type { Query } from "./types"; +import * as ML from "./v2"; export type QuestionCreatorOpts = { databaseId?: DatabaseId; @@ -1341,7 +1341,7 @@ class QuestionInner { // cache the metadata provider we create for our metadata. if (metadata === this._metadata) { if (!this.__mlv2MetadataProvider) { - this.__mlv2MetadataProvider = MLv2.metadataProvider( + this.__mlv2MetadataProvider = ML.metadataProvider( this.databaseId(), metadata, ); @@ -1356,7 +1356,7 @@ class QuestionInner { if (!this.__mlv2Query) { this.__mlv2QueryMetadata = metadata; - this.__mlv2Query = MLv2.query( + this.__mlv2Query = ML.fromLegacyQuery( this.databaseId(), metadata, this.datasetQuery(), @@ -1368,7 +1368,7 @@ class QuestionInner { generateQueryDescription() { const query = this._getMLv2Query(); - return MLv2.suggestedName(query); + return ML.suggestedName(query); } getUrlWithParameters(parameters, parameterValues, { objectId, clean } = {}) { diff --git a/frontend/src/metabase-lib/index.ts b/frontend/src/metabase-lib/index.ts index 323435ea77eb11c2e8c54a7d2d75f5092dd5b8d2..765c1b00239ba1a9bf2a7cbd6bb689edf8d8d050 100644 --- a/frontend/src/metabase-lib/index.ts +++ b/frontend/src/metabase-lib/index.ts @@ -1,5 +1 @@ -// Note: only metabase-lib v2 exports should be added here - -export * from "./limit"; -export * from "./query"; -export * from "./types"; +export * from "./v2"; diff --git a/frontend/src/metabase-lib/metadata.ts b/frontend/src/metabase-lib/metadata.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ba300ba3f8ee986fb3761e313e8b008a8154f56 --- /dev/null +++ b/frontend/src/metabase-lib/metadata.ts @@ -0,0 +1,16 @@ +import * as ML from "cljs/metabase.lib.js"; +import * as ML_MetadataCalculation from "cljs/metabase.lib.metadata.calculation"; +import type { DatabaseId } from "metabase-types/api"; +import type Metadata from "./metadata/Metadata"; +import type { Clause, MetadataProvider, Query } from "./types"; + +export function metadataProvider( + databaseId: DatabaseId, + metadata: Metadata, +): MetadataProvider { + return ML.metadataProvider(databaseId, metadata); +} + +export function displayName(query: Query, clause: Clause): string { + return ML_MetadataCalculation.display_name(query, clause); +} diff --git a/frontend/src/metabase-lib/order.unit.spec.ts b/frontend/src/metabase-lib/order.unit.spec.ts deleted file mode 100644 index 601afed8c752592f759e765b83b1423b5bf67700..0000000000000000000000000000000000000000 --- a/frontend/src/metabase-lib/order.unit.spec.ts +++ /dev/null @@ -1,117 +0,0 @@ -import _ from "underscore"; - -import * as ML from "cljs/metabase.lib.js"; -import * as ML_MetadataCalculation from "cljs/metabase.lib.metadata.calculation"; - -import * as SampleDatabase from "__support__/sample_database_fixture"; - -const createMetadataProvider = () => - ML.metadataProvider( - SampleDatabase.SAMPLE_DATABASE.id, - SampleDatabase.metadata, - ); - -const createQuery = () => { - const query = ML.query( - SampleDatabase.SAMPLE_DATABASE.id, - createMetadataProvider(), - { - database: SampleDatabase.SAMPLE_DATABASE.id, - type: "query", - query: { - "source-table": SampleDatabase.ORDERS.id, - }, - }, - ); - - it("can create an MLv2 query", () => { - expect(query).toBeTruthy(); - expect(ML.suggestedName(query)).toBe("Orders"); - }); - - return query; -}; - -describe("orderableColumns", () => { - const query = createQuery(); - const orderableColumns = ML.orderable_columns(query); - - it("returns an array", () => { - expect(orderableColumns).toBeInstanceOf(Array); - }); - - describe("returns metadata for columns in the source table", () => { - it("contains ORDERS.ID", () => { - const ordersID = _.find( - orderableColumns, - ({ id }) => id === SampleDatabase.ORDERS.ID.id, - ); - - expect(ordersID).toEqual( - expect.objectContaining({ - table_id: SampleDatabase.ORDERS.id, - name: "ID", - id: SampleDatabase.ORDERS.ID.id, - display_name: "ID", - base_type: "type/BigInteger", - }), - ); - }); - }); - - describe("returns metadata for columns in implicitly joinable tables", () => { - it("contains PRODUCTS.TITLE", () => { - const productsTitle = _.find( - orderableColumns, - ({ id }) => id === SampleDatabase.PRODUCTS.TITLE.id, - ); - - expect(productsTitle).toEqual( - expect.objectContaining({ - table_id: SampleDatabase.PRODUCTS.id, - name: "TITLE", - id: SampleDatabase.PRODUCTS.TITLE.id, - display_name: "Title", - base_type: "type/Text", - }), - ); - }); - }); -}); - -describe("add order by", () => { - const query = createQuery(); - - const orderBys = ML.order_bys(query); - - it("should not have order bys yet", () => { - expect(orderBys).toBeNull(); - }); - - const orderableColumns = ML.orderable_columns(query); - const productsTitle = _.find( - orderableColumns, - ({ id }) => id === SampleDatabase.PRODUCTS.TITLE.id, - ); - - it("should include PRODUCTS.TITLE in orderableColumns", () => { - expect(productsTitle).toBeTruthy(); - }); - - it("should update the query", () => { - const updatedQuery = ML.order_by(query, productsTitle); - // This name isn't GREAT but it's ok for now, we can update this if we improve MLv2. - expect(ML.suggestedName(updatedQuery)).toBe( - "Orders, Sorted by Title ascending", - ); - - const updatedOrderBys = ML.order_bys(updatedQuery); - - expect(updatedOrderBys).toHaveLength(1); - const orderBy = updatedOrderBys[0]; - - expect(ML_MetadataCalculation.display_name(query, orderBy)).toBe( - "Title ascending", - ); - }); -}); diff --git a/frontend/src/metabase-lib/order_by.ts b/frontend/src/metabase-lib/order_by.ts new file mode 100644 index 0000000000000000000000000000000000000000..cad71031d30aba4986acdde503527e2d12e3d2e2 --- /dev/null +++ b/frontend/src/metabase-lib/order_by.ts @@ -0,0 +1,21 @@ +import * as ML from "cljs/metabase.lib.js"; +import type { Field } from "metabase-types/api"; +import type { OrderByClause, Query } from "./types"; + +export function orderableColumns(query: Query): Field[] { + return ML.orderable_columns(query); +} + +export function orderBys(query: Query): OrderByClause[] { + const result = ML.order_bys(query); + return result === null ? [] : result; +} + +declare function OrderByFn(query: Query, field: Field): Query; +declare function OrderByFn( + query: Query, + stageIndex: number, + field: Field, +): Query; + +export const orderBy: typeof OrderByFn = ML.order_by; diff --git a/frontend/src/metabase-lib/order_by.unit.spec.ts b/frontend/src/metabase-lib/order_by.unit.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7dc1a0905e023a570ef127546b5364cd7e9d38c2 --- /dev/null +++ b/frontend/src/metabase-lib/order_by.unit.spec.ts @@ -0,0 +1,62 @@ +import type { Field } from "metabase-types/api"; +import { createQuery, SAMPLE_DATABASE } from "./test-helpers"; +import * as ML from "./v2"; + +describe("order by", () => { + describe("orderableColumns", () => { + const query = createQuery(); + const columns = ML.orderableColumns(query); + + it("returns metadata for columns in the source table", () => { + const ordersID = columns.find( + ({ id }) => id === SAMPLE_DATABASE.ORDERS.ID.id, + ); + + expect(ordersID).toEqual( + expect.objectContaining({ + table_id: SAMPLE_DATABASE.ORDERS.id, + name: "ID", + id: SAMPLE_DATABASE.ORDERS.ID.id, + display_name: "ID", + base_type: "type/BigInteger", + }), + ); + }); + + it("returns metadata for columns in implicitly joinable tables", () => { + const productsTitle = columns.find( + ({ id }) => id === SAMPLE_DATABASE.PRODUCTS.TITLE.id, + ); + + expect(productsTitle).toEqual( + expect.objectContaining({ + table_id: SAMPLE_DATABASE.PRODUCTS.id, + name: "TITLE", + id: SAMPLE_DATABASE.PRODUCTS.TITLE.id, + display_name: "Title", + base_type: "type/Text", + }), + ); + }); + }); + + describe("add order by", () => { + const query = createQuery(); + + it("should handle no order by clauses", () => { + expect(ML.orderBys(query)).toHaveLength(0); + }); + + it("should update the query", () => { + const columns = ML.orderableColumns(query); + const productTitle = columns.find( + column => column.id === SAMPLE_DATABASE.PRODUCTS.TITLE.id, + ); + const nextQuery = ML.orderBy(query, productTitle as Field); + const orderBys = ML.orderBys(nextQuery); + + expect(orderBys).toHaveLength(1); + expect(ML.displayName(nextQuery, orderBys[0])).toBe("Title ascending"); + }); + }); +}); diff --git a/frontend/src/metabase-lib/queries/StructuredQuery.ts b/frontend/src/metabase-lib/queries/StructuredQuery.ts index bf4e1d3941be4500c7c856d28dc3ce472cbbe317..320e05524e5751c3d3aef3c06aa0eb08a0c84fb1 100644 --- a/frontend/src/metabase-lib/queries/StructuredQuery.ts +++ b/frontend/src/metabase-lib/queries/StructuredQuery.ts @@ -49,7 +49,7 @@ import Dimension, { } from "metabase-lib/Dimension"; import DimensionOptions from "metabase-lib/DimensionOptions"; -import * as MLv2 from ".."; +import * as ML from "../v2"; import type { Limit, Query } from "../types"; import Segment from "../metadata/Segment"; @@ -134,7 +134,7 @@ class StructuredQueryInner extends AtomicQuery { } private updateWithMLv2(nextQuery: Query) { - const nextMLv1Query = MLv2.toLegacyQuery(nextQuery); + const nextMLv1Query = ML.toLegacyQuery(nextQuery); return this.setDatasetQuery(nextMLv1Query); } @@ -562,7 +562,7 @@ class StructuredQueryInner extends AtomicQuery { hasLimit() { const query = this.getMLv2Query(); - return MLv2.hasLimit(query); + return ML.hasLimit(query); } hasFields() { @@ -1105,12 +1105,12 @@ class StructuredQueryInner extends AtomicQuery { // LIMIT limit(): Limit { const query = this.getMLv2Query(); - return MLv2.currentLimit(query); + return ML.currentLimit(query); } updateLimit(limit: Limit) { const query = this.getMLv2Query(); - const nextQuery = MLv2.limit(query, limit); + const nextQuery = ML.limit(query, limit); return this.updateWithMLv2(nextQuery); } diff --git a/frontend/src/metabase-lib/query.ts b/frontend/src/metabase-lib/query.ts index f50bfcf8835dd18348897c8234592fb79cb541c3..d8bb3b3a2690df1992e70b6e9a36f299bdc88232 100644 --- a/frontend/src/metabase-lib/query.ts +++ b/frontend/src/metabase-lib/query.ts @@ -1,7 +1,19 @@ import * as ML from "cljs/metabase.lib.js"; -import type { DatasetQuery } from "metabase-types/api"; -import type { Query } from "./types"; +import type { DatabaseId, DatasetQuery } from "metabase-types/api"; +import type { MetadataProvider, Query } from "./types"; + +export function fromLegacyQuery( + databaseId: DatabaseId, + metadata: MetadataProvider, + datasetQuery: DatasetQuery, +): Query { + return ML.query(databaseId, metadata, datasetQuery); +} export function toLegacyQuery(query: Query): DatasetQuery { return ML.legacy_query(query); } + +export function suggestedName(query: Query): string { + return ML.suggestedName(query); +} diff --git a/frontend/src/metabase-lib/query.unit.spec.ts b/frontend/src/metabase-lib/query.unit.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d4ba1eb1769b72ff061c3ba7f47c5452dbc9f27 --- /dev/null +++ b/frontend/src/metabase-lib/query.unit.spec.ts @@ -0,0 +1,14 @@ +import { createQuery, DEFAULT_QUERY } from "./test-helpers"; +import * as ML from "./v2"; + +describe("query", () => { + it("should create a query", () => { + const query = createQuery(); + expect(ML.toLegacyQuery(query)).toEqual(DEFAULT_QUERY); + }); + + it("should suggest a name", () => { + const query = createQuery(); + expect(ML.suggestedName(query)).toBe("Orders"); + }); +}); diff --git a/frontend/src/metabase-lib/test-helpers.ts b/frontend/src/metabase-lib/test-helpers.ts new file mode 100644 index 0000000000000000000000000000000000000000..a257b62e9971cdfbe305a6c995e52a6b67ff13d9 --- /dev/null +++ b/frontend/src/metabase-lib/test-helpers.ts @@ -0,0 +1,44 @@ +/* istanbul ignore file */ + +import { + SAMPLE_DATABASE, + metadata as SAMPLE_METADATA, +} from "__support__/sample_database_fixture"; +import type { DatabaseId, DatasetQuery } from "metabase-types/api"; +import type Metadata from "./metadata/Metadata"; +import * as ML from "./v2"; + +export { SAMPLE_DATABASE, SAMPLE_METADATA }; + +type MetadataProviderOpts = { + databaseId?: DatabaseId; + metadata?: Metadata; +}; + +function createMetadataProvider({ + databaseId = SAMPLE_DATABASE.id, + metadata = SAMPLE_METADATA, +}: MetadataProviderOpts = {}) { + return ML.metadataProvider(databaseId, metadata); +} + +export const DEFAULT_QUERY: DatasetQuery = { + database: SAMPLE_DATABASE.id, + type: "query", + query: { + "source-table": SAMPLE_DATABASE.ORDERS.id, + }, +}; + +type QueryOpts = MetadataProviderOpts & { + query?: DatasetQuery; +}; + +export function createQuery({ + databaseId = SAMPLE_DATABASE.id, + metadata = SAMPLE_METADATA, + query = DEFAULT_QUERY, +}: QueryOpts = {}) { + const metadataProvider = createMetadataProvider({ databaseId, metadata }); + return ML.fromLegacyQuery(databaseId, metadataProvider, query); +} diff --git a/frontend/src/metabase-lib/types.ts b/frontend/src/metabase-lib/types.ts index 32a483d5f306c8493b5d2cee0221554ee038b23c..8a71a53ddb47909c6a0b4f42f06fe5b5d2f236aa 100644 --- a/frontend/src/metabase-lib/types.ts +++ b/frontend/src/metabase-lib/types.ts @@ -5,4 +5,12 @@ declare const Query: unique symbol; export type Query = unknown & { _opaque: typeof Query }; +declare const MetadataProvider: unique symbol; +export type MetadataProvider = unknown & { _opaque: typeof MetadataProvider }; + export type Limit = number | null; + +declare const OrderByClause: unique symbol; +export type OrderByClause = unknown & { _opaque: typeof OrderByClause }; + +export type Clause = OrderByClause; diff --git a/frontend/src/metabase-lib/v2.ts b/frontend/src/metabase-lib/v2.ts new file mode 100644 index 0000000000000000000000000000000000000000..103f3ee7ac1cd8a97f49a07519c2cd5acf488034 --- /dev/null +++ b/frontend/src/metabase-lib/v2.ts @@ -0,0 +1,7 @@ +// Note: only metabase-lib v2 exports should be added here + +export * from "./metadata"; +export * from "./limit"; +export * from "./order_by"; +export * from "./query"; +export * from "./types";