diff --git a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx index 7d736a34ddfdc4414d07fa767d9d31542ace0caa..dc5fffbe361e03876949ebf23be71b9138f7d7f2 100644 --- a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx +++ b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx @@ -280,7 +280,7 @@ export default class FieldRemapping extends React.Component { dimensions: [], fks: this.getForeignKeys(), }} - tableMetadata={table} + table={table} onFieldChange={this.onForeignKeyFieldChange} hideSectionHeader /> diff --git a/frontend/src/metabase/components/CollectionLanding.jsx b/frontend/src/metabase/components/CollectionLanding.jsx index b9c789e77a59b32bcb22703ceff01fe28ca067bd..a12581ded9c5b74e941fb0022bce14715545dbc2 100644 --- a/frontend/src/metabase/components/CollectionLanding.jsx +++ b/frontend/src/metabase/components/CollectionLanding.jsx @@ -153,7 +153,7 @@ class DefaultLanding extends React.Component { await Promise.all( this.state.selectedItems.map(item => item.setCollection(collection)), ); - this.setState({ selectedItems: null, selectedAction: null }); + this.handleCloseModal(); } finally { this.handleBulkActionSuccess(); } @@ -166,6 +166,10 @@ class DefaultLanding extends React.Component { this.props.onSelectNone(); }; + handleCloseModal = () => { + this.setState({ selectedItems: null, selectedAction: null }); + }; + render() { const { ancestors, @@ -499,14 +503,12 @@ class DefaultLanding extends React.Component { </Box> {!_.isEmpty(selectedItems) && selectedAction == "copy" && ( - <Modal> + <Modal onClose={this.handleCloseModal}> <CollectionCopyEntityModal entityObject={selectedItems[0]} - onClose={() => - this.setState({ selectedItems: null, selectedAction: null }) - } + onClose={this.handleCloseModal} onSaved={newEntityObject => { - this.setState({ selectedItems: null, selectedAction: null }); + this.handleCloseModal(); this.handleBulkActionSuccess(); }} /> @@ -514,16 +516,14 @@ class DefaultLanding extends React.Component { )} {!_.isEmpty(selectedItems) && selectedAction == "move" && ( - <Modal> + <Modal onClose={this.handleCloseModal}> <CollectionMoveModal title={ selectedItems.length > 1 ? t`Move ${selectedItems.length} items?` : t`Move "${selectedItems[0].getName()}"?` } - onClose={() => - this.setState({ selectedItems: null, selectedAction: null }) - } + onClose={this.handleCloseModal} onMove={this.handleBulkMove} /> </Modal> diff --git a/frontend/src/metabase/components/Header.jsx b/frontend/src/metabase/components/Header.jsx index e097f45daf5990f1871797301514c6603e82f8df..d4060974d33201c7ec08a8b23e533abb38f516cd 100644 --- a/frontend/src/metabase/components/Header.jsx +++ b/frontend/src/metabase/components/Header.jsx @@ -30,7 +30,7 @@ export default class Header extends Component { this.updateHeaderHeight(); } - componentWillUpdate() { + componentDidUpdate() { const modalIsOpen = !!this.props.headerModalMessage; if (modalIsOpen) { this.updateHeaderHeight(); diff --git a/frontend/src/metabase/containers/CollectionMoveModal.jsx b/frontend/src/metabase/containers/CollectionMoveModal.jsx index 8792b8b6bbada1f33815c94efaad6c84a3592939..e2b6cc855781a4cbfae4a48bf7888834b39ce6ba 100644 --- a/frontend/src/metabase/containers/CollectionMoveModal.jsx +++ b/frontend/src/metabase/containers/CollectionMoveModal.jsx @@ -3,10 +3,9 @@ import PropTypes from "prop-types"; import { t } from "c-3po"; -import { Flex, Box } from "grid-styled"; -import Subhead from "metabase/components/Subhead"; +import { Flex } from "grid-styled"; import Button from "metabase/components/Button"; -import Icon from "metabase/components/Icon"; +import ModalContent from "metabase/components/ModalContent"; import CollectionPicker from "metabase/containers/CollectionPicker"; @@ -41,15 +40,7 @@ class CollectionMoveModal extends React.Component { const { selectedCollectionId } = this.state; return ( - <Box p={3}> - <Flex align="center" mb={2}> - <Subhead>{this.props.title}</Subhead> - <Icon - name="close" - className="ml-auto" - onClick={() => this.props.onClose()} - /> - </Flex> + <ModalContent title={this.props.title} onClose={this.props.onClose}> <CollectionPicker value={selectedCollectionId} onChange={selectedCollectionId => @@ -78,7 +69,7 @@ class CollectionMoveModal extends React.Component { {t`Move`} </Button> </Flex> - </Box> + </ModalContent> ); } } diff --git a/frontend/src/metabase/dashboard/components/grid/GridLayout.jsx b/frontend/src/metabase/dashboard/components/grid/GridLayout.jsx index 92e6426cbad4a8976333f7b81698caf83db92909..49887e293226adafeb6fc94f74c38d7f8648c3ea 100644 --- a/frontend/src/metabase/dashboard/components/grid/GridLayout.jsx +++ b/frontend/src/metabase/dashboard/components/grid/GridLayout.jsx @@ -228,11 +228,12 @@ export default class GridLayout extends Component { } } + // generate one row of the grid, it will repeat because it's a background image getGridBackground() { - let { margin, cols } = this.props; - let cellSize = this.getCellSize(); - return ( - `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='${cellSize.width * + const { margin, cols } = this.props; + const cellSize = this.getCellSize(); + const svg = + `<svg xmlns='http://www.w3.org/2000/svg' width='${cellSize.width * cols}' height='${cellSize.height}'>` + _(cols) .times( @@ -246,8 +247,8 @@ export default class GridLayout extends Component { )}' height='${cellSize.height - margin - 3}'/>`, ) .join("") + - `</svg>")` - ); + `</svg>`; + return `url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`; } render() { diff --git a/frontend/src/metabase/modes/components/actions/PivotByAction.jsx b/frontend/src/metabase/modes/components/actions/PivotByAction.jsx index 3916a206742668eedf6cc009b06b5266ba691456..2f01016402006c9070fba27f87d102d960cdbc5c 100644 --- a/frontend/src/metabase/modes/components/actions/PivotByAction.jsx +++ b/frontend/src/metabase/modes/components/actions/PivotByAction.jsx @@ -62,7 +62,6 @@ export default (name: string, icon: string, fieldFilter: FieldFilter) => ({ breakoutOptions={breakoutOptions} onCommitBreakout={breakout => { const nextCard = question.pivot([breakout], dimensions).card(); - if (nextCard) { onChangeCardAndRun({ nextCard }); } diff --git a/frontend/src/metabase/query_builder/components/AggregationPopover.jsx b/frontend/src/metabase/query_builder/components/AggregationPopover.jsx index a834d1e479a24d6ad8d2e843d84811ac4eeb8129..f1953b94c762b7ee45fcfa3a0fffa2bd12e332c2 100644 --- a/frontend/src/metabase/query_builder/components/AggregationPopover.jsx +++ b/frontend/src/metabase/query_builder/components/AggregationPopover.jsx @@ -348,7 +348,7 @@ export default class AggregationPopover extends Component { <FieldList className={"text-green"} maxHeight={this.props.maxHeight - (this.state.headerHeight || 0)} - tableMetadata={tableMetadata} + table={tableMetadata} field={fieldId} fieldOptions={query.aggregationFieldOptions(agg)} customFieldOptions={customFields} diff --git a/frontend/src/metabase/query_builder/components/BreakoutPopover.jsx b/frontend/src/metabase/query_builder/components/BreakoutPopover.jsx index 5570f0325b7245d1ae4ba1dd29b80595fd2f1519..11ebc48250a1f279e499e56a19c32b19a323f860 100644 --- a/frontend/src/metabase/query_builder/components/BreakoutPopover.jsx +++ b/frontend/src/metabase/query_builder/components/BreakoutPopover.jsx @@ -26,22 +26,29 @@ const BreakoutPopover = ({ onClose, maxHeight, alwaysExpanded, -}: Props) => ( - <FieldList - className="text-green" - maxHeight={maxHeight} - field={breakout} - fieldOptions={breakoutOptions || query.breakoutOptions()} - onFieldChange={field => { - onCommitBreakout(field); - if (onClose) { - onClose(); - } - }} - tableMetadata={query.tableMetdata()} - enableSubDimensions - alwaysExpanded={alwaysExpanded} - /> -); +}: Props) => { + const table = query.table(); + // FieldList requires table + if (!table) { + return null; + } + return ( + <FieldList + className="text-green" + maxHeight={maxHeight} + field={breakout} + fieldOptions={breakoutOptions || query.breakoutOptions()} + onFieldChange={field => { + onCommitBreakout(field); + if (onClose) { + onClose(); + } + }} + table={table} + enableSubDimensions + alwaysExpanded={alwaysExpanded} + /> + ); +}; export default BreakoutPopover; diff --git a/frontend/src/metabase/query_builder/components/BreakoutWidget.jsx b/frontend/src/metabase/query_builder/components/BreakoutWidget.jsx index 647ccabf2f6f9ce96d0624092620e5e0cab93f66..5055b3aa4a41d49a35f942bebc82d4099b9506ca 100644 --- a/frontend/src/metabase/query_builder/components/BreakoutWidget.jsx +++ b/frontend/src/metabase/query_builder/components/BreakoutWidget.jsx @@ -58,7 +58,7 @@ export default class BreakoutWidget extends Component { > <FieldList className={"text-green"} - tableMetadata={this.props.tableMetadata} + table={this.props.tableMetadata} field={this.props.field} fieldOptions={this.props.fieldOptions} customFieldOptions={this.props.customFieldOptions} diff --git a/frontend/src/metabase/query_builder/components/FieldList.jsx b/frontend/src/metabase/query_builder/components/FieldList.jsx index 325c4bf5e05271debb44f51f73f5c5621d6b4658..4fccc60588a56d4cec9882cecb8f5eaec5fa9660 100644 --- a/frontend/src/metabase/query_builder/components/FieldList.jsx +++ b/frontend/src/metabase/query_builder/components/FieldList.jsx @@ -35,7 +35,7 @@ type Props = { // HACK: for segments onFilterChange?: (filter: any) => void, - tableMetadata: Table, + table: Table, alwaysExpanded?: boolean, enableSubDimensions?: boolean, @@ -61,13 +61,8 @@ export default class FieldList extends Component { } componentWillReceiveProps(newProps) { - let { - tableMetadata, - fieldOptions, - segmentOptions, - hideSectionHeader, - } = newProps; - let tableName = tableMetadata.display_name; + let { table, fieldOptions, segmentOptions, hideSectionHeader } = newProps; + let tableName = table.display_name; let specialOptions = []; if (segmentOptions) { @@ -110,11 +105,7 @@ export default class FieldList extends Component { }; renderItemExtra = item => { - const { - field, - enableSubDimensions, - tableMetadata: { metadata }, - } = this.props; + const { field, enableSubDimensions, table: { metadata } } = this.props; return ( <div className="Field-extra flex align-center"> @@ -167,7 +158,7 @@ export default class FieldList extends Component { }; renderSubDimensionTrigger(dimension) { - const { field, tableMetadata: { metadata } } = this.props; + const { field, table: { metadata } } = this.props; const subDimension = dimension.isSameBaseDimension(field) ? Dimension.parseMBQL(field, metadata) : dimension.defaultDimension(); @@ -181,16 +172,11 @@ export default class FieldList extends Component { } renderSegmentTooltip(segment) { - let { tableMetadata } = this.props; + let { table } = this.props; return ( <div className="p1"> <Tooltip - tooltip={ - <QueryDefinitionTooltip - object={segment} - tableMetadata={tableMetadata} - /> - } + tooltip={<QueryDefinitionTooltip object={segment} table={table} />} > <span className="QuestionTooltipTarget" /> </Tooltip> diff --git a/frontend/src/metabase/query_builder/components/FieldWidget.jsx b/frontend/src/metabase/query_builder/components/FieldWidget.jsx index f724c3795ce1474f80a13aeb775e2965b7be0580..8392c9e0fdb9d5686f7ef2467a34dd89ee63dc93 100644 --- a/frontend/src/metabase/query_builder/components/FieldWidget.jsx +++ b/frontend/src/metabase/query_builder/components/FieldWidget.jsx @@ -56,7 +56,7 @@ export default class FieldWidget extends Component { <Popover ref="popover" className="FieldPopover" onClose={this.toggle}> <FieldList className={"text-" + this.props.color} - tableMetadata={this.props.tableMetadata} + table={this.props.tableMetadata} field={this.props.field} fieldOptions={this.props.fieldOptions} customFieldOptions={this.props.customFieldOptions} diff --git a/frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx b/frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx index 8b0fa84cc486a8a18e653ecd421a52d8ab59eb37..43f3466e563d3fa1fe2d6a0924d2bc33b62003f2 100644 --- a/frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx +++ b/frontend/src/metabase/query_builder/components/filters/FilterPopover.jsx @@ -319,7 +319,7 @@ export default class FilterPopover extends Component { field={fieldRef} fieldOptions={query.filterFieldOptions(filter)} segmentOptions={query.filterSegmentOptions(filter)} - tableMetadata={query.table()} + table={query.table()} onFieldChange={this.setField} onFilterChange={this.commitFilter} /> diff --git a/frontend/src/metabase/questions/components/CollectionBadge.jsx b/frontend/src/metabase/questions/components/CollectionBadge.jsx index c8f84a6623bd3755873b7ef3b609960cc027e747..33bdbaa4d60d55e40e088b4d19b957b1d7e35015 100644 --- a/frontend/src/metabase/questions/components/CollectionBadge.jsx +++ b/frontend/src/metabase/questions/components/CollectionBadge.jsx @@ -13,14 +13,19 @@ import cx from "classnames"; entityType: "collections", entityId: (state, props) => props.collectionId || "root", wrapped: true, + loadingAndErrorWrapper: false, + properties: ["name"], }) class CollectionBadge extends React.Component { render() { - const { analyticsContext, object } = this.props; + const { analyticsContext, object, className } = this.props; + if (!object) { + return null; + } return ( <Link to={Urls.collection(object.id)} - className={cx("inline-block link")} + className={cx(className, "block link")} data-metabase-event={`${analyticsContext};Collection Badge Click`} > <Flex align="center"> diff --git a/frontend/test/metabase/modes/components/actions/PivotByAction.unit.spec.js b/frontend/test/metabase/modes/components/actions/PivotByAction.unit.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..598c042db6bbecaac9d05f6b0f8abffb316a939f --- /dev/null +++ b/frontend/test/metabase/modes/components/actions/PivotByAction.unit.spec.js @@ -0,0 +1,51 @@ +/* eslint-disable flowtype/require-valid-file-annotation */ + +import React from "react"; + +import { question } from "__support__/sample_dataset_fixture"; + +import { mount } from "enzyme"; +import { click } from "__support__/enzyme_utils"; + +import PivotByAction from "metabase/modes/components/actions/PivotByAction"; + +describe("PivotByAction", () => { + it("should return a broken out card", () => { + const TestPivotByAction = PivotByAction("Test", "test", () => true); + + const countQuestion = question + .query() + .addAggregation(["count"]) + .question() + .setDisplay("scalar"); + + const actions = TestPivotByAction({ question: countQuestion }); + expect(actions).toHaveLength(1); + + const PopoverComponent = actions[0].popover; + + const onChangeCardAndRun = jest.fn(); + const wrapper = mount( + <PopoverComponent onChangeCardAndRun={onChangeCardAndRun} />, + ); + + click(wrapper.find(".List-item a").first()); + + expect(onChangeCardAndRun).toHaveBeenLastCalledWith({ + nextCard: { + dataset_query: { + database: 1, + query: { + aggregation: [["count"]], + breakout: [["datetime-field", ["field-id", 1], "day"]], + "source-table": 1, + }, + type: "query", + }, + display: "line", + name: null, + visualization_settings: {}, + }, + }); + }); +}); diff --git a/frontend/test/metabase/query_builder/components/FieldList.e2e.spec.js b/frontend/test/metabase/query_builder/components/FieldList.e2e.spec.js index d36cf498f30b06a4512efae5374278301410f080..70aa5db39ec5fbdb93949700bbfbaeaa71744ee8 100644 --- a/frontend/test/metabase/query_builder/components/FieldList.e2e.spec.js +++ b/frontend/test/metabase/query_builder/components/FieldList.e2e.spec.js @@ -32,7 +32,7 @@ import Filter from "metabase/query_builder/components/Filter"; const getFieldList = (query, fieldOptions, segmentOptions) => ( <FieldList - tableMetadata={query.tableMetadata()} + table={query.table()} fieldOptions={fieldOptions} segmentOptions={segmentOptions} customFieldOptions={query.expressions()} diff --git a/modules/drivers/bigquery/src/metabase/driver/bigquery.clj b/modules/drivers/bigquery/src/metabase/driver/bigquery.clj index a7aa9d5e0ece735d243ebb7730aa24ddf21dfd41..e86fa902f1cd64fd848747cec80c45626a178de6 100644 --- a/modules/drivers/bigquery/src/metabase/driver/bigquery.clj +++ b/modules/drivers/bigquery/src/metabase/driver/bigquery.clj @@ -147,6 +147,7 @@ "DATETIME" :type/DateTime "TIMESTAMP" :type/DateTime "TIME" :type/Time + "NUMERIC" :type/Decimal :type/*)) (defn- table-schema->metabase-field-info [^TableSchema schema] @@ -217,6 +218,7 @@ {"BOOLEAN" (constantly #(Boolean/parseBoolean %)) "FLOAT" (constantly #(Double/parseDouble %)) "INTEGER" (constantly #(Long/parseLong %)) + "NUMERIC" (constantly #(bigdec %)) "RECORD" (constantly identity) "STRING" (constantly identity) "DATE" parse-timestamp-str diff --git a/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj b/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj index 0f2d0721a32ae5075a18ca65c92f0b30df8c00ab..8e817cb3a725e0be2f02c5ebfe9a7eab9099cc1f 100644 --- a/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj +++ b/modules/drivers/bigquery/test/metabase/test/data/bigquery.clj @@ -137,7 +137,7 @@ :type/Date :TIMESTAMP :type/DateTime :TIMESTAMP :type/DateTimeWithTZ :TIMESTAMP - :type/Decimal :FLOAT + :type/Decimal :NUMERIC :type/Dictionary :RECORD :type/Float :FLOAT :type/Integer :INTEGER diff --git a/src/metabase/api/activity.clj b/src/metabase/api/activity.clj index d27a9c5dee0976628a8d578853df5eba5d14b618..187eca09980338ec448a6ded46d715f03ca8dd3c 100644 --- a/src/metabase/api/activity.clj +++ b/src/metabase/api/activity.clj @@ -13,8 +13,8 @@ [hydrate :refer [hydrate]]])) (defn- dashcard-activity? [activity] - (contains? #{:dashboard-add-cards :dashboard-remove-cards} - (:topic activity))) + (#{:dashboard-add-cards :dashboard-remove-cards} + (:topic activity))) (defn- activities->referenced-objects "Get a map of model name to a set of referenced IDs in these ACTIVITIES. diff --git a/src/metabase/automagic_dashboards/core.clj b/src/metabase/automagic_dashboards/core.clj index 2a19fbf7ef52d643acc04d996998dc516553c3f0..73f9b2ca4e943884a27148c7481c84a5a9b7ba50 100644 --- a/src/metabase/automagic_dashboards/core.clj +++ b/src/metabase/automagic_dashboards/core.clj @@ -1230,6 +1230,7 @@ :group-by [:table_id] :having [:= :%count.* 1]})) (into #{} (map :table_id))) + ;; Table comprised entierly of join keys link-table? (->> (db/query {:select [:table_id [:%count.* "count"]] :from [Field] :where [:and [:in :table_id (keys field-count)] @@ -1269,7 +1270,7 @@ schema (concat [:schema schema]))) (filter mi/can-read?) enhance-table-stats - (remove (comp (some-fn :link-table? :list-like? (comp zero? :num-fields)) :stats)) + (remove (comp (some-fn :link-table? (comp zero? :num-fields)) :stats)) (map (fn [table] (let [root (->root table) rule (->> root @@ -1279,7 +1280,10 @@ {:url (format "%stable/%s" public-endpoint (u/get-id table)) :title (:full-name root) :score (+ (math/sq (:specificity rule)) - (math/log (-> table :stats :num-fields))) + (math/log (-> table :stats :num-fields)) + (if (-> table :stats :list-like?) + -10 + 0)) :description (:description dashboard) :table table :rule (:rule rule)}))) diff --git a/src/metabase/events/activity_feed.clj b/src/metabase/events/activity_feed.clj index aad5306c961b1fede7ab226e31979af27c6a06e1..228c76dacefe25411b6cc653e49598cde04f18d3 100644 --- a/src/metabase/events/activity_feed.clj +++ b/src/metabase/events/activity_feed.clj @@ -15,7 +15,7 @@ [toucan.db :as db])) (def ^:private activity-feed-topics - "The `Set` of event topics which are subscribed to for use in the Metabase activity feed." + "The set of event topics which are subscribed to for use in the Metabase activity feed." #{:alert-create :alert-delete :card-create @@ -34,7 +34,7 @@ :segment-create :segment-update :segment-delete - :user-login}) + :user-login}) ; this is only used these days the first time someone logs in to record 'user-joined' events (def ^:private activity-feed-channel "Channel for receiving event notifications we want to subscribe to for the activity feed." diff --git a/src/metabase/models/activity.clj b/src/metabase/models/activity.clj index db2bf1da1807dae091fb16de150eef5fdd14dafa..c88a0e8cb79451273c6cb0d7ee79cd8236ecb999 100644 --- a/src/metabase/models/activity.clj +++ b/src/metabase/models/activity.clj @@ -2,6 +2,7 @@ (:require [metabase [events :as events] [util :as u]] + [metabase.api.common :as api] [metabase.models [card :refer [Card]] [dashboard :refer [Dashboard]] @@ -23,10 +24,26 @@ "pulse" Pulse "segment" Segment}) -(defn- can-? [f {model :model, model-id :model_id, :as activity}] +(defmulti can-? + "Implementation for `can-read?`/`can-write?` for items in the activity feed. Dispatches off of the activity `:topic`, + e.g. `:user-joined`. `perms-check-fn` is `can-read?` or `can-write?` and should be called as needed on models the + activity records." + {:arglists '([perms-check-fn activity])} + (fn [_ {:keys [topic]}] + topic)) + +;; For now only admins can see when another user joined -- we don't want every user knowing about every other user. In +;; the future we might want to change this and come up with some sort of system where we can determine which users get +;; to see other users -- perhaps if they are in a group together other than 'All Users' +(defmethod can-? :user-joined [_ _] + api/*is-superuser?*) + +;; For every other activity topic we'll look at the read/write perms for the object the activty is about (e.g. a Card +;; or Dashboard). For all other activity feed items with no model everyone can read/write +(defmethod can-? :default [perms-check-fn {model :model, model-id :model_id, :as activity}] (if-let [object (when-let [entity (model->entity model)] (entity model-id))] - (f object) + (perms-check-fn object) true)) @@ -47,6 +64,7 @@ i/IObjectPermissions (merge i/IObjectPermissionsDefaults {:can-read? (partial can-? i/can-read?) + ;; TODO - when do people *write* activities? :can-write? (partial can-? i/can-write?)})) diff --git a/src/metabase/sync/analyze/classifiers/category.clj b/src/metabase/sync/analyze/classifiers/category.clj index 92a59f93209e59289c17d65ca7fe2c4f40b526f5..6548238d41c9bda4e83fe590c4072331258108d9 100644 --- a/src/metabase/sync/analyze/classifiers/category.clj +++ b/src/metabase/sync/analyze/classifiers/category.clj @@ -23,8 +23,9 @@ (defn- cannot-be-category-or-list? [{:keys [base_type special_type]}] - (or (isa? base_type :type/DateTime) - (isa? base_type :type/Collection) + (or (isa? base_type :type/DateTime) + (isa? base_type :type/Collection) + (isa? base_type :type/Float) ;; Don't let IDs become list Fields (they already can't become categories, because they already have a special ;; type). It just doesn't make sense to cache a sequence of numbers since they aren't inherently meaningful (isa? special_type :type/PK) diff --git a/src/metabase/sync/analyze/fingerprint/insights.clj b/src/metabase/sync/analyze/fingerprint/insights.clj index 2e6e4d2e0bf3d89ffea26ec455535e14c0d0debe..8285f270577de65fa67587c364f29c1a54d3fc1e 100644 --- a/src/metabase/sync/analyze/fingerprint/insights.clj +++ b/src/metabase/sync/analyze/fingerprint/insights.clj @@ -1,6 +1,9 @@ (ns metabase.sync.analyze.fingerprint.insights "Deeper statistical analysis of results." - (:require [kixi.stats + (:require [clj-time + [coerce :as t.coerce] + [core :as t]] + [kixi.stats [core :as stats] [math :as math]] [metabase.models.field :as field] @@ -171,7 +174,7 @@ ;; unit=year workaround. While the field is in this case marked as :type/Text, ;; at this stage in the pipeline the value is still an int, so we can use it ;; directly. - (comp (stats/somef ms->day) #(nth % x-position)))] + #(some-> % (nth x-position) t/date-time t.coerce/to-long ms->day))] (apply redux/juxt (for [number-col numbers] (redux/post-complete diff --git a/test/metabase/api/activity_test.clj b/test/metabase/api/activity_test.clj index f5bd666940bb601b5c67c575625eaeecaa79bf6b..72497b5e1f071f3b2ad02842e4ab6c5868f2fde3 100644 --- a/test/metabase/api/activity_test.clj +++ b/test/metabase/api/activity_test.clj @@ -97,8 +97,8 @@ :details $})] ;; clear any other activities from the DB just in case; not sure this step is needed any more (do (db/delete! Activity :id [:not-in #{(:id activity1) - (:id activity2) - (:id activity3)}]) + (:id activity2) + (:id activity3)}]) (for [activity ((user->client :crowberto) :get 200 "activity")] (dissoc activity :timestamp)))) @@ -211,3 +211,17 @@ {:model "card", :model_id 0} {:model "dashboard", :model_id 0, :topic :dashboard-remove-cards, :details {:dashcards [{:card_id card-id} {:card_id 0}]}}])) + +;; Only admins should get to see user-joined activities +(defn- user-can-see-user-joined-activity? [user] + ;; clear out all existing Activity entries + (db/delete! Activity) + (-> (tt/with-temp Activity [activity {:topic "user-joined" + :details {} + :timestamp (du/->Timestamp #inst "2019-02-15T11:55:00.000Z")}] + ((user->client user) :get 200 "activity")) + seq + boolean)) + +(expect true (user-can-see-user-joined-activity? :crowberto)) +(expect false (user-can-see-user-joined-activity? :rasta)) diff --git a/test/metabase/automagic_dashboards/core_test.clj b/test/metabase/automagic_dashboards/core_test.clj index 6ca8193f7fc1e114ec845aeaa8f582810ff970fc..62840668b83bf8ec35fada26047e606c68d881cc 100644 --- a/test/metabase/automagic_dashboards/core_test.clj +++ b/test/metabase/automagic_dashboards/core_test.clj @@ -319,7 +319,7 @@ ;;; ------------------- /candidates ------------------- (expect - 3 + 4 (with-rasta (->> (Database (data/id)) candidate-tables first :tables count)))