Skip to content
Snippets Groups Projects
Commit 48c989ef authored by Allen Gilliland's avatar Allen Gilliland
Browse files

Merge pull request #996 from metabase/new_filters

New filter widget, relative time filters, and time groupings
parents 2247337e 29c3ee77
No related branches found
No related tags found
No related merge requests found
Showing
with 947 additions and 424 deletions
......@@ -2,7 +2,7 @@
"parser": "babel-eslint",
"rules": {
"no-undef": 2,
"no-unused-vars": [2, {"vars": "all", "args": "none", "varsIgnorePattern": "React|PropTypes|Component"}],
"no-unused-vars": [1, {"vars": "all", "args": "none", "varsIgnorePattern": "React|PropTypes|Component"}],
"quotes": 0,
"camelcase": 0,
"eqeqeq": 0,
......
......@@ -13,7 +13,6 @@ var Metabase = angular.module('metabase', [
'metabase.components',
'metabase.card',
'metabase.dashboard',
'metabase.explore',
'metabase.home',
'metabase.user',
'metabase.setup',
......
......@@ -4,6 +4,7 @@ import _ from "underscore";
import MetabaseAnalytics from '../lib/analytics';
import DataGrid from "metabase/lib/data_grid";
import { addValidOperatorsToFields } from "metabase/lib/schema_metadata";
import DataReference from '../query_builder/DataReference.react';
import GuiQueryEditor from '../query_builder/GuiQueryEditor.react';
......@@ -73,8 +74,8 @@ CardControllers.controller('CardList', ['$scope', '$location', 'Card', function(
}]);
CardControllers.controller('CardDetail', [
'$rootScope', '$scope', '$route', '$routeParams', '$location', '$q', '$window', '$timeout', 'Card', 'Dashboard', 'MetabaseFormGenerator', 'Metabase', 'VisualizationSettings', 'QueryUtils', 'Revision',
function($rootScope, $scope, $route, $routeParams, $location, $q, $window, $timeout, Card, Dashboard, MetabaseFormGenerator, Metabase, VisualizationSettings, QueryUtils, Revision) {
'$rootScope', '$scope', '$route', '$routeParams', '$location', '$q', '$window', '$timeout', 'Card', 'Dashboard', 'Metabase', 'VisualizationSettings', 'QueryUtils', 'Revision',
function($rootScope, $scope, $route, $routeParams, $location, $q, $window, $timeout, Card, Dashboard, Metabase, VisualizationSettings, QueryUtils, Revision) {
// promise helper
$q.resolve = function(object) {
var deferred = $q.defer();
......@@ -188,7 +189,6 @@ CardControllers.controller('CardDetail', [
isRunning: false,
isShowingDataReference: null,
databases: null,
tables: null,
tableMetadata: null,
tableForeignKeys: null,
query: null,
......@@ -377,7 +377,6 @@ CardControllers.controller('CardDetail', [
editorModel.isRunning = isRunning;
editorModel.isShowingDataReference = $scope.isShowingDataReference;
editorModel.databases = databases;
editorModel.tables = tables;
editorModel.tableMetadata = tableMetadata;
editorModel.tableForeignKeys = tableForeignKeys;
editorModel.query = card.dataset_query;
......@@ -696,7 +695,7 @@ CardControllers.controller('CardDetail', [
}
function markupTableMetadata(table) {
var updatedTable = MetabaseFormGenerator.addValidOperatorsToFields(table);
var updatedTable = addValidOperatorsToFields(table);
return QueryUtils.populateQueryOptions(updatedTable);
}
......@@ -931,7 +930,15 @@ CardControllers.controller('CardDetail', [
// TODO: while we wait for the databases list we should put something on screen
// grab our database list, then handle the rest
Metabase.db_list(function (dbs) {
async function loadDatabasesAndTables() {
let dbs = await Metabase.db_list().$promise;
return await * dbs.map(async function(db) {
db.tables = await Metabase.db_tables({ dbId: db.id }).$promise;
return db;
});
}
loadDatabasesAndTables().then(function(dbs) {
databases = dbs;
if (dbs.length < 1) {
......
'use strict';
import React, { Component, PropTypes } from 'react';
import Icon from 'metabase/components/Icon.react';
export default class CheckBox extends Component {
onClick() {
if (this.props.onChange) {
// TODO: use a proper event object?
this.props.onChange({ target: { checked: !this.props.checked }})
}
}
render() {
const { checked } = this.props;
const style = {
width: '1rem',
height: '1rem',
border: '2px solid #ddd',
borderRadius: '4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
};
return (
<div style={style} onClick={() => this.onClick()}>
{ checked ? <Icon name='check' width={10} height={10} /> : null }
</div>
)
}
}
CheckBox.propTypes = {
checked: PropTypes.bool,
onChange: PropTypes.func
};
......@@ -47,6 +47,15 @@
font-size: 0.6rem;
}
.Button--medium {
padding: 0.5rem 1rem;
font-size: 0.8rem;
}
.Button-normal {
font-weight: normal;
}
.Button--primary {
color: #fff;
background: var(--primary-button-bg-color);
......@@ -69,6 +78,12 @@
border-color: color(var(--base-grey) shade(30%));
}
.Button--purple {
color: white;
background-color: #A989C5;
border: 1px solid #885AB1;
}
.Button-group {
display: inline-block;
border-radius: var(--default-button-border-radius);
......
......@@ -6,14 +6,25 @@
display: flex;
}
.Calendar-day {
.Calendar-day,
.Calendar-day-name {
flex: 1;
padding: 0.75em;
margin: 0.25em;
text-align: center;
}
.Calendar-day {
color: color(var(--base-grey) shade(30%));
border-radius: 99px;
cursor: pointer;
position: relative;
border: 1px solid color(var(--base-grey) shade(20%) alpha(-50%));
border-radius: 0;
border-bottom-width: 0;
border-right-width: 0;
}
.Calendar-day:last-child {
border-right-width: 1px;
}
.Calendar-week:last-child .Calendar-day {
border-bottom-width: 1px;
}
.Calendar-day-name {
......@@ -36,7 +47,49 @@
color: inherit !important;
}
.Calendar-day--selected {
.Calendar-day--selected,
.Calendar-day--selected-end {
color: white !important;
background-color: var(--purple-light-color);
}
.Calendar-day--selected,
.Calendar-day--selected-end {
background-color: var(--purple-color);
}
.Calendar-day--in-range {
background-color: #E3DAEB;
}
.Calendar-day--selected:after,
.Calendar-day--selected-end:after,
.Calendar-day--in-range:after {
content: "";
position: absolute;
top: -2px;
bottom: -1px;
left: -2px;
right: -2px;
border: 2px solid color(var(--purple-color) shade(25%));
border-radius: 4px;
z-index: 2;
}
.Calendar-day--in-range:after {
border-left-color: transparent;
border-right-color: transparent;
border-radius: 0px;
}
.Calendar-day--week-start.Calendar-day--in-range:after {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
border-left-color: color(var(--purple-color) shade(25%));
}
.Calendar-day--week-end.Calendar-day--in-range:after {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-right-color: color(var(--purple-color) shade(25%));
}
......@@ -62,6 +62,18 @@
border-color: rgba(0,0,0,0.2) !important;
}
.border-purple {
border-color: var(--purple-color) !important;
}
.border-error {
border-color: var(--error-color) !important;
}
.border-hover:hover {
border-color: color(var(--border-color) shade(20%));
}
/* BORDERLESS IS THE DEFAULT */
/* ONLY USE IF needing to override an existing border! */
/* ensure there is no border via important */
......
......@@ -98,6 +98,7 @@
.bg-gold { background-color: var(--gold-color); }
.bg-purple { background-color: var(--purple-color); }
.bg-purple-light { background-color: var(--purple-light-color); }
.bg-green { background-color: var(--green-color); }
/* alt */
......
......@@ -98,3 +98,7 @@
.text-current {
color: currentColor;
}
.text-underline {
text-decoration: underline;;
}
......@@ -76,13 +76,13 @@
overflow-x: scroll;
max-width: 400px;
white-space: nowrap;
height: 55px;
}
.Query-filter {
display: flex;
align-items: center;
font-size: 0.75em;
height: 56px;
border: 2px solid transparent;
border-radius: var(--default-border-radius);
}
......@@ -285,7 +285,7 @@
.GuiBuilder-section {
position: relative;
min-height: 48px;
min-height: 55px;
min-width: 120px;
}
......@@ -323,30 +323,30 @@
/* FILTER BY SECTION */
.Filter-section-field,
.Filter-section-operator,
.Filter-section-value {
.Filter-section-operator {
color: var(--purple-color);
}
.Filter-section-field.selected .QueryOption {
.Filter-section-field .QueryOption {
color: var(--purple-color);
}
.Filter-section-operator.selected .QueryOption {
.Filter-section-operator .QueryOption {
color: var(--purple-color);
text-transform: lowercase;
}
.Filter-section-value.selected .QueryOption {
color: var(--purple-color);
.Filter-section-value .QueryOption {
color: white;
background-color: var(--purple-color);
border: 1px solid color(var(--purple-color) shade(30%));
border-radius: 6px;
padding: 0.5em;
padding-top: 0.3em;
padding-bottom: 0.3em;
margin-bottom: 0.2em;
}
/* put quotes around numeric or text values */
.Filter-section-value .QueryOption.QueryOption--text:before,
.Filter-section-value .QueryOption.QueryOption--text:after,
.Filter-section-value .QueryOption.QueryOption--number:before,
.Filter-section-value .QueryOption.QueryOption--number:after,
.Filter-section-value .QueryOption.QueryOption--select:before,
.Filter-section-value .QueryOption.QueryOption--select:after {
content: '"';
.Filter-section-value {
padding-right: 0.5em;
}
.Filter-section-sort-field.selected .QueryOption,
......@@ -354,10 +354,6 @@
color: inherit;
}
.Filter-section-value {
padding-right: 0.5em;
}
.FilterPopover .ColumnarSelector-row--selected,
.FilterPopover .PopoverHeader-item.selected {
color: var(--purple-color) !important;
......@@ -366,23 +362,6 @@
background-color: var(--purple-color) !important;
}
.Filter-section-value .Button,
.Filter-section-value .input {
padding: 0.5rem;
}
.Filter-section-value .input {
font-size: inherit;
border: 1px solid var(--border-color);
border-radius: 4px;
}
.Filter-section-value .input:focus {
outline: none;
border-color: var(--purple-color);
box-shadow: 0 0 2px var(--purple-color);
}
/* VIEW SECTION */
.View-section-aggregation,
......@@ -562,3 +541,54 @@
font-weight: bold;
border-right: 1px solid color(var(--base-grey) shade(40%));
}
.List-item {
display: flex;
border-radius: 6px;
border: 2px solid transparent;
}
.List-section-header .Icon,
.List-item .Icon {
color: #ddd;
}
.List-item:hover,
.List-item--selected {
background-color: currentColor;
border-color: rgba(0,0,0,0.2);
/*color: white;*/
}
.List-item-title {
color: var(--default-font-color);
}
.List-item:hover .List-item-title,
.List-item--selected .List-item-title {
color: white;
}
.List-section-header {
color: var(--default-font-color);
}
.List-section-header:hover {
color: currentColor;
}
.List-item:hover .Icon,
.List-item--selected .Icon {
color: white;
}
.FieldList-grouping-trigger {
display: none;
}
.List-item:hover .FieldList-grouping-trigger,
.List-item--selected .FieldList-grouping-trigger {
display: flex;
border-left: 2px solid rgba(0,0,0,0.1);
color: rgba(255,255,255,0.5);
}
'use strict';
// Explore (Metabase)
angular.module('metabase.explore', [
'metabase.explore.services'
]);
'use strict';
import _ from "underscore";
import SchemaMetadata from "metabase/lib/schema_metadata";
var ExploreServices = angular.module('metabase.explore.services', []);
ExploreServices.service('MetabaseFormGenerator', [function() {
// Valid Operators per field
function isDate(field) {
return SchemaMetadata.isDateType(field);
}
function isNumber(field) {
return SchemaMetadata.isNumericType(field);
}
function isSummable(field) {
return SchemaMetadata.isSummableType(field);
}
function isCategory(field) {
return SchemaMetadata.isCategoryType(field);
}
function isDimension(field) {
return SchemaMetadata.isDimension(field);
}
function freeformArgument(field, table) {
return {
'type': "text"
};
}
function numberArgument(field, table) {
return {
'type': "number"
};
}
function comparableArgument(field, table) {
var inputType = "text";
if (isNumber(field)) {
inputType = "number";
}
if (isDate(field)) {
inputType = "date";
}
return {
'type': inputType
};
}
function equivalentArgument(field, table) {
var input_type = "text";
if (isDate(field)) {
input_type = "date";
}
if (isNumber(field)) {
input_type = "number";
}
if (isCategory(field)) {
// DON'T UNDERSTAND WHY I HAVE TO DO THIS (!)
if (!table.field_values) {
table.field_values = {};
for (var fld in table.fields) {
table.field_values[fld.id] = fld.display_name; // ???
}
}
if (field.id in table.field_values && table.field_values[field.id].length > 0) {
var valid_values = table.field_values[field.id];
valid_values.sort();
return {
"type": "select",
'values': _.map(valid_values, function(value) {
return {
'key': value,
'name': value
};
})
};
}
}
return {
'type': input_type
};
}
function longitudeFieldSelectArgument(field, table) {
var longitudeFields = _.filter(table.fields, function(field) {
return field.special_type == "longitude";
});
var validValues = _.map(longitudeFields, function(field) {
return {
'key': field.id,
'name': field.display_name
};
});
return {
"values": validValues,
"type": "select"
};
}
var FilterOperators = {
'IS': {
'name': "=",
'verbose_name': "Is",
'validArgumentsFilters': [equivalentArgument]
},
'IS_NOT': {
'name': "!=",
'verbose_name': "Is Not",
'validArgumentsFilters': [equivalentArgument]
},
'IS_NULL': {
'name': "IS_NULL",
'verbose_name': "Is Null",
'validArgumentsFilters': []
},
'IS_NOT_NULL': {
'name': "NOT_NULL",
'verbose_name': "Is Not Null",
'validArgumentsFilters': []
},
'LESS_THAN': {
'name': "<",
'verbose_name': "Less Than",
'validArgumentsFilters': [comparableArgument]
},
'LESS_THAN_OR_EQUAL': {
'name': "<=",
'verbose_name': "Less Than or Equal To",
'validArgumentsFilters': [comparableArgument]
},
'GREATER_THAN': {
'name': ">",
'verbose_name': "Greater Than",
'validArgumentsFilters': [comparableArgument]
},
'GREATER_THAN_OR_EQUAL': {
'name': ">=",
'verbose_name': "Greater Than or Equal To",
'validArgumentsFilters': [comparableArgument]
},
'INSIDE': {
'name': "INSIDE",
'verbose_name': "Inside - (Lat,Long) for upper left, (Lat,Long) for lower right",
'validArgumentsFilters': [longitudeFieldSelectArgument, numberArgument, numberArgument, numberArgument, numberArgument]
},
'BETWEEN': {
'name': "BETWEEN",
'verbose_name': "Between - Min, Max",
'validArgumentsFilters': [comparableArgument, comparableArgument]
},
'STARTS_WITH': {
'name': "STARTS_WITH",
'verbose_name': "Starts With",
'validArgumentsFilters': [freeformArgument]
},
'ENDS_WITH': {
'name': "ENDS_WITH",
'verbose_name': "Ends With",
'validArgumentsFilters': [freeformArgument]
},
'CONTAINS': {
'name': "CONTAINS",
'verbose_name': "Contains",
'validArgumentsFilters': [freeformArgument]
}
};
var BaseOperators = ['IS', 'IS_NOT', 'IS_NULL', 'IS_NOT_NULL'];
var AdditionalOperators = {
'CharField': ['STARTS_WITH', 'ENDS_WITH', 'CONTAINS'],
'TextField': ['STARTS_WITH', 'ENDS_WITH', 'CONTAINS'],
'IntegerField': ['LESS_THAN', 'LESS_THAN_OR_EQUAL', 'GREATER_THAN', 'GREATER_THAN_OR_EQUAL', 'BETWEEN'],
'BigIntegerField': ['LESS_THAN', 'LESS_THAN_OR_EQUAL', 'GREATER_THAN', 'GREATER_THAN_OR_EQUAL', 'BETWEEN'],
'DecimalField': ['LESS_THAN', 'LESS_THAN_OR_EQUAL', 'GREATER_THAN', 'GREATER_THAN_OR_EQUAL', 'BETWEEN'],
'FloatField': ['LESS_THAN', 'LESS_THAN_OR_EQUAL', 'GREATER_THAN', 'GREATER_THAN_OR_EQUAL', 'BETWEEN'],
'DateTimeField': ['LESS_THAN', 'LESS_THAN_OR_EQUAL', 'GREATER_THAN', 'GREATER_THAN_OR_EQUAL', 'BETWEEN'],
'DateField': ['LESS_THAN', 'LESS_THAN_OR_EQUAL', 'GREATER_THAN', 'GREATER_THAN_OR_EQUAL', 'BETWEEN'],
'LatLongField': ['INSIDE'],
'latitude': ['INSIDE']
};
function formatOperator(cls, field, table) {
return {
'name': cls.name,
'verbose_name': cls.verbose_name,
'validArgumentsFilters': cls.validArgumentsFilters,
'fields': _.map(cls.validArgumentsFilters, function(validArgumentsFilter) {
return validArgumentsFilter(field, table);
})
};
}
function getOperators(field, table) {
// All fields have the base operators
var validOperators = BaseOperators;
// Check to see if the field's base type offers additional operators
if (field.base_type in AdditionalOperators) {
validOperators = validOperators.concat(AdditionalOperators[field.base_type]);
}
// Check to see if the field's semantic type offers additional operators
if (field.special_type in AdditionalOperators) {
validOperators = validOperators.concat(AdditionalOperators[field.special_type]);
}
// Wrap them up and send them back
return _.map(validOperators, function(operator) {
return formatOperator(FilterOperators[operator], field, table);
});
}
// Breakouts and Aggregation options
function allFields(fields) {
return fields;
}
function summableFields(fields) {
return _.filter(fields, isSummable);
}
function dimensionFields(fields) {
return _.filter(fields, isDimension);
}
var Aggregators = [{
"name": "Raw data",
"short": "rows",
"description": "Just a table with the rows in the answer, no additional operations.",
"advanced": false,
"validFieldsFilters": []
}, {
"name": "Count",
"short": "count",
"description": "Total number of rows in the answer.",
"advanced": false,
"validFieldsFilters": []
}, {
"name": "Sum",
"short": "sum",
"description": "Sum of all the values of a column.",
"advanced": false,
"validFieldsFilters": [summableFields]
}, {
"name": "Average",
"short": "avg",
"description": "Average of all the values of a column",
"advanced": false,
"validFieldsFilters": [summableFields]
}, {
"name": "Number of distinct values",
"short": "distinct",
"description": "Number of unique values of a column among all the rows in the answer.",
"advanced": true,
"validFieldsFilters": [allFields]
}, {
"name": "Cumulative sum",
"short": "cum_sum",
"description": "Additive sum of all the values of a column.\ne.x. total revenue over time.",
"advanced": true,
"validFieldsFilters": [summableFields]
}, {
"name": "Standard deviation",
"short": "stddev",
"description": "Number which expresses how much the values of a colum vary among all rows in the answer.",
"advanced": true,
"validFieldsFilters": [summableFields]
}];
var BreakoutAggregator = {
"name": "Break out by dimension",
"short": "breakout",
"validFieldsFilters": [dimensionFields]
};
function populateFields(aggregator, fields) {
return {
'name': aggregator.name,
'short': aggregator.short,
'description': aggregator.description || '',
'advanced': aggregator.advanced || false,
'fields': _.map(aggregator.validFieldsFilters, function(validFieldsFilterFn) {
return validFieldsFilterFn(fields);
}),
'validFieldsFilters': aggregator.validFieldsFilters
};
}
function getAggregators(fields) {
return _.map(Aggregators, function(aggregator) {
return populateFields(aggregator, fields);
});
}
function getBreakouts(fields) {
var result = populateFields(BreakoutAggregator, fields);
result.fields = result.fields[0];
result.validFieldsFilter = result.validFieldsFilters[0];
return result;
}
// Main entry function
this.addValidOperatorsToFields = function(table) {
_.each(table.fields, function(field) {
field.valid_operators = getOperators(field, table);
});
table.aggregation_options = getAggregators(table.fields);
table.breakout_options = getBreakouts(table.fields);
return table;
};
}]);
......@@ -20,6 +20,10 @@ export var ICON_PATHS = {
area: 'M25.4980562,23.9977382 L26.0040287,23.9999997 L26.0040283,22.4903505 L26.0040283,14 L26.0040287,12 L25.3213548,13.2692765 C25.3213548,13.2692765 22.6224921,15.7906709 21.2730607,17.0513681 C21.1953121,17.1240042 15.841225,18.0149981 15.841225,18.0149981 L15.5173319,18.0717346 L15.2903187,18.3096229 L10.5815987,23.2439142 L9.978413,23.9239006 L11.3005782,23.9342813 L25.4980562,23.9977382 L11.3050484,23.9342913 L16.0137684,19 L21.7224883,18 L26.0040283,14 L26.0040283,23.4903505 C26.0040283,23.7718221 25.7731425,23.9989679 25.4980562,23.9977382 Z M7,23.9342913 L14,16 L21,14 L25.6441509,9.35958767 C25.8429057,9.16099288 26.0040283,9.22974944 26.0040283,9.49379817 L26.0040283,13 L26.0040283,24 L7,23.9342913 Z',
bar: 'M9,20 L12,20 L12,24 L9,24 L9,20 Z M14,14 L17,14 L17,24 L14,24 L14,14 Z M19,9 L22,9 L22,24 L19,24 L19,9 Z',
cards: 'M16.5,11 C16.1340991,11 15.7865579,10.9213927 15.4733425,10.7801443 L7.35245972,21.8211652 C7.7548404,22.264891 8,22.8538155 8,23.5 C8,24.8807119 6.88071187,26 5.5,26 C4.11928813,26 3,24.8807119 3,23.5 C3,22.1192881 4.11928813,21 5.5,21 C5.87370843,21 6.22826528,21.0819977 6.5466604,21.2289829 L14.6623495,10.1950233 C14.2511829,9.74948188 14,9.15407439 14,8.5 C14,7.11928813 15.1192881,6 16.5,6 C17.8807119,6 19,7.11928813 19,8.5 C19,8.96980737 18.8704088,9.4093471 18.6450228,9.78482291 L25.0405495,15.4699905 C25.4512188,15.1742245 25.9552632,15 26.5,15 C27.8807119,15 29,16.1192881 29,17.5 C29,18.8807119 27.8807119,20 26.5,20 C25.1192881,20 24,18.8807119 24,17.5 C24,17.0256697 24.1320984,16.5821926 24.3615134,16.2043506 L17.9697647,10.5225413 C17.5572341,10.8228405 17.0493059,11 16.5,11 Z M5.5,25 C6.32842712,25 7,24.3284271 7,23.5 C7,22.6715729 6.32842712,22 5.5,22 C4.67157288,22 4,22.6715729 4,23.5 C4,24.3284271 4.67157288,25 5.5,25 Z M26.5,19 C27.3284271,19 28,18.3284271 28,17.5 C28,16.6715729 27.3284271,16 26.5,16 C25.6715729,16 25,16.6715729 25,17.5 C25,18.3284271 25.6715729,19 26.5,19 Z M16.5,10 C17.3284271,10 18,9.32842712 18,8.5 C18,7.67157288 17.3284271,7 16.5,7 C15.6715729,7 15,7.67157288 15,8.5 C15,9.32842712 15.6715729,10 16.5,10 Z',
calendar: {
path: 'M21,2 L21,0 L18,0 L18,2 L6,2 L6,0 L3,0 L3,2 L2.99109042,2 C1.34177063,2 0,3.34314575 0,5 L0,6.99502651 L0,20.009947 C0,22.2157067 1.78640758,24 3.99005301,24 L20.009947,24 C22.2157067,24 24,22.2135924 24,20.009947 L24,6.99502651 L24,5 C24,3.34651712 22.6608432,2 21.0089096,2 L21,2 L21,2 Z M22,8 L22,20.009947 C22,21.1099173 21.1102431,22 20.009947,22 L3.99005301,22 C2.89008272,22 2,21.1102431 2,20.009947 L2,8 L22,8 L22,8 Z M6,12 L10,12 L10,16 L6,16 L6,12 Z',
attrs: { viewBox: '0 0 24 24'}
},
check: 'M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z ',
chevrondown: 'M1 12 L16 26 L31 12 L27 8 L16 18 L5 8 z ',
chevronleft: 'M20 1 L24 5 L14 16 L24 27 L20 31 L6 16 z',
......@@ -50,8 +54,16 @@ export var ICON_PATHS = {
gear: 'M14 0 H18 L19 6 L20.707 6.707 L26 3.293 L28.707 6 L25.293 11.293 L26 13 L32 14 V18 L26 19 L25.293 20.707 L28.707 26 L26 28.707 L20.707 25.293 L19 26 L18 32 L14 32 L13 26 L11.293 25.293 L6 28.707 L3.293 26 L6.707 20.707 L6 19 L0 18 L0 14 L6 13 L6.707 11.293 L3.293 6 L6 3.293 L11.293 6.707 L13 6 L14 0 z M16 10 A6 6 0 0 0 16 22 A6 6 0 0 0 16 10',
grid: 'M2 2 L10 2 L10 10 L2 10z M12 2 L20 2 L20 10 L12 10z M22 2 L30 2 L30 10 L22 10z M2 12 L10 12 L10 20 L2 20z M12 12 L20 12 L20 20 L12 20z M22 12 L30 12 L30 20 L22 20z M2 22 L10 22 L10 30 L2 30z M12 22 L20 22 L20 30 L12 30z M22 22 L30 22 L30 30 L22 30z',
history: 'M31.3510226,15.6624718 C31.3510226,23.4208104 25.0228069,29.7490261 17.2644683,29.7490261 C13.3633933,29.7490261 9.80936578,28.145223 7.2955414,25.5882133 L9.46263138,23.4208104 C11.4563542,25.3710349 14.2302293,26.6281036 17.2644683,26.6281036 C23.3326331,26.6281036 28.2301,21.7306367 28.2301,15.6624718 C28.2301,9.59430691 23.3326331,4.69652708 17.2644683,4.69652708 C11.9329575,4.69652708 7.4689086,8.38073652 6.47189074,13.4083853 L9.98273298,12.8882837 L4.99826955,19.8664699 L0.0573043855,12.8882837 L3.35128116,13.364887 C4.39148435,6.64706454 10.1995984,1.57591751 17.2644683,1.57591751 C25.0228069,1.57591751 31.3510226,7.86063493 31.3510226,15.6624718 Z M22.4222989,11.0679281 C24.2426545,10.2877757 25.4562249,13.1051492 23.6793675,13.9287998 L18.0449336,16.1825734 L16.8745485,19.3463683 C16.1810797,21.3400911 13.320521,20.04015 13.9704915,18.2629798 L15.3139308,14.5352721 C15.487298,14.1450394 15.7041635,13.8418033 16.0508979,13.7116214 L22.4222989,11.0679281 Z',
int: {
path: 'M15.141,15.512 L14.294,20 L13.051,20 C12.8309989,20 12.6403341,19.9120009 12.479,19.736 C12.3176659,19.5599991 12.237,19.343668 12.237,19.087 C12.237,19.0503332 12.2388333,19.0155002 12.2425,18.9825 C12.2461667,18.9494998 12.2516666,18.9146668 12.259,18.878 L12.908,15.512 L10.653,15.512 L10.015,19.01 C9.94899967,19.3620018 9.79866784,19.6149992 9.564,19.769 C9.32933216,19.9230008 9.06900143,20 8.783,20 L7.584,20 L8.42,15.512 L7.155,15.512 C6.92033216,15.512 6.74066729,15.4551672 6.616,15.3415 C6.49133271,15.2278328 6.429,15.0390013 6.429,14.775 C6.429,14.6723328 6.43999989,14.5550007 6.462,14.423 L6.605,13.554 L8.695,13.554 L9.267,10.518 L6.913,10.518 L7.122,9.385 C7.17333359,9.10633194 7.28699912,8.89916734 7.463,8.7635 C7.63900088,8.62783266 7.92499802,8.56 8.321,8.56 L9.542,8.56 L10.224,5.018 C10.282667,4.7246652 10.4183323,4.49733414 10.631,4.336 C10.8436677,4.17466586 11.0929986,4.094 11.379,4.094 L12.611,4.094 L11.775,8.56 L14.019,8.56 L14.866,4.094 L16.076,4.094 C16.3326679,4.094 16.5416659,4.1673326 16.703,4.314 C16.8643341,4.4606674 16.945,4.64766553 16.945,4.875 C16.945,4.9483337 16.9413334,5.00333315 16.934,5.04 L16.252,8.56 L18.485,8.56 L18.276,9.693 C18.2246664,9.97166806 18.1091676,10.1788327 17.9295,10.3145 C17.7498324,10.4501673 17.4656686,10.518 17.077,10.518 L15.977,10.518 L15.416,13.554 L16.978,13.554 C17.2126678,13.554 17.3904994,13.6108328 17.5115,13.7245 C17.6325006,13.8381672 17.693,14.0306653 17.693,14.302 C17.693,14.4046672 17.6820001,14.5219993 17.66,14.654 L17.528,15.512 L15.141,15.512 Z M10.928,13.554 L13.183,13.554 L13.744,10.518 L11.5,10.518 L10.928,13.554 Z',
attrs: { viewBox: '0 0 24, 24' }
},
line: 'M17.5684644,16.0668074 L15.9388754,14.3793187 L15.8968592,14.4198933 L15.8953638,14.4183447 L15.8994949,14.4142136 L15.6628229,14.1775415 L15.5851122,14.0970697 L15.5837075,14.0984261 L14.4852814,13 L7.56742615,19.9178552 L8.98163972,21.3320688 L14.4809348,15.8327737 L14.4809348,15.8327737 L16.1103863,17.52012 L16.1522861,17.4796579 L16.1522861,17.4796579 L16.1539209,17.4813508 L16.1500476,17.4852242 L16.3719504,17.707127 L16.4640332,17.8024814 L16.4656976,17.8008741 L17.5643756,18.8995521 L24.4820322,11.9818955 L23.0677042,10.5675676 L17.5684644,16.0668074 Z',
list: 'M3 8 A3 3 0 0 0 9 8 A3 3 0 0 0 3 8 M12 6 L28 6 L28 10 L12 10z M3 16 A3 3 0 0 0 9 16 A3 3 0 0 0 3 16 M12 14 L28 14 L28 18 L12 18z M3 24 A3 3 0 0 0 9 24 A3 3 0 0 0 3 24 M12 22 L28 22 L28 26 L12 26z',
location: {
path: 'M19.4917776,13.9890373 C20.4445763,12.5611169 21,10.8454215 21,9 C21,4.02943725 16.9705627,0 12,0 C7.02943725,0 3,4.02943725 3,9 C3,10.8454215 3.5554237,12.5611168 4.50822232,13.9890371 L4.49999986,14.0000004 L4.58010869,14.0951296 C4.91305602,14.5790657 5.29212089,15.0288088 5.71096065,15.4380163 L12.5,23.5 L19.4999993,13.9999996 L19.4917776,13.9890373 L19.4917776,13.9890373 Z M12,12 C13.6568542,12 15,10.6568542 15,9 C15,7.34314575 13.6568542,6 12,6 C10.3431458,6 9,7.34314575 9,9 C9,10.6568542 10.3431458,12 12,12 Z',
attrs: { viewBox: '0 0 24 24' }
},
lock: 'M8.8125,13.2659641 L5.50307055,13.2659641 C4.93891776,13.2659641 4.5,13.7132101 4.5,14.2649158 L4.5,30.8472021 C4.5,31.4051918 4.94908998,31.8461538 5.50307055,31.8461538 L26.4969294,31.8461538 C27.0610822,31.8461538 27.5,31.3989079 27.5,30.8472021 L27.5,14.2649158 C27.5,13.7069262 27.05091,13.2659641 26.4969294,13.2659641 L23.1875,13.2659641 L23.1875,7.18200446 C23.1875,3.22368836 19.9695466,0 16,0 C12.0385306,0 8.8125,3.21549292 8.8125,7.18200446 L8.8125,13.2659641 Z M12.3509615,7.187641 C12.3509615,5.17225484 13.9813894,3.53846154 15.9955768,3.53846154 C18.0084423,3.53846154 19.6401921,5.17309313 19.6401921,7.187641 L19.6401921,13.0473232 L12.3509615,13.0473232 L12.3509615,7.187641 Z',
mine: 'M28.4907419,50 C25.5584999,53.6578499 21.0527692,56 16,56 C10.9472308,56 6.44150015,53.6578499 3.50925809,50 L28.4907419,50 Z M29.8594823,31.9999955 C27.0930063,27.217587 21.922257,24 16,24 C10.077743,24 4.9069937,27.217587 2.1405177,31.9999955 L29.8594849,32 Z M16,21 C19.8659932,21 23,17.1944204 23,12.5 C23,7.80557963 22,3 16,3 C10,3 9,7.80557963 9,12.5 C9,17.1944204 12.1340068,21 16,21 Z',
number: 'M8,8.4963932 C8,8.22224281 8.22618103,8 8.4963932,8 L23.5036068,8 C23.7777572,8 24,8.22618103 24,8.4963932 L24,23.5036068 C24,23.7777572 23.773819,24 23.5036068,24 L8.4963932,24 C8.22224281,24 8,23.773819 8,23.5036068 L8,8.4963932 Z M12.136,19 L12.136,13.4 L11.232,13.4 C11.1999998,13.6133344 11.1333338,13.7919993 11.032,13.936 C10.9306662,14.0800007 10.8066674,14.1959996 10.66,14.284 C10.5133326,14.3720004 10.3480009,14.4333332 10.164,14.468 C9.97999908,14.5026668 9.78933432,14.5173334 9.592,14.512 L9.592,15.368 L11,15.368 L11,19 L12.136,19 Z M13.616,16.176 C13.616,16.7360028 13.6706661,17.2039981 13.78,17.58 C13.8893339,17.9560019 14.0373324,18.2559989 14.224,18.48 C14.4106676,18.7040011 14.6279988,18.8639995 14.876,18.96 C15.1240012,19.0560005 15.3866653,19.104 15.664,19.104 C15.9466681,19.104 16.2119988,19.0560005 16.46,18.96 C16.7080012,18.8639995 16.9266657,18.7040011 17.116,18.48 C17.3053343,18.2559989 17.4546661,17.9560019 17.564,17.58 C17.6733339,17.2039981 17.728,16.7360028 17.728,16.176 C17.728,15.6319973 17.6733339,15.1746685 17.564,14.804 C17.4546661,14.4333315 17.3053343,14.1360011 17.116,13.912 C16.9266657,13.6879989 16.7080012,13.5280005 16.46,13.432 C16.2119988,13.3359995 15.9466681,13.288 15.664,13.288 C15.3866653,13.288 15.1240012,13.3359995 14.876,13.432 C14.6279988,13.5280005 14.4106676,13.6879989 14.224,13.912 C14.0373324,14.1360011 13.8893339,14.4333315 13.78,14.804 C13.6706661,15.1746685 13.616,15.6319973 13.616,16.176 Z M14.752,16.176 C14.752,16.0799995 14.7533333,15.9640007 14.756,15.828 C14.7586667,15.6919993 14.7679999,15.5520007 14.784,15.408 C14.8000001,15.2639993 14.8266665,15.121334 14.864,14.98 C14.9013335,14.838666 14.953333,14.7120006 15.02,14.6 C15.086667,14.4879994 15.1719995,14.3973337 15.276,14.328 C15.3800005,14.2586663 15.5093326,14.224 15.664,14.224 C15.8186674,14.224 15.9493328,14.2586663 16.056,14.328 C16.1626672,14.3973337 16.2506663,14.4879994 16.32,14.6 C16.3893337,14.7120006 16.4413332,14.838666 16.476,14.98 C16.5106668,15.121334 16.5373332,15.2639993 16.556,15.408 C16.5746668,15.5520007 16.5853333,15.6919993 16.588,15.828 C16.5906667,15.9640007 16.592,16.0799995 16.592,16.176 C16.592,16.3360008 16.5866667,16.5293322 16.576,16.756 C16.5653333,16.9826678 16.5320003,17.2013323 16.476,17.412 C16.4199997,17.6226677 16.329334,17.8026659 16.204,17.952 C16.078666,18.1013341 15.8986678,18.176 15.664,18.176 C15.4346655,18.176 15.2586673,18.1013341 15.136,17.952 C15.0133327,17.8026659 14.9240003,17.6226677 14.868,17.412 C14.8119997,17.2013323 14.7786667,16.9826678 14.768,16.756 C14.7573333,16.5293322 14.752,16.3360008 14.752,16.176 Z M18.064,16.176 C18.064,16.7360028 18.1186661,17.2039981 18.228,17.58 C18.3373339,17.9560019 18.4853324,18.2559989 18.672,18.48 C18.8586676,18.7040011 19.0759988,18.8639995 19.324,18.96 C19.5720012,19.0560005 19.8346653,19.104 20.112,19.104 C20.3946681,19.104 20.6599988,19.0560005 20.908,18.96 C21.1560012,18.8639995 21.3746657,18.7040011 21.564,18.48 C21.7533343,18.2559989 21.9026661,17.9560019 22.012,17.58 C22.1213339,17.2039981 22.176,16.7360028 22.176,16.176 C22.176,15.6319973 22.1213339,15.1746685 22.012,14.804 C21.9026661,14.4333315 21.7533343,14.1360011 21.564,13.912 C21.3746657,13.6879989 21.1560012,13.5280005 20.908,13.432 C20.6599988,13.3359995 20.3946681,13.288 20.112,13.288 C19.8346653,13.288 19.5720012,13.3359995 19.324,13.432 C19.0759988,13.5280005 18.8586676,13.6879989 18.672,13.912 C18.4853324,14.1360011 18.3373339,14.4333315 18.228,14.804 C18.1186661,15.1746685 18.064,15.6319973 18.064,16.176 Z M19.2,16.176 C19.2,16.0799995 19.2013333,15.9640007 19.204,15.828 C19.2066667,15.6919993 19.2159999,15.5520007 19.232,15.408 C19.2480001,15.2639993 19.2746665,15.121334 19.312,14.98 C19.3493335,14.838666 19.401333,14.7120006 19.468,14.6 C19.534667,14.4879994 19.6199995,14.3973337 19.724,14.328 C19.8280005,14.2586663 19.9573326,14.224 20.112,14.224 C20.2666674,14.224 20.3973328,14.2586663 20.504,14.328 C20.6106672,14.3973337 20.6986663,14.4879994 20.768,14.6 C20.8373337,14.7120006 20.8893332,14.838666 20.924,14.98 C20.9586668,15.121334 20.9853332,15.2639993 21.004,15.408 C21.0226668,15.5520007 21.0333333,15.6919993 21.036,15.828 C21.0386667,15.9640007 21.04,16.0799995 21.04,16.176 C21.04,16.3360008 21.0346667,16.5293322 21.024,16.756 C21.0133333,16.9826678 20.9800003,17.2013323 20.924,17.412 C20.8679997,17.6226677 20.777334,17.8026659 20.652,17.952 C20.526666,18.1013341 20.3466678,18.176 20.112,18.176 C19.8826655,18.176 19.7066673,18.1013341 19.584,17.952 C19.4613327,17.8026659 19.3720003,17.6226677 19.316,17.412 C19.2599997,17.2013323 19.2266667,16.9826678 19.216,16.756 C19.2053333,16.5293322 19.2,16.3360008 19.2,16.176 Z',
......@@ -68,8 +80,13 @@ export var ICON_PATHS = {
search: 'M12 0 A12 12 0 0 0 0 12 A12 12 0 0 0 12 24 A12 12 0 0 0 18.5 22.25 L28 32 L32 28 L22.25 18.5 A12 12 0 0 0 24 12 A12 12 0 0 0 12 0 M12 4 A8 8 0 0 1 12 20 A8 8 0 0 1 12 4 ',
star: 'M16 0 L21 11 L32 12 L23 19 L26 31 L16 25 L6 31 L9 19 L0 12 L11 11',
statemap: 'M19.4375,6.97396734 L27,8.34417492 L27,25.5316749 L18.7837192,23.3765689 L11.875,25.3366259 L11.875,25.3366259 L11.875,11.0941749 L11.1875,11.0941749 L11.1875,25.5316749 L5,24.1566749 L5,7.65667492 L11.1875,9.03167492 L18.75,6.90135976 L18.75,22.0941749 L19.4375,22.0941749 L19.4375,6.97396734 Z',
string: {
path: 'M14.022,18 L11.533,18 C11.2543319,18 11.0247509,17.935084 10.84425,17.80525 C10.6637491,17.675416 10.538667,17.5091677 10.469,17.3065 L9.652,14.8935 L4.389,14.8935 L3.572,17.3065 C3.50866635,17.4838342 3.38516758,17.6437493 3.2015,17.78625 C3.01783241,17.9287507 2.79300133,18 2.527,18 L0.019,18 L5.377,4.1585 L8.664,4.1585 L14.022,18 Z M5.13,12.7085 L8.911,12.7085 L7.638,8.918 C7.55566626,8.67733213 7.45908389,8.3939183 7.34825,8.06775 C7.23741611,7.7415817 7.12816721,7.3885019 7.0205,7.0085 C6.91916616,7.39483527 6.8146672,7.75266502 6.707,8.082 C6.5993328,8.41133498 6.49800047,8.69633213 6.403,8.937 L5.13,12.7085 Z M21.945,18 C21.6663319,18 21.4557507,17.9620004 21.31325,17.886 C21.1707493,17.8099996 21.0520005,17.6516679 20.957,17.411 L20.748,16.8695 C20.5009988,17.078501 20.2635011,17.2621659 20.0355,17.4205 C19.8074989,17.5788341 19.5715846,17.7134161 19.32775,17.82425 C19.0839154,17.9350839 18.8242514,18.0174164 18.54875,18.07125 C18.2732486,18.1250836 17.9676683,18.152 17.632,18.152 C17.1823311,18.152 16.7738352,18.0934173 16.4065,17.97625 C16.0391648,17.8590827 15.7272513,17.6865011 15.47075,17.4585 C15.2142487,17.2304989 15.016334,16.947085 14.877,16.60825 C14.737666,16.269415 14.668,15.8783355 14.668,15.435 C14.668,15.0866649 14.7566658,14.7288352 14.934,14.3615 C15.1113342,13.9941648 15.4184978,13.6600848 15.8555,13.35925 C16.2925022,13.0584152 16.8814963,12.8066677 17.6225,12.604 C18.3635037,12.4013323 19.297661,12.2873335 20.425,12.262 L20.425,11.844 C20.425,11.2676638 20.3062512,10.8512513 20.06875,10.59475 C19.8312488,10.3382487 19.4940022,10.21 19.057,10.21 C18.7086649,10.21 18.4236678,10.2479996 18.202,10.324 C17.9803322,10.4000004 17.7824175,10.4854995 17.60825,10.5805 C17.4340825,10.6755005 17.2646675,10.7609996 17.1,10.837 C16.9353325,10.9130004 16.7390011,10.951 16.511,10.951 C16.3083323,10.951 16.1357507,10.9019172 15.99325,10.80375 C15.8507493,10.7055828 15.7383337,10.5836674 15.656,10.438 L15.124,9.5165 C15.7193363,8.99083071 16.3795797,8.59975128 17.10475,8.34325 C17.8299203,8.08674872 18.6073292,7.9585 19.437,7.9585 C20.0323363,7.9585 20.5690809,8.05508237 21.04725,8.24825 C21.5254191,8.44141763 21.9307483,8.71058161 22.26325,9.05575 C22.5957517,9.40091839 22.8506658,9.81099763 23.028,10.286 C23.2053342,10.7610024 23.294,11.2803305 23.294,11.844 L23.294,18 L21.945,18 Z M18.563,16.2045 C18.9430019,16.2045 19.2754986,16.1380007 19.5605,16.005 C19.8455014,15.8719993 20.1336652,15.6566682 20.425,15.359 L20.425,13.991 C19.8359971,14.0163335 19.3515019,14.0669996 18.9715,14.143 C18.5914981,14.2190004 18.2906678,14.3139994 18.069,14.428 C17.8473322,14.5420006 17.6937504,14.6718326 17.60825,14.8175 C17.5227496,14.9631674 17.48,15.1214991 17.48,15.2925 C17.48,15.6281683 17.5718324,15.8640827 17.7555,16.00025 C17.9391676,16.1364173 18.2083316,16.2045 18.563,16.2045 L18.563,16.2045 Z',
attrs: { viewBox: '0 0 24 24'}
},
table: 'M13.6373197,13.6373197 L18.3626803,13.6373197 L18.3626803,18.3626803 L13.6373197,18.3626803 L13.6373197,13.6373197 Z M18.9533504,18.9533504 L23.6787109,18.9533504 L23.6787109,23.6787109 L18.9533504,23.6787109 L18.9533504,18.9533504 Z M13.6373197,18.9533504 L18.3626803,18.9533504 L18.3626803,23.6787109 L13.6373197,23.6787109 L13.6373197,18.9533504 Z M8.32128906,18.9533504 L13.0466496,18.9533504 L13.0466496,23.6787109 L8.32128906,23.6787109 L8.32128906,18.9533504 Z M8.32128906,8.32128906 L13.0466496,8.32128906 L13.0466496,13.0466496 L8.32128906,13.0466496 L8.32128906,8.32128906 Z M8.32128906,13.6373197 L13.0466496,13.6373197 L13.0466496,18.3626803 L8.32128906,18.3626803 L8.32128906,13.6373197 Z M18.9533504,8.32128906 L23.6787109,8.32128906 L23.6787109,13.0466496 L18.9533504,13.0466496 L18.9533504,8.32128906 Z M18.9533504,13.6373197 L23.6787109,13.6373197 L23.6787109,18.3626803 L18.9533504,18.3626803 L18.9533504,13.6373197 Z M13.6373197,8.32128906 L18.3626803,8.32128906 L18.3626803,13.0466496 L13.6373197,13.0466496 L13.6373197,8.32128906 Z',
trash: 'M4.31904507,29.7285487 C4.45843264,30.9830366 5.59537721,32 6.85726914,32 L20.5713023,32 C21.8337371,32 22.9701016,30.9833707 23.1095264,29.7285487 L25.1428571,11.4285714 L2.28571429,11.4285714 L4.31904507,29.7285487 L4.31904507,29.7285487 Z M6.85714286,4.57142857 L8.57142857,0 L18.8571429,0 L20.5714286,4.57142857 L25.1428571,4.57142857 C27.4285714,4.57142857 27.4285714,9.14285714 27.4285714,9.14285714 L13.7142857,9.14285714 L-1.0658141e-14,9.14285714 C-1.0658141e-14,9.14285714 -1.0658141e-14,4.57142857 2.28571429,4.57142857 L6.85714286,4.57142857 L6.85714286,4.57142857 Z M9.14285714,4.57142857 L18.2857143,4.57142857 L17.1428571,2.28571429 L10.2857143,2.28571429 L9.14285714,4.57142857 L9.14285714,4.57142857 Z',
unknown: 'M16.5,26.5 C22.0228475,26.5 26.5,22.0228475 26.5,16.5 C26.5,10.9771525 22.0228475,6.5 16.5,6.5 C10.9771525,6.5 6.5,10.9771525 6.5,16.5 C6.5,22.0228475 10.9771525,26.5 16.5,26.5 L16.5,26.5 Z M16.5,23.5 C12.6340068,23.5 9.5,20.3659932 9.5,16.5 C9.5,12.6340068 12.6340068,9.5 16.5,9.5 C20.3659932,9.5 23.5,12.6340068 23.5,16.5 C23.5,20.3659932 20.3659932,23.5 16.5,23.5 L16.5,23.5 Z',
"illustration-icon-pie": {
svg: "<path d='M29.8065455,22.2351515 L15.7837576,15.9495758 L15.7837576,31.2174545 C22.0004848,31.2029091 27.3444848,27.5258182 29.8065455,22.2351515' fill='#78B5EC'></path><g id='Fill-1-+-Fill-3'><path d='M29.8065455,22.2351515 C30.7316364,20.2482424 31.2630303,18.0402424 31.2630303,15.7032727 C31.2630303,11.8138182 29.8220606,8.26763636 27.4569697,5.54472727 L15.7837576,15.9495758 L29.8065455,22.2351515' fill='#3875AC'></path><path d='M27.4569697,5.54472727 C24.6118788,2.26909091 20.4266667,0.188121212 15.7478788,0.188121212 C7.17963636,0.188121212 0.232727273,7.1350303 0.232727273,15.7032727 C0.232727273,24.2724848 7.17963636,31.2184242 15.7478788,31.2184242 C15.7604848,31.2184242 15.7721212,31.2174545 15.7837576,31.2174545 L15.7837576,15.9495758 L27.4569697,5.54472727' fill='#4C9DE6'></path></g>"
},
......
......@@ -2,7 +2,7 @@
import _ from "underscore";
import SchemaMetadata from "metabase/lib/schema_metadata";
import * as SchemaMetadata from "metabase/lib/schema_metadata";
function compareNumbers(a, b) {
return a - b;
......@@ -50,13 +50,13 @@ var DataGrid = {
}
// sort the column values sensibly
if (SchemaMetadata.isNumericType(data.cols[pivotCol])) {
if (SchemaMetadata.isNumeric(data.cols[pivotCol])) {
pivotColValues.sort(compareNumbers);
} else {
pivotColValues.sort();
}
if (SchemaMetadata.isNumericType(data.cols[normalCol])) {
if (SchemaMetadata.isNumeric(data.cols[normalCol])) {
normalColValues.sort(compareNumbers);
} else {
normalColValues.sort();
......
"use strict";
import d3 from "d3";
import inflection from "inflection";
var precisionNumberFormatter = d3.format(".2r");
var fixedNumberFormatter = d3.format(",.f");
......@@ -22,3 +23,16 @@ export function formatScalar(scalar) {
return String(scalar);
}
}
export function singularize(...args) {
return inflection.singularize(...args);
}
export function capitalize(...args) {
return inflection.capitalize(...args);
}
// Removes trailing "id" from field names
export function stripId(name) {
return name && name.replace(/ id$/i, "");
}
......@@ -340,11 +340,25 @@ var Query = {
typeof field === "number" ||
(Array.isArray(field) && (
(field[0] === 'fk->' && typeof field[1] === "number" && typeof field[2] === "number") ||
(field[0] === 'datetime_field' && Query.isValidField(field[1]) && field[2] === "as" && typeof field[3] === "string") ||
(field[0] === 'aggregation' && typeof field[1] === "number")
))
);
},
getFieldTarget: function(field, tableMetadata) {
let table, fieldId, fk;
if (Array.isArray(field) && field[0] === "fk->") {
fk = tableMetadata.fields_lookup[field[1]];
table = fk.target.table;
fieldId = field[2];
} else {
table = tableMetadata;
fieldId = field;
}
return { table, field: table.fields_lookup[fieldId] };
},
getFieldOptions: function(fields, includeJoins = false, filterFn = (fields) => fields, usedFields = {}) {
var results = {
count: 0,
......
"use strict";
import moment from "moment";
import inflection from "inflection";
export function computeFilterTimeRange(filter) {
let expandedFilter;
if (filter[0] === "TIME_INTERVAL") {
expandedFilter = expandTimeIntervalFilter(filter);
} else {
expandedFilter = filter;
}
let [operator, field, ...values] = expandedFilter;
let bucketing = parseFieldBucketing(field);
let start, end;
if (operator === "=" && values[0]) {
let point = absolute(values[0]);
start = point.clone().startOf(bucketing);
end = point.clone().endOf(bucketing);
} else if (operator === ">" && values[0]) {
start = absolute(values[0]).endOf(bucketing);
end = max();
} else if (operator === "<" && values[0]) {
start = min();
end = absolute(values[0]).startOf(bucketing);
} else if (operator === "BETWEEN" && values[0] && values[1]) {
start = absolute(values[0]).startOf(bucketing);
end = absolute(values[1]).endOf(bucketing);
}
return [start, end];
}
export function expandTimeIntervalFilter(filter) {
let [operator, field, n, unit] = filter;
if (operator !== "TIME_INTERVAL") {
throw new Error("translateTimeInterval expects operator TIME_INTERVAL");
}
if (n === "current") {
n = 0;
} else if (n === "last") {
n = -1;
} else if (n === "next") {
n = 1;
}
field = ["datetime_field", field, "as", unit];
if (n < -1) {
return ["BETWEEN", field, ["relative_datetime", n-1, unit], ["relative_datetime", -1, unit]];
} else if (n > 1) {
return ["BETWEEN", field, ["relative_datetime", 1, unit], ["relative_datetime", n, unit]];
} else if (n === 0) {
return ["=", field, ["relative_datetime", "current"]];
} else {
return ["=", field, ["relative_datetime", n, unit]];
}
}
export function generateTimeFilterValuesDescriptions(filter) {
let [operator, field, ...values] = filter;
let bucketing = parseFieldBucketing(field);
if (operator === "TIME_INTERVAL") {
let [n, unit] = values;
return generateTimeIntervalDescription(n, unit);
} else {
return values.map(value => generateTimeValueDescription(value, bucketing));
}
}
export function generateTimeIntervalDescription(n, unit) {
if (unit === "day") {
switch (n) {
case "current":
case 0:
return "Today";
case "next":
case 1:
return "Tomorrow";
case "last":
case -1:
return "Yesterday";
}
}
unit = inflection.capitalize(unit);
if (typeof n === "string") {
if (n === "current") {
n = "this";
}
return [inflection.capitalize(n) + " " + unit];
} else {
if (n < 0) {
return ["Past " + (-n) + " " + inflection.inflect(unit, -n)];
} else if (n > 0) {
return ["Next " + (n) + " " + inflection.inflect(unit, n)];
} else {
return ["This " + unit];
}
}
}
export function generateTimeValueDescription(value, bucketing) {
if (typeof value === "string") {
return moment(value).format("MMMM D, YYYY");
} else if (Array.isArray(value) && value[0] === "relative_datetime") {
let n = value[1];
let unit = value[2];
if (n === "current") {
n = 0;
unit = bucketing;
}
if (bucketing === unit) {
return generateTimeIntervalDescription(n, unit);
} else {
// FIXME: what to do if the bucketing and unit don't match?
if (n === 0) {
return "Now";
} else {
return Math.abs(n) + " " + inflection.inflect(unit, Math.abs(n)) + (n < 0 ? " ago" : " from now");
}
}
} else {
console.warn("Unknown datetime format", value);
return "[Unknown]";
}
}
export function formatBucketing(bucketing) {
let words = bucketing.split("-");
words[0] = inflection.capitalize(words[0]);
return words.join(" ");
}
export function absolute(date) {
if (typeof date === "string") {
return moment(date);
} else if (Array.isArray(date) && date[0] === "relative_datetime") {
return moment().add(date[1], date[2]);
} else {
console.warn("Unknown datetime format", date);
}
}
export function parseFieldBucketing(field) {
if (Array.isArray(field)) {
if (field[0] === "datetime_field") {
return field[3];
} if (field[0] === "fk->") {
return "day";
} else {
console.warn("Unknown field format", field);
}
}
return "day";
}
export function parseFieldTarget(field) {
if (Array.isArray(field)) {
if (field[0] === "datetime_field") {
return field[1];
} if (field[0] === "fk->") {
return field;
} else {
console.warn("Unknown field format", field);
}
}
return field;
}
// 271821 BC and 275760 AD and should be far enough in the past/future
function max() {
return moment(new Date(864000000000000));
}
function min() {
return moment(new Date(-864000000000000));
}
......@@ -2,44 +2,392 @@
import _ from "underscore";
// create a standardized set of strings to return
export const TIME = 'TIME';
export const NUMBER = 'NUMBER';
export const STRING = 'STRING';
export const BOOL = 'BOOL';
export const LOCATION = 'LOCATION';
export const UNKNOWN = 'UNKNOWN';
var DateBaseTypes = ['DateTimeField', 'DateField'];
var DateSpecialTypes = ['timestamp_milliseconds', 'timestamp_seconds'];
var NumberBaseTypes = ['IntegerField', 'DecimalField', 'FloatField', 'BigIntegerField'];
var SummableBaseTypes = ['IntegerField', 'DecimalField', 'FloatField', 'BigIntegerField'];
var CategoryBaseTypes = ["BooleanField"];
var CategorySpecialTypes = ["category", "zip_code", "city", "state", "country"];
const DateBaseTypes = ['DateTimeField', 'DateField'];
const NumberBaseTypes = ['IntegerField', 'DecimalField', 'FloatField', 'BigIntegerField'];
const BooleanTypes = ["BooleanField"];
function isInTypes(type, type_collection) {
if (_.indexOf(type_collection, type) >= 0) {
return true;
const SummableBaseTypes = ['IntegerField', 'DecimalField', 'FloatField', 'BigIntegerField'];
const CategoryBaseTypes = ["BooleanField"];
const DateSpecialTypes = ['timestamp_milliseconds', 'timestamp_seconds'];
const CategorySpecialTypes = ["category", "zip_code", "city", "state", "country"];
function isInTypes(type, typeCollection) {
return _.contains(typeCollection, type);
}
export function isDate(field) {
return isInTypes(field.base_type, DateBaseTypes) || isInTypes(field.special_type, DateSpecialTypes);
}
export function isNumeric(field) {
return isInTypes(field.base_type, NumberBaseTypes);
}
export function isBoolean(field) {
return isInTypes(field.base_type, BooleanTypes);
}
export function isSummable(field) {
return isInTypes(field.base_type, SummableBaseTypes);
}
export function isCategory(field) {
return isInTypes(field.base_type, CategoryBaseTypes) || isInTypes(field.special_type, CategorySpecialTypes);
}
export function isDimension(field) {
return isDate(field) || isCategory(field) || isInTypes(field.field_type, ['dimension']);
}
// will return a string with possible values of 'date', 'number', 'bool', 'string'
// if the type cannot be parsed, then return undefined
export function getUmbrellaType(field) {
return parseSpecialType(field.special_type) || parseBaseType(field.base_type);
}
export function parseBaseType(type) {
switch(type) {
case 'DateField':
case 'DateTimeField':
case 'TimeField':
return TIME;
case 'BigIntegerField':
case 'IntegerField':
case 'FloatField':
case 'DecimalField':
return NUMBER;
case 'CharField':
case 'TextField':
return STRING;
case 'BooleanField':
return BOOL;
}
return false;
}
export function parseSpecialType(type) {
switch(type) {
case 'timestamp_milliseconds':
case 'timestamp_seconds':
return TIME;
case 'city':
case 'country':
case 'latitude':
case 'longitude':
case 'state':
case 'zipcode':
return LOCATION;
case 'name':
return STRING;
case 'number':
return NUMBER;
}
}
var SchemaMetadata = {
function freeformArgument(field, table) {
return {
type: "text"
};
}
isDateType: function(field) {
return isInTypes(field.base_type, DateBaseTypes) || isInTypes(field.special_type, DateSpecialTypes);
},
function numberArgument(field, table) {
return {
type: "number"
};
}
isNumericType: function(field) {
return isInTypes(field.base_type, NumberBaseTypes);
},
isSummableType: function(field) {
return isInTypes(field.base_type, SummableBaseTypes);
},
function comparableArgument(field, table) {
if (isNumeric(field)) {
return {
type: "number"
};
}
isCategoryType: function(field) {
return isInTypes(field.base_type, CategoryBaseTypes) || isInTypes(field.special_type, CategorySpecialTypes);
},
if (isDate(field)) {
return {
type: "date"
};
}
return {
type: "text"
};
}
isDimension: function(field) {
return SchemaMetadata.isDateType(field) || SchemaMetadata.isCategoryType(field) || isInTypes(field.field_type, ['dimension']);
function equivalentArgument(field, table) {
if (isBoolean(field)) {
return {
type: "select",
values: [
{ key: true, name: "True" },
{ key: false, name: "False" }
]
};
}
if (isCategory(field)) {
if (field.id in table.field_values && table.field_values[field.id].length > 0) {
let validValues = table.field_values[field.id];
validValues.sort();
return {
type: "select",
values: validValues
.filter(value => value != null)
.map(value => ({
key: value,
name: value
}))
};
}
}
if (isDate(field)) {
return {
type: "date"
};
}
if (isNumeric(field)) {
return {
type: "number"
};
}
return {
type: "text"
};
}
function longitudeFieldSelectArgument(field, table) {
return {
type: "select",
values: table.fields
.filter(field => field.special_type === "longitude")
.map(field => ({
key: field.id,
name: field.display_name
}))
};
}
const OPERATORS = {
"=": {
validArgumentsFilters: [equivalentArgument],
multi: true
},
"!=": {
validArgumentsFilters: [equivalentArgument],
multi: true
},
"IS_NULL": {
validArgumentsFilters: []
},
"NOT_NULL": {
validArgumentsFilters: []
},
"<": {
validArgumentsFilters: [comparableArgument]
},
"<=": {
validArgumentsFilters: [comparableArgument]
},
">": {
validArgumentsFilters: [comparableArgument]
},
">=": {
validArgumentsFilters: [comparableArgument]
},
"INSIDE": {
validArgumentsFilters: [longitudeFieldSelectArgument, numberArgument, numberArgument, numberArgument, numberArgument],
placeholders: ["Select longitude field", "Enter upper latitude", "Enter left longitude", "Enter lower latitude", "Enter right latitude"]
},
"BETWEEN": {
validArgumentsFilters: [comparableArgument, comparableArgument]
},
"STARTS_WITH": {
validArgumentsFilters: [freeformArgument]
},
"ENDS_WITH": {
validArgumentsFilters: [freeformArgument]
},
"CONTAINS": {
validArgumentsFilters: [freeformArgument]
}
};
// ordered list of operators and metadata per type
const OPERATORS_BY_TYPE_ORDERED = {
[NUMBER]: [
{ name: "=", verboseName: "Equal" },
{ name: "!=", verboseName: "Not equal" },
{ name: ">", verboseName: "Greater than" },
{ name: "<", verboseName: "Less than" },
{ name: "BETWEEN", verboseName: "Between" },
{ name: ">=", verboseName: "Greater than or equal to", advanced: true },
{ name: "<=", verboseName: "Less than or equal to", advanced: true },
{ name: "IS_NULL", verboseName: "Is empty", advanced: true },
{ name: "NOT_NULL",verboseName: "Not empty", advanced: true }
],
[STRING]: [
{ name: "=", verboseName: "Is" },
{ name: "!=", verboseName: "Is not" },
{ name: "IS_NULL", verboseName: "Is empty", advanced: true },
{ name: "NOT_NULL",verboseName: "Not empty", advanced: true }
],
[TIME]: [
{ name: "=", verboseName: "Is" },
{ name: "<", verboseName: "Before" },
{ name: ">", verboseName: "After" },
{ name: "BETWEEN", verboseName: "Between" }
],
[LOCATION]: [
{ name: "=", verboseName: "Is" },
{ name: "!=", verboseName: "Is not" },
{ name: "INSIDE", verboseName: "Inside" }
],
[BOOL]: [
{ name: "=", verboseName: "Is", multi: false, defaults: [true] },
{ name: "IS_NULL", verboseName: "Is empty" },
{ name: "NOT_NULL",verboseName: "Not empty" }
],
[UNKNOWN]: [
{ name: "=", verboseName: "Is" },
{ name: "!=", verboseName: "Is not" },
{ name: "IS_NULL", verboseName: "Is empty", advanced: true },
{ name: "NOT_NULL",verboseName: "Not empty", advanced: true }
]
};
const MORE_VERBOSE_NAMES = {
"equal": "is equal to",
"not equal": "is not equal to",
"before": "is before",
"after": "is afer",
"not empty": "is not empty",
"less than": "is less than",
"greater than": "is greater than",
"less than or equal to": "is less than or equal to",
"greater than or equal to": "is greater than or equal to",
}
function getOperators(field, table) {
let type = getUmbrellaType(field) || UNKNOWN;
return OPERATORS_BY_TYPE_ORDERED[type].map(operatorForType => {
let operator = OPERATORS[operatorForType.name];
let verboseNameLower = operatorForType.verboseName.toLowerCase();
return {
...operator,
...operatorForType,
moreVerboseName: MORE_VERBOSE_NAMES[verboseNameLower] || verboseNameLower,
fields: operator.validArgumentsFilters.map(validArgumentsFilter => validArgumentsFilter(field, table))
};
});
}
// Breakouts and Aggregation options
function allFields(fields) {
return fields;
}
function summableFields(fields) {
return _.filter(fields, isSummable);
}
function dimensionFields(fields) {
return _.filter(fields, isDimension);
}
var Aggregators = [{
"name": "Raw data",
"short": "rows",
"description": "Just a table with the rows in the answer, no additional operations.",
"advanced": false,
"validFieldsFilters": []
}, {
"name": "Count",
"short": "count",
"description": "Total number of rows in the answer.",
"advanced": false,
"validFieldsFilters": []
}, {
"name": "Sum",
"short": "sum",
"description": "Sum of all the values of a column.",
"advanced": false,
"validFieldsFilters": [summableFields]
}, {
"name": "Average",
"short": "avg",
"description": "Average of all the values of a column",
"advanced": false,
"validFieldsFilters": [summableFields]
}, {
"name": "Number of distinct values",
"short": "distinct",
"description": "Number of unique values of a column among all the rows in the answer.",
"advanced": true,
"validFieldsFilters": [allFields]
}, {
"name": "Cumulative sum",
"short": "cum_sum",
"description": "Additive sum of all the values of a column.\ne.x. total revenue over time.",
"advanced": true,
"validFieldsFilters": [summableFields]
}, {
"name": "Standard deviation",
"short": "stddev",
"description": "Number which expresses how much the values of a colum vary among all rows in the answer.",
"advanced": true,
"validFieldsFilters": [summableFields]
}];
var BreakoutAggregator = {
"name": "Break out by dimension",
"short": "breakout",
"validFieldsFilters": [dimensionFields]
};
function populateFields(aggregator, fields) {
return {
'name': aggregator.name,
'short': aggregator.short,
'description': aggregator.description || '',
'advanced': aggregator.advanced || false,
'fields': _.map(aggregator.validFieldsFilters, function(validFieldsFilterFn) {
return validFieldsFilterFn(fields);
}),
'validFieldsFilters': aggregator.validFieldsFilters
};
}
function getAggregators(fields) {
return _.map(Aggregators, function(aggregator) {
return populateFields(aggregator, fields);
});
}
export default SchemaMetadata;
function getBreakouts(fields) {
var result = populateFields(BreakoutAggregator, fields);
result.fields = result.fields[0];
result.validFieldsFilter = result.validFieldsFilters[0];
return result;
}
export function addValidOperatorsToFields(table) {
for (let field of table.fields) {
field.valid_operators = getOperators(field, table);
}
table.aggregation_options = getAggregators(table.fields);
table.breakout_options = getBreakouts(table.fields);
return table;
}
'use strict';
var Table = {
isQueryable: function(table) {
return table.visibility_type == null;
}
};
export default Table;
export function isQueryable(table) {
return table.visibility_type == null;
}
"use strict";
import React, { Component, PropTypes } from "react";
import Icon from "metabase/components/Icon.react";
import cx from "classnames";
export default class AccordianList extends Component {
constructor(props) {
super(props);
this.state = {
openSection: undefined
};
}
toggleSection(sectionIndex) {
let openSection = this.getOpenSection();
if (openSection === sectionIndex) {
this.setState({ openSection: null });
} else {
this.setState({ openSection: sectionIndex });
}
}
getOpenSection() {
let { openSection } = this.state;
if (openSection === undefined) {
if (this.props.sectionIsSelected) {
for (let [index, section] of this.props.sections.entries()) {
if (this.props.sectionIsSelected(section, index)) {
openSection = index;
break;
}
}
}
if (openSection === undefined) {
openSection = 0;
}
}
return openSection;
}
itemIsSelected(item) {
if (this.props.itemIsSelected) {
return this.props.itemIsSelected(item);
} else {
return false;
}
}
onChange(item) {
if (this.props.onChange) {
this.props.onChange(item);
}
}
renderItem(item) {
if (this.props.renderItem) {
return this.props.renderItem(item);
} else {
return (
<div className="flex-full flex">
<a className="flex-full flex align-center px2 py1 cursor-pointer"
onClick={this.onChange.bind(this, item)}
>
<h4 className="List-item-title ml2">{item.name}</h4>
</a>
</div>
);
}
}
renderSectionIcon(section, sectionIndex) {
if (this.props.renderSectionIcon) {
return this.props.renderSectionIcon(section, sectionIndex);
} else {
return null;
}
}
render() {
let { sections } = this.props;
let openSection = this.getOpenSection();
return (
<div className={this.props.className} style={{width: '300px'}}>
{sections.map((section, sectionIndex) =>
<section key={sectionIndex}>
{ section.name != null ?
<div className="p1 border-bottom">
{ sections.length > 1 ?
<div className="List-section-header px2 py1 cursor-pointer full flex align-center" onClick={() => this.toggleSection(sectionIndex)}>
{ this.renderSectionIcon(section, sectionIndex) }
<h4>{section.name}</h4>
<span className="flex-align-right">
<Icon name={openSection === sectionIndex ? "chevronup" : "chevrondown"} width={12} height={12} />
</span>
</div>
:
<h4 className="px2 py1 text-default">{section.name}</h4>
}
</div>
: null }
{ openSection === sectionIndex ?
<ul style={{maxHeight: 400}} className="p1 border-bottom scroll-y">
{section.items.map((item, itemIndex) => {
return (
<li key={itemIndex} className={cx("List-item flex", { 'List-item--selected': this.itemIsSelected(item) })}>
{this.renderItem(item, itemIndex)}
</li>
)
})}
</ul>
: null }
</section>
)}
</div>
);
}
}
AccordianList.propTypes = {
sections: PropTypes.array.isRequired,
onChange: PropTypes.func,
sectionIsSelected: PropTypes.func,
itemIsSelected: PropTypes.func,
renderItem: PropTypes.func,
renderSectionIcon: PropTypes.func
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment