From 1a9b648d335e6f9b279c8cd962b3c8f61c75c3b8 Mon Sep 17 00:00:00 2001 From: Tom Robinson <tlrobinson@gmail.com> Date: Fri, 10 Jul 2020 16:02:08 -0700 Subject: [PATCH] Metabase lib cleanup - metadata edition (#12859) * Remove StructuredQuery's dependency on deprecated metabase/lib/query.js * Refactor QueryDefinition{,Tooltip} to use metabase-lib * Replace *Name components with displayName and various other legacy cleanup * Fix flow * Move flow types to metabase-types * Remove loadTableAndForeignKeys * Add aggregationOperator/filterOperator to Table/Field and remove metabase/lib/table.js completely * Use metadata database/table/field/etc functions instead of property access * Fix flow --- frontend/src/metabase-lib/README.md | 24 ++-- frontend/src/metabase-lib/lib/Dimension.js | 2 +- frontend/src/metabase-lib/lib/Question.js | 10 -- .../src/metabase-lib/lib/metadata/Database.js | 21 ++++ .../src/metabase-lib/lib/metadata/Field.js | 74 +++++++++--- .../src/metabase-lib/lib/metadata/Table.js | 51 ++++++-- .../metabase-lib/lib/queries/NativeQuery.js | 2 +- .../lib/queries/StructuredQuery.js | 24 +--- .../lib/queries/structured/Aggregation.js | 10 +- .../lib/queries/structured/Filter.js | 2 +- frontend/src/metabase-lib/lib/utils.js | 8 ++ .../admin/datamodel/containers/FieldApp.jsx | 30 ++--- frontend/src/metabase/dashboard/selectors.js | 4 +- frontend/src/metabase/entities/tables.js | 11 +- frontend/src/metabase/lib/permissions.js | 4 +- frontend/src/metabase/lib/query/field_ref.js | 17 +-- frontend/src/metabase/lib/table.js | 85 ------------- frontend/src/metabase/meta/Card.js | 10 +- frontend/src/metabase/meta/Parameter.js | 4 +- .../components/TimeseriesFilterWidget.jsx | 5 +- .../modes/components/modes/TimeseriesMode.jsx | 2 - frontend/src/metabase/modes/lib/drilldown.js | 2 +- .../components/AggregationPopover.jsx | 17 +-- .../query_builder/components/FieldList.jsx | 15 ++- .../components/QueryVisualization.jsx | 6 +- .../components/dataref/FieldPane.jsx | 8 +- .../components/dataref/MetricPane.jsx | 2 +- .../components/dataref/SegmentPane.jsx | 2 +- .../components/dataref/TablePane.jsx | 112 +++++++++--------- .../components/filters/FilterWidgetList.jsx | 19 +-- .../template_tags/TagEditorParam.jsx | 4 +- .../query_builder/containers/QueryBuilder.jsx | 3 - frontend/src/metabase/reference/utils.js | 4 +- frontend/src/metabase/selectors/metadata.js | 27 ----- .../visualizations/ObjectDetail.jsx | 21 ++-- .../metabase-lib/lib/Dimension.unit.spec.js | 6 +- .../containers/SaveQuestionModal.unit.spec.js | 2 +- .../lib/expressions/compile.unit.spec.js | 2 +- frontend/test/metabase/meta/Card.unit.spec.js | 20 +++- .../modes/TimeseriesFilterWidget.unit.spec.js | 1 - .../metabase/selectors/metadata.unit.spec.js | 2 +- 41 files changed, 304 insertions(+), 371 deletions(-) delete mode 100644 frontend/src/metabase/lib/table.js diff --git a/frontend/src/metabase-lib/README.md b/frontend/src/metabase-lib/README.md index 253f8443a30..4979d46aa4f 100644 --- a/frontend/src/metabase-lib/README.md +++ b/frontend/src/metabase-lib/README.md @@ -1,23 +1,23 @@ ## Wrapper Objects: -* `setFoo(bar)`: returns clone of the wrapper but with the "foo" attribute to set to `bar` -* `replace(object)`: returns clone of parent wrapper with this object replaced by `object` -* `remove()`: returns clone of the parent wrapper with this object removed -* `update()`: propagates current wrapper update to parent wrapper, recursively +- `setFoo(bar)`: returns clone of the wrapper but with the "foo" attribute to set to `bar` +- `replace(object)`: returns clone of parent wrapper with this object replaced by `object` +- `remove()`: returns clone of the parent wrapper with this object removed +- `update()`: propagates current wrapper update to parent wrapper, recursively Examples: -* `question().query.aggregation()[0].setDimension(dimension).update()` +- `question().query().aggregation()[0].setDimension(dimension).update()` Exceptions: -* StructuredQuery::updateAggregation, updateBreakout, updateFilter, etc should be called setAggregation, etc +- StructuredQuery::updateAggregation, updateBreakout, updateFilter, etc should be called setAggregation, etc ## Wrapper Hierarchy: -* Question - * StructuredQuery - * Aggregation - * Breakout - * Filter - * NativeQuery +- Question + - StructuredQuery + - Aggregation + - Breakout + - Filter + - NativeQuery diff --git a/frontend/src/metabase-lib/lib/Dimension.js b/frontend/src/metabase-lib/lib/Dimension.js index 4f3745f5476..3970e51866d 100644 --- a/frontend/src/metabase-lib/lib/Dimension.js +++ b/frontend/src/metabase-lib/lib/Dimension.js @@ -473,7 +473,7 @@ export class FieldIDDimension extends FieldDimension { field() { return ( - (this._metadata && this._metadata.fields[this._args[0]]) || + (this._metadata && this._metadata.field(this._args[0])) || new Field({ id: this._args[0] }) ); } diff --git a/frontend/src/metabase-lib/lib/Question.js b/frontend/src/metabase-lib/lib/Question.js index d1b3a8dfc66..95febfeea59 100644 --- a/frontend/src/metabase-lib/lib/Question.js +++ b/frontend/src/metabase-lib/lib/Question.js @@ -627,16 +627,6 @@ export default class Question { } } - // deprecated - tableMetadata(): ?Table { - const query = this.query(); - if (query instanceof StructuredQuery) { - return query.table(); - } else { - return null; - } - } - @memoize mode(): ?Mode { return Mode.forQuestion(this); diff --git a/frontend/src/metabase-lib/lib/metadata/Database.js b/frontend/src/metabase-lib/lib/metadata/Database.js index a81762abe46..b3bad7ac97d 100644 --- a/frontend/src/metabase-lib/lib/metadata/Database.js +++ b/frontend/src/metabase-lib/lib/metadata/Database.js @@ -6,6 +6,8 @@ import Base from "./Base"; import Table from "./Table"; import Schema from "./Schema"; +import { memoize, createLookupByProperty } from "metabase-lib/lib/utils"; + import { generateSchemaId } from "metabase/schema"; import type { SchemaName } from "metabase-types/types/Table"; @@ -33,6 +35,8 @@ export default class Database extends Base { return this.name; } + // SCEMAS + schema(schemaName: ?SchemaName) { return this.metadata.schema(generateSchemaId(this.id, schemaName)); } @@ -41,6 +45,21 @@ export default class Database extends Base { return this.schemas.map(s => s.name).sort((a, b) => a.localeCompare(b)); } + // TABLES + + @memoize + tablesLookup() { + return createLookupByProperty(this.tables, "id"); + } + + // @deprecated: use tablesLookup + // $FlowFixMe: known to not have side-effects + get tables_lookup() { + return this.tablesLookup(); + } + + // FEATURES + hasFeature( feature: null | DatabaseFeature | VirtualDatabaseFeature, ): boolean { @@ -60,6 +79,8 @@ export default class Database extends Base { } } + // QUESTIONS + newQuestion(): Question { return this.question() .setDefaultQuery() diff --git a/frontend/src/metabase-lib/lib/metadata/Field.js b/frontend/src/metabase-lib/lib/metadata/Field.js index 0317eadbc7d..83b4bafff2f 100644 --- a/frontend/src/metabase-lib/lib/metadata/Field.js +++ b/frontend/src/metabase-lib/lib/metadata/Field.js @@ -5,6 +5,8 @@ import Table from "./Table"; import moment from "moment"; +import { memoize, createLookupByProperty } from "metabase-lib/lib/utils"; + import Dimension from "../Dimension"; import { formatField, stripId } from "metabase/lib/formatting"; @@ -48,7 +50,7 @@ export default class Field extends Base { name_field: ?Field; parent() { - return this.metadata ? this.metadata.fields[this.parent_id] : null; + return this.metadata ? this.metadata.field(this.parent_id) : null; } path() { @@ -187,28 +189,70 @@ export default class Field extends Base { return d && d.field(); } + // FILTERS + + @memoize + filterOperators() { + return getFilterOperators(this, this.table); + } + + @memoize + filterOperatorsLookup() { + return createLookupByProperty(this.filterOperators(), "name"); + } + filterOperator(operatorName) { - if (this.filter_operators_lookup) { - return this.filter_operators_lookup[operatorName]; - } else { - return this.filterOperators().find(o => o.name === operatorName); - } + return this.filterOperatorsLookup()[operatorName]; } - filterOperators() { - return this.filter_operators || getFilterOperators(this, this.table); + // @deprecated: use filterOperators + // $FlowFixMe: known to not have side-effects + get filter_operators() { + return this.filterOperators(); + } + // @deprecated: use filterOperatorsLookup + // $FlowFixMe: known to not have side-effects + get filter_operators_lookup() { + return this.filterOperatorsLookup(); } + // AGGREGATIONS + + @memoize aggregationOperators() { return this.table - ? this.table.aggregation_operators.filter( - aggregation => - aggregation.validFieldsFilters[0] && - aggregation.validFieldsFilters[0]([this]).length === 1, - ) + ? this.table + .aggregationOperators() + .filter( + aggregation => + aggregation.validFieldsFilters[0] && + aggregation.validFieldsFilters[0]([this]).length === 1, + ) : null; } + @memoize + aggregationOperatorsLookup() { + return createLookupByProperty(this.aggregationOperators(), "short"); + } + + aggregationOperator(short) { + return this.aggregationOperatorsLookup()[short]; + } + + // @deprecated: use aggregationOperators + // $FlowFixMe: known to not have side-effects + get aggregation_operators() { + return this.aggregationOperators(); + } + // @deprecated: use aggregationOperatorsLookup + // $FlowFixMe: known to not have side-effects + get aggregation_operators_lookup() { + return this.aggregationOperatorsLookup(); + } + + // BREAKOUTS + /** * Returns a default breakout MBQL clause for this field */ @@ -240,6 +284,8 @@ export default class Field extends Base { } } + // REMAPPINGS + /** * Returns the remapped field, if any */ @@ -247,7 +293,7 @@ export default class Field extends Base { const displayFieldId = this.dimensions && this.dimensions.human_readable_field_id; if (displayFieldId != null) { - return this.metadata.fields[displayFieldId]; + return this.metadata.field(displayFieldId); } // this enables "implicit" remappings from type/PK to type/Name on the same table, // used in FieldValuesWidget, but not table/object detail listings diff --git a/frontend/src/metabase-lib/lib/metadata/Table.js b/frontend/src/metabase-lib/lib/metadata/Table.js index df0c25909d7..9a627f85ef4 100644 --- a/frontend/src/metabase-lib/lib/metadata/Table.js +++ b/frontend/src/metabase-lib/lib/metadata/Table.js @@ -8,19 +8,17 @@ import Database from "./Database"; import Schema from "./Schema"; import Field from "./Field"; -import type { SchemaName } from "metabase-types/types/Table"; -import type { FieldMetadata } from "metabase-types/types/Metadata"; +import Dimension from "../Dimension"; import { singularize } from "metabase/lib/formatting"; +import { getAggregationOperatorsWithFields } from "metabase/lib/schema_metadata"; +import { memoize, createLookupByProperty } from "metabase-lib/lib/utils"; -import Dimension from "../Dimension"; - +import type { SchemaName } from "metabase-types/types/Table"; import type StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"; type EntityType = string; // TODO: move somewhere central -import _ from "underscore"; - /** This is the primary way people interact with tables */ export default class Table extends Base { description: string; @@ -31,7 +29,7 @@ export default class Table extends Base { // @deprecated: use schema.name (all tables should have a schema object, in theory) schema_name: ?SchemaName; - fields: FieldMetadata[]; + fields: Field[]; entity_type: ?EntityType; @@ -91,11 +89,44 @@ export default class Table extends Base { return this.fields.filter(field => field.isDate()); } + // AGGREGATIONS + + @memoize aggregationOperators() { - return this.aggregation_operators || []; + return getAggregationOperatorsWithFields(this); + } + + @memoize + aggregationOperatorsLookup() { + return createLookupByProperty(this.aggregationOperators(), "short"); + } + + aggregationOperator(short) { + return this.aggregation_operators_lookup[short]; + } + + // @deprecated: use aggregationOperators + // $FlowFixMe: known to not have side-effects + get aggregation_operators() { + return this.aggregationOperators(); + } + + // @deprecated: use aggregationOperatorsLookup + // $FlowFixMe: known to not have side-effects + get aggregation_operators_lookup() { + return this.aggregationOperatorsLookup(); + } + + // FIELDS + + @memoize + fieldsLookup() { + return createLookupByProperty(this.fields, "id"); } - aggregation(agg) { - return _.findWhere(this.aggregationOperators(), { short: agg }); + // @deprecated: use fieldsLookup + // $FlowFixMe: known to not have side-effects + get fields_lookup() { + return this.fieldsLookup(); } } diff --git a/frontend/src/metabase-lib/lib/queries/NativeQuery.js b/frontend/src/metabase-lib/lib/queries/NativeQuery.js index 8a41996d84d..70bb0656464 100644 --- a/frontend/src/metabase-lib/lib/queries/NativeQuery.js +++ b/frontend/src/metabase-lib/lib/queries/NativeQuery.js @@ -125,7 +125,7 @@ export default class NativeQuery extends AtomicQuery { } database(): ?Database { const databaseId = this.databaseId(); - return databaseId != null ? this._metadata.databases[databaseId] : null; + return databaseId != null ? this._metadata.database(databaseId) : null; } engine(): ?DatabaseEngine { const database = this.database(); diff --git a/frontend/src/metabase-lib/lib/queries/StructuredQuery.js b/frontend/src/metabase-lib/lib/queries/StructuredQuery.js index 2d8ccd6e945..b9402c62885 100644 --- a/frontend/src/metabase-lib/lib/queries/StructuredQuery.js +++ b/frontend/src/metabase-lib/lib/queries/StructuredQuery.js @@ -5,7 +5,6 @@ */ import * as Q from "metabase/lib/query/query"; -import { addValidOperatorsToFields } from "metabase/lib/schema_metadata"; import { format as formatExpression, DISPLAY_QUOTES, @@ -29,10 +28,7 @@ import type { DatasetQuery, StructuredDatasetQuery, } from "metabase-types/types/Card"; -import type { - TableMetadata, - AggregationOperator, -} from "metabase-types/types/Metadata"; +import type { AggregationOperator } from "metabase-types/types/Metadata"; import Dimension, { FKDimension, @@ -59,7 +55,6 @@ import OrderByWrapper from "./structured/OrderBy"; import Table from "../metadata/Table"; import Field from "../metadata/Field"; -import { augmentDatabase } from "metabase/lib/table"; import { TYPE } from "metabase/lib/types"; @@ -303,12 +298,12 @@ export default class StructuredQuery extends AtomicQuery { table(): Table { const sourceQuery = this.sourceQuery(); if (sourceQuery) { - const table = new Table({ + return new Table({ name: "", display_name: "", db: sourceQuery.database(), fields: sourceQuery.columns().map( - (column, index) => + column => new Field({ ...column, id: ["field-literal", column.name, column.base_type], @@ -320,22 +315,11 @@ export default class StructuredQuery extends AtomicQuery { segments: [], metrics: [], }); - // HACK: ugh various parts of the UI still expect this stuff - addValidOperatorsToFields(table); - augmentDatabase({ tables: [table] }); - return table; } else { return this.metadata().table(this.sourceTableId()); } } - /** - * @deprecated Alias of `table()`. Use only when partially porting old code that uses @type {TableMetadata} object. - */ - tableMetadata(): ?TableMetadata { - return this.table(); - } - /** * Removes invalid clauses from the query (and source-query, recursively) */ @@ -634,7 +618,7 @@ export default class StructuredQuery extends AtomicQuery { */ aggregationFieldOptions(agg: string | AggregationOperator): DimensionOptions { const aggregation: AggregationOperator = - typeof agg === "string" ? this.table().aggregation(agg) : agg; + typeof agg === "string" ? this.table().aggregationOperator(agg) : agg; if (aggregation) { const fieldOptions = this.fieldOptions(field => { return ( diff --git a/frontend/src/metabase-lib/lib/queries/structured/Aggregation.js b/frontend/src/metabase-lib/lib/queries/structured/Aggregation.js index bb0ae779655..2ec65953e16 100644 --- a/frontend/src/metabase-lib/lib/queries/structured/Aggregation.js +++ b/frontend/src/metabase-lib/lib/queries/structured/Aggregation.js @@ -138,14 +138,14 @@ export default class Aggregation extends MBQLClause { return this.aggregation().isValid(); } else if (this.isStandard() && this.dimension()) { const dimension = this.dimension(); - const aggregation = this.query() + const aggregationOperator = this.query() .table() - .aggregation(this[0]); + .aggregationOperator(this[0]); return ( - aggregation && - (!aggregation.requiresField || + aggregationOperator && + (!aggregationOperator.requiresField || this.query() - .aggregationFieldOptions(aggregation) + .aggregationFieldOptions(aggregationOperator) .hasDimension(dimension)) ); } else if (this.isMetric()) { diff --git a/frontend/src/metabase-lib/lib/queries/structured/Filter.js b/frontend/src/metabase-lib/lib/queries/structured/Filter.js index 2b7a7ddc3c3..07544edce75 100644 --- a/frontend/src/metabase-lib/lib/queries/structured/Filter.js +++ b/frontend/src/metabase-lib/lib/queries/structured/Filter.js @@ -224,7 +224,7 @@ export default class Filter extends MBQLClause { setDimension( fieldRef: ?Field, { useDefaultOperator = false }: { useDefaultOperator?: boolean } = {}, - ) { + ): Filter { if (!fieldRef) { return this.set([]); } diff --git a/frontend/src/metabase-lib/lib/utils.js b/frontend/src/metabase-lib/lib/utils.js index 6cdacc70e41..8cc16b8ecda 100644 --- a/frontend/src/metabase-lib/lib/utils.js +++ b/frontend/src/metabase-lib/lib/utils.js @@ -56,3 +56,11 @@ export function sortObject(obj) { } return o; } + +export function createLookupByProperty(items, property) { + const lookup = {}; + for (const item of items) { + lookup[item[property]] = item; + } + return lookup; +} diff --git a/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx b/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx index cda9658ad3f..eb93593c6cd 100644 --- a/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx +++ b/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx @@ -195,8 +195,8 @@ export default class FieldApp extends React.Component { params: { section }, } = this.props; - const db = metadata.databases[databaseId]; - const table = metadata.tables[tableId]; + const db = metadata.database(databaseId); + const table = metadata.table(tableId); const isLoading = !field || !table || !idfields; @@ -224,18 +224,20 @@ export default class FieldApp extends React.Component { } > <div className="wrapper"> - <div className="mb4 pt2 ml-auto mr-auto"> - <Breadcrumbs - crumbs={[ - [db.name, `/admin/datamodel/database/${db.id}`], - [ - table.display_name, - `/admin/datamodel/database/${db.id}/table/${table.id}`, - ], - t`${field.display_name} – Field Settings`, - ]} - /> - </div> + {db && table && ( + <div className="mb4 pt2 ml-auto mr-auto"> + <Breadcrumbs + crumbs={[ + [db.name, `/admin/datamodel/database/${db.id}`], + [ + table.display_name, + `/admin/datamodel/database/${db.id}/table/${table.id}`, + ], + t`${field.display_name} – Field Settings`, + ]} + /> + </div> + )} <div className="absolute top right mt4 mr4"> <SaveStatus ref={ref => (this.saveStatus = ref)} /> </div> diff --git a/frontend/src/metabase/dashboard/selectors.js b/frontend/src/metabase/dashboard/selectors.js index 846adf3a075..d5f9d522f13 100644 --- a/frontend/src/metabase/dashboard/selectors.js +++ b/frontend/src/metabase/dashboard/selectors.js @@ -123,7 +123,7 @@ export const getMappingsByParameter = createSelector( const card = _.findWhere(cards, { id: mapping.card_id }); const fieldId = card && getParameterTargetFieldId(mapping.target, card.dataset_query); - const field = metadata.fields[fieldId]; + const field = metadata.field(fieldId); const values = (field && field.fieldValues()) || []; if (values.length) { countsByParameter[mapping.parameter_id] = @@ -210,7 +210,7 @@ export const getParameters = createSelector( .filter(fieldId => fieldId != null) .value(); const fieldIdsWithFKResolved = _.chain(fieldIds) - .map(id => metadata.fields[id]) + .map(id => metadata.field(id)) .filter(f => f) .map(f => (f.target || f).id) .uniq() diff --git a/frontend/src/metabase/entities/tables.js b/frontend/src/metabase/entities/tables.js index 6886a96ec87..fe3bdc216c6 100644 --- a/frontend/src/metabase/entities/tables.js +++ b/frontend/src/metabase/entities/tables.js @@ -22,8 +22,6 @@ import Fields from "metabase/entities/fields"; import { GET, PUT } from "metabase/lib/api"; -import { addValidOperatorsToFields } from "metabase/lib/schema_metadata"; - import { getMetadata } from "metabase/selectors/metadata"; const listTables = GET("/api/table"); @@ -78,13 +76,12 @@ const Tables = createEntity({ ({ id }) => [...Tables.getObjectStatePath(id), "fetchMetadata"], ), withNormalize(TableSchema), - )(({ id }, options = {}) => async (dispatch, getState) => { - const table = await MetabaseApi.table_query_metadata({ + )(({ id }, options = {}) => (dispatch, getState) => + MetabaseApi.table_query_metadata({ tableId: id, ...options.params, - }); - return addValidOperatorsToFields(table); - }), + }), + ), // like fetchMetadata but also loads tables linked by foreign key fetchMetadataAndForeignTables: createThunkAction( diff --git a/frontend/src/metabase/lib/permissions.js b/frontend/src/metabase/lib/permissions.js index 1e4b27522ad..5f77dd29a19 100644 --- a/frontend/src/metabase/lib/permissions.js +++ b/frontend/src/metabase/lib/permissions.js @@ -200,7 +200,7 @@ function inferEntityPermissionValueFromChildTables( metadata: Metadata, ) { const { databaseId } = entityId; - const database = metadata && metadata.databases[databaseId]; + const database = metadata && metadata.database(databaseId); const entityIdsForDescendantTables: TableEntityId[] = _.chain(database.tables) .filter(t => _.isMatch(t, entityIdToMetadataTableFields(entityId))) @@ -342,7 +342,7 @@ export function updateSchemasPermission( value: string, metadata: Metadata, ): GroupsPermissions { - const database = metadata.databases[databaseId]; + const database = metadata.database(databaseId); const schemaNames = database && database.schemaNames(); const schemaNamesOrNoSchema = schemaNames && diff --git a/frontend/src/metabase/lib/query/field_ref.js b/frontend/src/metabase/lib/query/field_ref.js index 33792369fe4..c8304f40aee 100644 --- a/frontend/src/metabase/lib/query/field_ref.js +++ b/frontend/src/metabase/lib/query/field_ref.js @@ -3,9 +3,7 @@ import _ from "underscore"; import Field from "metabase-lib/lib/metadata/Field"; import * as Table from "./table"; -import { getFilterOperators } from "metabase/lib/schema_metadata"; import { TYPE } from "metabase/lib/types"; -import { createLookupByProperty } from "metabase/lib/table"; // DEPRECATED export function isRegularField(field: FieldReference): boolean { @@ -126,24 +124,11 @@ export function getFieldTarget(field, tableDef, path = []) { display_name: field[1], name: field[1], expression_name: field[1], + table: tableDef, metadata: tableDef.metadata, // TODO: we need to do something better here because filtering depends on knowing a sensible type for the field base_type: TYPE.Float, - filter_operators_lookup: {}, - filter_operators: [], - active: true, - fk_target_field_id: null, - parent_id: null, - preview_display: true, - special_type: null, - target: null, - visibility_type: "normal", }); - fieldDef.filter_operators = getFilterOperators(fieldDef, tableDef); - fieldDef.filter_operators_lookup = createLookupByProperty( - fieldDef.filter_operators, - "name", - ); return { table: tableDef, diff --git a/frontend/src/metabase/lib/table.js b/frontend/src/metabase/lib/table.js deleted file mode 100644 index 7650fccb7c4..00000000000 --- a/frontend/src/metabase/lib/table.js +++ /dev/null @@ -1,85 +0,0 @@ -import { addValidOperatorsToFields } from "metabase/lib/schema_metadata"; - -import _ from "underscore"; - -import { MetabaseApi } from "metabase/services"; - -export async function loadTableAndForeignKeys(tableId) { - const [table, foreignKeys] = await Promise.all([ - MetabaseApi.table_query_metadata({ tableId }), - MetabaseApi.table_fks({ tableId }), - ]); - - await augmentTable(table); - - return { - table, - foreignKeys, - }; -} - -export async function augmentTable(table) { - table = populateQueryOptions(table); - table = await loadForeignKeyTables(table); - return table; -} - -export function augmentDatabase(database) { - database.tables_lookup = createLookupByProperty(database.tables, "id"); - for (const table of database.tables) { - addValidOperatorsToFields(table); - table.fields_lookup = createLookupByProperty(table.fields, "id"); - for (const field of table.fields) { - addFkTargets(field, database.tables_lookup); - field.filter_operators_lookup = createLookupByProperty( - field.filter_operators, - "name", - ); - } - } - return database; -} - -async function loadForeignKeyTables(table) { - // Load joinable tables - await Promise.all( - table.fields - .filter(f => f.target != null) - .map(async field => { - const targetTable = await MetabaseApi.table_query_metadata({ - tableId: field.target.table_id, - }); - field.target.table = populateQueryOptions(targetTable); - }), - ); - return table; -} - -function populateQueryOptions(table) { - table = addValidOperatorsToFields(table); - - table.fields_lookup = {}; - - _.each(table.fields, function(field) { - table.fields_lookup[field.id] = field; - field.filter_operators_lookup = {}; - _.each(field.filter_operators, function(operator) { - field.filter_operators_lookup[operator.name] = operator; - }); - }); - - return table; -} - -function addFkTargets(field, tables) { - if (field.target != null) { - field.target.table = tables[field.target.table_id]; - } -} - -export function createLookupByProperty(items, property) { - return items.reduce((lookup, item) => { - lookup[item[property]] = item; - return lookup; - }, {}); -} diff --git a/frontend/src/metabase/meta/Card.js b/frontend/src/metabase/meta/Card.js index 55dc15a3c78..257d5345b02 100644 --- a/frontend/src/metabase/meta/Card.js +++ b/frontend/src/metabase/meta/Card.js @@ -26,7 +26,8 @@ import type { ParameterMapping, ParameterValues, } from "metabase-types/types/Parameter"; -import type { Metadata, TableMetadata } from "metabase-types/types/Metadata"; +import type Metadata from "metabase-lib/lib/metadata/Metadata"; +import type Table from "metabase-lib/lib/metadata/Table"; declare class Object { static values<T>(object: { [key: string]: T }): Array<T>; @@ -97,13 +98,10 @@ export function getQuery(card: Card): ?StructuredQuery { } } -export function getTableMetadata( - card: Card, - metadata: Metadata, -): ?TableMetadata { +export function getTableMetadata(card: Card, metadata: Metadata): ?Table { const query = getQuery(card); if (query && query["source-table"] != null) { - return metadata.tables[query["source-table"]] || null; + return metadata.table(query["source-table"]) || null; } return null; } diff --git a/frontend/src/metabase/meta/Parameter.js b/frontend/src/metabase/meta/Parameter.js index 93dc13114da..eeaa6792540 100644 --- a/frontend/src/metabase/meta/Parameter.js +++ b/frontend/src/metabase/meta/Parameter.js @@ -17,7 +17,7 @@ import type { ParameterType, } from "metabase-types/types/Parameter"; import type { FieldId } from "metabase-types/types/Field"; -import type { Metadata } from "metabase-types/types/Metadata"; +import type Metadata from "metabase-lib/lib/metadata/Metadata"; import moment from "moment"; @@ -212,7 +212,7 @@ export function parameterToMBQLFilter( return dateParameterValueToMBQL(parameter.value, fieldRef); } else { const fieldId = FIELD_REF.getFieldTargetId(fieldRef); - const field = metadata.fields[fieldId]; + const field = metadata.field(fieldId); // if the field is numeric, parse the value as a number if (isNumericBaseType(field)) { return numberParameterValueToMBQL(parameter.value, fieldRef); diff --git a/frontend/src/metabase/modes/components/TimeseriesFilterWidget.jsx b/frontend/src/metabase/modes/components/TimeseriesFilterWidget.jsx index 32d630d8a01..62bf5c1b805 100644 --- a/frontend/src/metabase/modes/components/TimeseriesFilterWidget.jsx +++ b/frontend/src/metabase/modes/components/TimeseriesFilterWidget.jsx @@ -25,13 +25,11 @@ import type { Card as CardObject, StructuredDatasetQuery, } from "metabase-types/types/Card"; -import type { TableMetadata } from "metabase-types/types/Metadata"; import type { FieldFilter } from "metabase-types/types/Query"; type Props = { className?: string, card: CardObject, - tableMetadata: TableMetadata, setDatasetQuery: ( datasetQuery: StructuredDatasetQuery, options: { run: boolean }, @@ -88,7 +86,7 @@ export default class TimeseriesFilterWidget extends Component { } render() { - const { className, card, tableMetadata, setDatasetQuery } = this.props; + const { className, card, setDatasetQuery } = this.props; const { filter, filterIndex, currentFilter } = this.state; let currentDescription; @@ -126,7 +124,6 @@ export default class TimeseriesFilterWidget extends Component { onFilterChange={newFilter => { this.setState({ filter: newFilter }); }} - tableMetadata={tableMetadata} includeAllTime /> <div className="p1"> diff --git a/frontend/src/metabase/modes/components/modes/TimeseriesMode.jsx b/frontend/src/metabase/modes/components/modes/TimeseriesMode.jsx index e64be7869b4..0fec4972e93 100644 --- a/frontend/src/metabase/modes/components/modes/TimeseriesMode.jsx +++ b/frontend/src/metabase/modes/components/modes/TimeseriesMode.jsx @@ -16,12 +16,10 @@ import type { Card as CardObject, DatasetQuery, } from "metabase-types/types/Card"; -import type { TableMetadata } from "metabase-types/types/Metadata"; import TimeseriesGroupingWidget from "metabase/modes/components/TimeseriesGroupingWidget"; type Props = { lastRunCard: CardObject, - tableMetadata: TableMetadata, setDatasetQuery: (datasetQuery: DatasetQuery) => void, runQuestionQuery: () => void, }; diff --git a/frontend/src/metabase/modes/lib/drilldown.js b/frontend/src/metabase/modes/lib/drilldown.js index 8fef2fc48b4..8105223f3a4 100644 --- a/frontend/src/metabase/modes/lib/drilldown.js +++ b/frontend/src/metabase/modes/lib/drilldown.js @@ -254,6 +254,6 @@ function columnToBreakout(column) { // returns the table metadata for a dimension function tableForDimensions(dimensions, metadata) { const fieldId = getIn(dimensions, [0, "column", "id"]); - const field = metadata.fields[fieldId]; + const field = metadata.field(fieldId); return field && field.table; } diff --git a/frontend/src/metabase/query_builder/components/AggregationPopover.jsx b/frontend/src/metabase/query_builder/components/AggregationPopover.jsx index 47b7b430f5d..02f36932861 100644 --- a/frontend/src/metabase/query_builder/components/AggregationPopover.jsx +++ b/frontend/src/metabase/query_builder/components/AggregationPopover.jsx @@ -139,11 +139,6 @@ export default class AggregationPopover extends Component { }); }; - _getTableMetadata() { - const { query, tableMetadata } = this.props; - return tableMetadata || query.tableMetadata(); - } - _getAvailableAggregations() { const { aggregationOperators, query, dimension, showRawData } = this.props; return ( @@ -193,14 +188,14 @@ export default class AggregationPopover extends Component { alwaysExpanded, } = this.props; - const tableMetadata = this._getTableMetadata(); + const table = query.table(); const aggregationOperators = this._getAvailableAggregations(); if (dimension) { showCustom = false; showMetrics = false; } - if (tableMetadata.db.features.indexOf("expression-aggregations") < 0) { + if (table.database.hasFeature("expression-aggregations")) { showCustom = false; } @@ -209,7 +204,7 @@ export default class AggregationPopover extends Component { let selectedAggregation; if (AGGREGATION.isMetric(aggregation)) { - selectedAggregation = _.findWhere(tableMetadata.metrics, { + selectedAggregation = _.findWhere(table.metrics, { id: AGGREGATION.getMetric(aggregation), }); } else if (AGGREGATION.isStandard(aggregation)) { @@ -231,8 +226,8 @@ export default class AggregationPopover extends Component { // we only want to consider active metrics, with the ONE exception that if the currently selected aggregation is a // retired metric then we include it in the list to maintain continuity - const metrics = tableMetadata.metrics - ? tableMetadata.metrics.filter(metric => + const metrics = table.metrics + ? table.metrics.filter(metric => showMetrics ? !metric.archived || (selectedAggregation && selectedAggregation.id === metric.id) @@ -349,7 +344,7 @@ export default class AggregationPopover extends Component { className={"text-green"} width={this.props.width} maxHeight={this.props.maxHeight - (this.state.headerHeight || 0)} - table={tableMetadata} + query={query} field={fieldId} fieldOptions={query.aggregationFieldOptions(agg)} onFieldChange={this.onPickField} diff --git a/frontend/src/metabase/query_builder/components/FieldList.jsx b/frontend/src/metabase/query_builder/components/FieldList.jsx index 7334b126a69..21691406a08 100644 --- a/frontend/src/metabase/query_builder/components/FieldList.jsx +++ b/frontend/src/metabase/query_builder/components/FieldList.jsx @@ -7,11 +7,9 @@ import DimensionList from "./DimensionList"; import Dimension from "metabase-lib/lib/Dimension"; import DimensionOptions from "metabase-lib/lib/DimensionOptions"; -import type { - StructuredQuery, - ConcreteField, -} from "metabase-types/types/Query"; +import type { ConcreteField } from "metabase-types/types/Query"; import type Metadata from "metabase-lib/lib/metadata/Metadata"; +import type StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"; // import type { Section } from "metabase/components/AccordionList"; export type AccordionListItem = {}; @@ -87,11 +85,16 @@ export default class FieldList extends Component { }; render() { - const { field, metadata, query } = this.props; + const { field, query, metadata } = this.props; + const dimension = + field && + (query + ? query.parseFieldReference(field) + : Dimension.parseMBQL(field, metadata)); return ( <DimensionList sections={this.state.sections} - dimension={field && Dimension.parseMBQL(field, metadata, query)} + dimension={dimension} onChangeDimension={this.handleChangeDimension} onChangeOther={this.handleChangeOther} // forward AccordionList props diff --git a/frontend/src/metabase/query_builder/components/QueryVisualization.jsx b/frontend/src/metabase/query_builder/components/QueryVisualization.jsx index 7ba03db0513..1b751d6a5b8 100644 --- a/frontend/src/metabase/query_builder/components/QueryVisualization.jsx +++ b/frontend/src/metabase/query_builder/components/QueryVisualization.jsx @@ -15,8 +15,8 @@ import Utils from "metabase/lib/utils"; import cx from "classnames"; import Question from "metabase-lib/lib/Question"; -import type { Database } from "metabase-types/types/Database"; -import type { TableMetadata } from "metabase-types/types/Metadata"; +import type Database from "metabase-lib/lib/metadata/Database"; +import type Table from "metabase-lib/lib/metadata/Table"; import type { DatasetQuery } from "metabase-types/types/Card"; import type { ParameterValues } from "metabase-types/types/Parameter"; @@ -26,7 +26,7 @@ type Props = { originalQuestion: Question, result?: Object, databases?: Database[], - tableMetadata?: TableMetadata, + tableMetadata?: Table, tableForeignKeys?: [], tableForeignKeyReferences?: {}, onUpdateVisualizationSettings: any => void, diff --git a/frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx b/frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx index 8f5d795e05f..4782cf433c7 100644 --- a/frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx +++ b/frontend/src/metabase/query_builder/components/dataref/FieldPane.jsx @@ -78,7 +78,7 @@ export default class FieldPane extends Component { query = query.clearAggregations(); } - const defaultBreakout = metadata.fields[field.id].getDefaultBreakout(); + const defaultBreakout = metadata.field(field.id).getDefaultBreakout(); query = query.breakout(defaultBreakout); this.props.updateQuestion(query.question()); @@ -89,7 +89,7 @@ export default class FieldPane extends Component { newCard = () => { const { metadata, field } = this.props; const tableId = field.table_id; - const dbId = metadata.tables[tableId].database.id; + const dbId = metadata.table(tableId).database.id; const card = createCard(); card.dataset_query = Q_DEPRECATED.createQuery("query", dbId, tableId); @@ -104,7 +104,7 @@ export default class FieldPane extends Component { setQueryDistinct = () => { const { metadata, field } = this.props; - const defaultBreakout = metadata.fields[field.id].getDefaultBreakout(); + const defaultBreakout = metadata.field(field.id).getDefaultBreakout(); const card = this.newCard(); card.dataset_query.query.aggregation = ["rows"]; @@ -114,7 +114,7 @@ export default class FieldPane extends Component { setQueryCountGroupedBy = chartType => { const { metadata, field } = this.props; - const defaultBreakout = metadata.fields[field.id].getDefaultBreakout(); + const defaultBreakout = metadata.field(field.id).getDefaultBreakout(); const card = this.newCard(); card.dataset_query.query.aggregation = ["count"]; diff --git a/frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx b/frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx index fca11bfe882..daa2484046c 100644 --- a/frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx +++ b/frontend/src/metabase/query_builder/components/dataref/MetricPane.jsx @@ -50,7 +50,7 @@ export default class MetricPane extends Component { newCard() { const { metric, metadata } = this.props; - const table = metadata && metadata.tables[metric.table_id]; + const table = metadata && metadata.table(metric.table_id); if (table) { const card = createCard(); diff --git a/frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx b/frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx index 4cf803b2ea0..6b18b06862b 100644 --- a/frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx +++ b/frontend/src/metabase/query_builder/components/dataref/SegmentPane.jsx @@ -76,7 +76,7 @@ export default class SegmentPane extends Component { newCard() { const { segment, metadata } = this.props; - const table = metadata && metadata.tables[segment.table_id]; + const table = metadata && metadata.table(segment.table_id); if (table) { const card = createCard(); diff --git a/frontend/src/metabase/query_builder/components/dataref/TablePane.jsx b/frontend/src/metabase/query_builder/components/dataref/TablePane.jsx index ce9cc73e7c3..94e65eb6234 100644 --- a/frontend/src/metabase/query_builder/components/dataref/TablePane.jsx +++ b/frontend/src/metabase/query_builder/components/dataref/TablePane.jsx @@ -1,85 +1,85 @@ /* eslint "react/prop-types": "warn" */ -import React, { Component } from "react"; +import React from "react"; import PropTypes from "prop-types"; +import { connect } from "react-redux"; import { t } from "ttag"; import cx from "classnames"; -import Icon from "metabase/components/Icon"; // components +import Icon from "metabase/components/Icon"; import Expandable from "metabase/components/Expandable"; // lib -import { createCard } from "metabase/lib/card"; -import * as Q_DEPRECATED from "metabase/lib/query"; import { foreignKeyCountsByOriginTable } from "metabase/lib/schema_metadata"; import { inflect } from "metabase/lib/formatting"; -export default class TablePane extends Component { - constructor(props, context) { - super(props, context); - this.setQueryAllRows = this.setQueryAllRows.bind(this); - this.showPane = this.showPane.bind(this); - - this.state = { - table: undefined, - tableForeignKeys: undefined, - pane: "fields", - }; - } +// entities +import Table from "metabase/entities/tables"; + +const mapStateToProps = (state, ownProps) => ({ + tableId: ownProps.table.id, + table: Table.selectors.getObject(state, { entityId: ownProps.table.id }), +}); + +const mapDispatchToProps = { + fetchForeignKeys: Table.actions.fetchForeignKeys, + fetchMetadata: Table.actions.fetchMetadata, +}; + +@connect( + mapStateToProps, + mapDispatchToProps, +) +export default class TablePane extends React.Component { + state = { + pane: "fields", + error: null, + }; static propTypes = { query: PropTypes.object.isRequired, - loadTableAndForeignKeysFn: PropTypes.func.isRequired, show: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, setCardAndRun: PropTypes.func.isRequired, + tableId: PropTypes.number.isRequired, table: PropTypes.object, + fetchForeignKeys: PropTypes.func.isRequired, + fetchMetadata: PropTypes.func.isRequired, }; - componentWillMount() { - this.props - .loadTableAndForeignKeysFn(this.props.table.id) - .then(result => { - this.setState({ - table: result.table, - tableForeignKeys: result.foreignKeys, - }); - }) - .catch(error => { - this.setState({ - error: t`An error occurred loading the table`, - }); + async componentWillMount() { + try { + await Promise.all([ + this.props.fetchForeignKeys({ id: this.props.tableId }), + this.props.fetchMetadata({ id: this.props.tableId }), + ]); + } catch (e) { + this.setState({ + error: t`An error occurred loading the table`, }); + } } - showPane(name) { + showPane = name => { this.setState({ pane: name }); - } - - setQueryAllRows() { - const card = createCard(); - card.dataset_query = Q_DEPRECATED.createQuery( - "query", - this.state.table.db_id, - this.state.table.id, - ); - this.props.setCardAndRun(card); - } + }; render() { - const { table, error } = this.state; + const { table } = this.props; + const { pane, error } = this.state; if (table) { + const fks = table.fks || []; const panes = { fields: table.fields.length, // "metrics": table.metrics.length, // "segments": table.segments.length, - connections: this.state.tableForeignKeys.length, + connections: fks.length, }; const tabs = Object.entries(panes).map(([name, count]) => ( <a key={name} className={cx("Button Button--small", { - "Button--active": name === this.state.pane, + "Button--active": name === pane, })} onClick={this.showPane.bind(null, name)} > @@ -88,20 +88,18 @@ export default class TablePane extends Component { </a> )); - let pane; const descriptionClasses = cx({ "text-medium": !table.description }); const description = ( <p className={"text-spaced " + descriptionClasses}> {table.description || t`No description set.`} </p> ); - if (this.state.pane === "connections") { - const fkCountsByTable = foreignKeyCountsByOriginTable( - this.state.tableForeignKeys, - ); - pane = ( + let content; + if (pane === "connections") { + const fkCountsByTable = foreignKeyCountsByOriginTable(fks); + content = ( <ul> - {this.state.tableForeignKeys + {fks .sort((a, b) => a.origin.table.display_name.localeCompare( b.origin.table.display_name, @@ -126,11 +124,11 @@ export default class TablePane extends Component { ))} </ul> ); - } else if (this.state.pane) { - const itemType = this.state.pane.replace(/s$/, ""); - pane = ( + } else if (pane) { + const itemType = pane.replace(/s$/, ""); + content = ( <ul> - {table[this.state.pane].map((item, index) => ( + {table[pane].map((item, index) => ( <li> <a key={item.id} @@ -157,7 +155,7 @@ export default class TablePane extends Component { {tabs} </div> </div> - {pane} + {content} </div> ); } else { diff --git a/frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx b/frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx index 4dc3fff1b3e..ba46e64d53d 100644 --- a/frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx +++ b/frontend/src/metabase/query_builder/components/filters/FilterWidgetList.jsx @@ -1,15 +1,12 @@ /* @flow */ -import React, { Component } from "react"; +import React from "react"; import { findDOMNode } from "react-dom"; import { t } from "ttag"; import FilterWidget from "./FilterWidget"; import StructuredQuery from "metabase-lib/lib/queries/StructuredQuery"; import Filter from "metabase-lib/lib/queries/structured/Filter"; -import Dimension from "metabase-lib/lib/Dimension"; - -import type { TableMetadata } from "metabase-types/types/Metadata"; type Props = { query: StructuredQuery, @@ -17,14 +14,13 @@ type Props = { removeFilter?: (index: number) => void, updateFilter?: (index: number, filter: Filter) => void, maxDisplayValues?: number, - tableMetadata?: TableMetadata, // legacy parameter }; type State = { shouldScroll: boolean, }; -export default class FilterList extends Component { +export default class FilterWidgetList extends React.Component { props: Props; state: State; @@ -55,21 +51,14 @@ export default class FilterList extends Component { } render() { - const { query, filters, tableMetadata } = this.props; + const { query, filters } = this.props; return ( <div className="Query-filterList scroll-x scroll-show"> {filters.map((filter, index) => ( <FilterWidget key={index} placeholder={t`Item`} - // TODO: update widgets that are still passing tableMetadata instead of query - query={ - query || { - table: () => tableMetadata, - parseFieldReference: fieldRef => - Dimension.parseMBQL(fieldRef, tableMetadata), - } - } + query={query} filter={filter} index={index} removeFilter={this.props.removeFilter} diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx index 6c05ed9fadf..68a9f7d2cdc 100644 --- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx +++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx @@ -83,7 +83,7 @@ export default class TagEditorParam extends Component { const { tag, onUpdate, metadata } = this.props; const dimension = ["field-id", fieldId]; if (!_.isEqual(tag.dimension !== dimension)) { - const field = metadata.fields[dimension[1]]; + const field = metadata.field(dimension[1]); if (!field) { return; } @@ -111,7 +111,7 @@ export default class TagEditorParam extends Component { table, fieldMetadataLoaded = false; if (tag.type === "dimension" && Array.isArray(tag.dimension)) { - const field = metadata.fields[tag.dimension[1]]; + const field = metadata.field(tag.dimension[1]); if (field) { widgetOptions = parameterOptionsForField(field); diff --git a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx index 3e97346fd96..d7964205fa7 100644 --- a/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx +++ b/frontend/src/metabase/query_builder/containers/QueryBuilder.jsx @@ -6,8 +6,6 @@ import { connect } from "react-redux"; import { t } from "ttag"; import _ from "underscore"; -import { loadTableAndForeignKeys } from "metabase/lib/table"; - import fitViewport from "metabase/hoc/FitViewPort"; import View from "../components/view/View"; @@ -137,7 +135,6 @@ const mapStateToProps = (state, props) => { questionAlerts: getQuestionAlerts(state), visualizationSettings: getVisualizationSettings(state), - loadTableAndForeignKeysFn: loadTableAndForeignKeys, autocompleteResultsFn: prefix => autocompleteResults(state.qb.card, prefix), instanceSettings: getSettings(state), diff --git a/frontend/src/metabase/reference/utils.js b/frontend/src/metabase/reference/utils.js index cf419ec582a..60b892a2b3b 100644 --- a/frontend/src/metabase/reference/utils.js +++ b/frontend/src/metabase/reference/utils.js @@ -75,8 +75,8 @@ export const getQuestion = ({ ) .updateIn(["display"], display => visualization || display) .updateIn(["dataset_query", "query", "breakout"], oldBreakout => { - if (fieldId && metadata && metadata.fields[fieldId]) { - return [metadata.fields[fieldId].getDefaultBreakout()]; + if (fieldId && metadata && metadata.field(fieldId)) { + return [metadata.field(fieldId).getDefaultBreakout()]; } if (fieldId) { return [["field-id", fieldId]]; diff --git a/frontend/src/metabase/selectors/metadata.js b/frontend/src/metabase/selectors/metadata.js index b76e8052a2e..d5d89d62599 100644 --- a/frontend/src/metabase/selectors/metadata.js +++ b/frontend/src/metabase/selectors/metadata.js @@ -18,10 +18,6 @@ import _ from "underscore"; import { shallowEqual } from "recompose"; import { getFieldValues, getRemappings } from "metabase/lib/query/field"; -import { - getFilterOperators, - getAggregationOperatorsWithFields, -} from "metabase/lib/schema_metadata"; import { getIn } from "icepick"; // fully nomalized, raw "entities" @@ -149,21 +145,9 @@ export const getMetadata = createSelector( } }); - hydrate(meta.fields, "filter_operators", f => - getFilterOperators(f, f.table), - ); - hydrate(meta.tables, "aggregation_operators", t => - getAggregationOperatorsWithFields(t), - ); - hydrate(meta.fields, "values", f => getFieldValues(f)); hydrate(meta.fields, "remapping", f => new Map(getRemappings(f))); - hydrateLookup(meta.databases, "tables", "id"); - hydrateLookup(meta.tables, "fields", "id"); - hydrateLookup(meta.fields, "filter_operators", "name"); - hydrateLookup(meta.tables, "aggregation_operators", "short"); - return meta; }, ); @@ -310,17 +294,6 @@ function hydrateList(objects, property, targetObjects) { ); } -// creates a *_lookup object for a previously hydrated list -function hydrateLookup(objects, property, idProperty = "id") { - hydrate(objects, property + "_lookup", object => { - const lookup = {}; - for (const item of object[property] || []) { - lookup[item[idProperty]] = item; - } - return lookup; - }); -} - function filterValues(obj, pred) { const filtered = {}; for (const [k, v] of Object.entries(obj)) { diff --git a/frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx b/frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx index 26d5c298b1e..34a34cf3794 100644 --- a/frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx +++ b/frontend/src/metabase/visualizations/visualizations/ObjectDetail.jsx @@ -15,7 +15,7 @@ import { foreignKeyCountsByOriginTable, } from "metabase/lib/schema_metadata"; import { TYPE, isa } from "metabase/lib/types"; -import { singularize, inflect } from "inflection"; +import { inflect } from "inflection"; import { formatValue, formatColumn } from "metabase/lib/formatting"; import Tables from "metabase/entities/tables"; @@ -37,8 +37,8 @@ import cx from "classnames"; import _ from "underscore"; import type { VisualizationProps } from "metabase-types/types/Visualization"; -import type { TableMetadata } from "metabase-types/types/Metadata"; import type { FieldId, Field } from "metabase-types/types/Field"; +import type Table from "metabase-lib/lib/metadata/Table"; type ForeignKeyId = number; type ForeignKey = { @@ -56,7 +56,7 @@ type ForeignKeyCountInfo = { }; type Props = VisualizationProps & { - tableMetadata: ?TableMetadata, + table: ?Table, tableForeignKeys: ?(ForeignKey[]), tableForeignKeyReferences: { [id: ForeignKeyId]: ForeignKeyCountInfo }, fetchTableFks: () => void, @@ -68,7 +68,7 @@ type Props = VisualizationProps & { }; const mapStateToProps = state => ({ - tableMetadata: getTableMetadata(state), + table: getTableMetadata(state), tableForeignKeys: getTableForeignKeys(state), tableForeignKeyReferences: getTableForeignKeyReferences(state), }); @@ -99,9 +99,9 @@ export class ObjectDetail extends Component { }; componentDidMount() { - const { tableMetadata } = this.props; - if (tableMetadata && tableMetadata.fks == null) { - this.props.fetchTableFks(tableMetadata.id); + const { table } = this.props; + if (table && table.fks == null) { + this.props.fetchTableFks(table.id); } // load up FK references if (this.props.tableForeignKeys) { @@ -323,13 +323,12 @@ export class ObjectDetail extends Component { }; render() { - if (!this.props.data) { + const { data, table } = this.props; + if (!data) { return false; } - const tableName = this.props.tableMetadata - ? singularize(this.props.tableMetadata.display_name) - : t`Unknown`; + const tableName = table ? table.objectName() : t`Unknown`; // TODO: once we nail down the "title" column of each table this should be something other than the id const idValue = this.getIdValue(); diff --git a/frontend/test/metabase-lib/lib/Dimension.unit.spec.js b/frontend/test/metabase-lib/lib/Dimension.unit.spec.js index dfaa971aaa8..caa52feb2a2 100644 --- a/frontend/test/metabase-lib/lib/Dimension.unit.spec.js +++ b/frontend/test/metabase-lib/lib/Dimension.unit.spec.js @@ -223,7 +223,7 @@ describe("Dimension", () => { it("should return array of FK dimensions for foreign key field dimension", () => { pending(); // Something like this: - // fieldsInProductsTable = metadata.tables[1].fields.length; + // fieldsInProductsTable = metadata.table(1).fields.length; // expect(FKDimension.dimensions(fkFieldIdDimension).length).toEqual(fieldsInProductsTable); }); it("should return empty array for non-FK field dimension", () => { @@ -286,7 +286,7 @@ describe("Dimension", () => { it("should return an array with dimensions for each datetime unit", () => { pending(); // Something like this: - // fieldsInProductsTable = metadata.tables[1].fields.length; + // fieldsInProductsTable = metadata.table(1).fields.length; // expect(FKDimension.dimensions(fkFieldIdDimension).length).toEqual(fieldsInProductsTable); }); it("should return empty array for non-date field dimension", () => { @@ -421,7 +421,7 @@ describe("Dimension", () => { it("should return array of FK dimensions for foreign key field dimension", () => { pending(); // Something like this: - // fieldsInProductsTable = metadata.tables[1].fields.length; + // fieldsInProductsTable = metadata.table(1).fields.length; // expect(FKDimension.dimensions(fkFieldIdDimension).length).toEqual(fieldsInProductsTable); }); it("should return empty array for non-FK field dimension", () => { diff --git a/frontend/test/metabase/containers/SaveQuestionModal.unit.spec.js b/frontend/test/metabase/containers/SaveQuestionModal.unit.spec.js index d7393659789..68b0241d620 100644 --- a/frontend/test/metabase/containers/SaveQuestionModal.unit.spec.js +++ b/frontend/test/metabase/containers/SaveQuestionModal.unit.spec.js @@ -24,7 +24,7 @@ const mountSaveQuestionModal = (question, originalQuestion) => { <SaveQuestionModal card={question.card()} originalCard={originalQuestion && originalQuestion.card()} - tableMetadata={question.tableMetadata()} + tableMetadata={question.table()} onCreate={onCreateMock} onSave={onSaveMock} onClose={() => {}} diff --git a/frontend/test/metabase/lib/expressions/compile.unit.spec.js b/frontend/test/metabase/lib/expressions/compile.unit.spec.js index 2ca70856374..887e5991c23 100644 --- a/frontend/test/metabase/lib/expressions/compile.unit.spec.js +++ b/frontend/test/metabase/lib/expressions/compile.unit.spec.js @@ -6,7 +6,7 @@ import { expressionOpts, } from "./__support__/expressions"; -const ENABLE_PERF_TESTS = !process.env["CI"]; +const ENABLE_PERF_TESTS = false; //!process.env["CI"]; function expectFast(fn, milliseconds = 1000) { const start = Date.now(); diff --git a/frontend/test/metabase/meta/Card.unit.spec.js b/frontend/test/metabase/meta/Card.unit.spec.js index c01fa57e0c4..19a818335fe 100644 --- a/frontend/test/metabase/meta/Card.unit.spec.js +++ b/frontend/test/metabase/meta/Card.unit.spec.js @@ -1,16 +1,24 @@ import * as Card from "metabase/meta/Card"; import { assocIn, dissoc } from "icepick"; +import { getMetadata } from "metabase/selectors/metadata"; describe("metabase/meta/Card", () => { describe("questionUrlWithParameters", () => { - const metadata = { - fields: { - 2: { - base_type: "type/Integer", + const metadata = getMetadata({ + entities: { + databases: {}, + schemas: {}, + tables: {}, + fields: { + 2: { + base_type: "type/Integer", + }, }, + metrics: {}, + segments: {}, }, - }; + }); const parameters = [ { @@ -164,7 +172,7 @@ describe("metabase/meta/Card", () => { card, metadata, parameters, - { "2": "123" }, + { "2": 123 }, parameterMappings, ); expect(parseUrl(url)).toEqual({ diff --git a/frontend/test/metabase/modes/TimeseriesFilterWidget.unit.spec.js b/frontend/test/metabase/modes/TimeseriesFilterWidget.unit.spec.js index 8d0ab14bf09..ad11c3d4032 100644 --- a/frontend/test/metabase/modes/TimeseriesFilterWidget.unit.spec.js +++ b/frontend/test/metabase/modes/TimeseriesFilterWidget.unit.spec.js @@ -13,7 +13,6 @@ import { const getTimeseriesFilterWidget = question => ( <TimeseriesFilterWidget card={question.card()} - tableMetadata={question.tableMetadata()} datasetQuery={question.query().datasetQuery()} setDatasetQuery={() => {}} /> diff --git a/frontend/test/metabase/selectors/metadata.unit.spec.js b/frontend/test/metabase/selectors/metadata.unit.spec.js index fe95d4db5c3..79d7cb2583f 100644 --- a/frontend/test/metabase/selectors/metadata.unit.spec.js +++ b/frontend/test/metabase/selectors/metadata.unit.spec.js @@ -47,7 +47,7 @@ describe("getMetadata", () => { }); it("should have a parent database", () => { - expect(table.database).toEqual(metadata.databases[SAMPLE_DATASET.id]); + expect(table.database).toEqual(metadata.database(SAMPLE_DATASET.id)); }); }); -- GitLab