Skip to content
Snippets Groups Projects
Commit 406b87cd authored by Tom Robinson's avatar Tom Robinson
Browse files

Conditional formatting: add contains, does not contain, starts with, ends with

parent 757c0c8d
No related branches found
No related tags found
No related merge requests found
...@@ -14,7 +14,6 @@ import NumericInput from "metabase/components/NumericInput"; ...@@ -14,7 +14,6 @@ import NumericInput from "metabase/components/NumericInput";
import { SortableContainer, SortableElement } from "react-sortable-hoc"; import { SortableContainer, SortableElement } from "react-sortable-hoc";
import MetabaseAnalytics from "metabase/lib/analytics"; import MetabaseAnalytics from "metabase/lib/analytics";
import { capitalize } from "metabase/lib/formatting";
import { isNumeric, isString } from "metabase/lib/schema_metadata"; import { isNumeric, isString } from "metabase/lib/schema_metadata";
import _ from "underscore"; import _ from "underscore";
...@@ -22,21 +21,25 @@ import d3 from "d3"; ...@@ -22,21 +21,25 @@ import d3 from "d3";
import cx from "classnames"; import cx from "classnames";
const NUMBER_OPERATOR_NAMES = { const NUMBER_OPERATOR_NAMES = {
"<": t`less than`, "<": t`is less than`,
">": t`greater than`, ">": t`is greater than`,
"<=": t`less than or equal to`, "<=": t`is less than or equal to`,
">=": t`greater than or equal to`, ">=": t`is greater than or equal to`,
"=": t`equal to`, "=": t`is equal to`,
"!=": t`not equal to`, "!=": t`is not equal to`,
"is-null": t`null`, "is-null": t`is null`,
"not-null": t`not null`, "not-null": t`is not null`,
}; };
const STRING_OPERATOR_NAMES = { const STRING_OPERATOR_NAMES = {
"=": t`equal to`, "=": t`is equal to`,
"!=": t`not equal to`, "!=": t`is not equal to`,
"is-null": t`null`, "is-null": t`is null`,
"not-null": t`not null`, "not-null": t`is not null`,
contains: t`contains`,
"does-not-contain": t`does not contain`,
"starts-with": t`starts with`,
"ends-with": t`ends with`,
}; };
const ALL_OPERATOR_NAMES = { const ALL_OPERATOR_NAMES = {
...@@ -293,7 +296,7 @@ const RuleDescription = ({ rule }) => ( ...@@ -293,7 +296,7 @@ const RuleDescription = ({ rule }) => (
{rule.type === "range" {rule.type === "range"
? t`Cells in this column will be tinted based on their values.` ? t`Cells in this column will be tinted based on their values.`
: rule.type === "single" : rule.type === "single"
? jt`When a cell in these columns is ${( ? jt`When a cell in these columns ${(
<span className="text-bold"> <span className="text-bold">
{ALL_OPERATOR_NAMES[rule.operator]} {rule.value} {ALL_OPERATOR_NAMES[rule.operator]} {rule.value}
</span> </span>
...@@ -352,7 +355,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => { ...@@ -352,7 +355,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => {
)} )}
{rule.type === "single" ? ( {rule.type === "single" ? (
<div> <div>
<h3 className="mt3 mb1">{t`When a cell in this column is…`}</h3> <h3 className="mt3 mb1">{t`When a cell in this column…`}</h3>
<Select <Select
value={rule.operator} value={rule.operator}
onChange={e => onChange({ ...rule, operator: e.target.value })} onChange={e => onChange({ ...rule, operator: e.target.value })}
...@@ -360,7 +363,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => { ...@@ -360,7 +363,7 @@ const RuleEditor = ({ rule, cols, isNew, onChange, onDone, onRemove }) => {
{Object.entries( {Object.entries(
isNumericRule ? NUMBER_OPERATOR_NAMES : STRING_OPERATOR_NAMES, isNumericRule ? NUMBER_OPERATOR_NAMES : STRING_OPERATOR_NAMES,
).map(([operator, operatorName]) => ( ).map(([operator, operatorName]) => (
<Option value={operator}>{capitalize(operatorName)}</Option> <Option value={operator}>{operatorName}</Option>
))} ))}
</Select> </Select>
{hasOperand && isNumericRule ? ( {hasOperand && isNumericRule ? (
......
...@@ -21,7 +21,19 @@ type SingleFormat = { ...@@ -21,7 +21,19 @@ type SingleFormat = {
type: "single", type: "single",
columns: ColumnName[], columns: ColumnName[],
color: Color, color: Color,
operator: "<" | ">" | "<=" | ">=" | "=" | "!=" | "is-null" | "not-null", operator:
| "<"
| ">"
| "<="
| ">="
| "="
| "!="
| "is-null"
| "not-null"
| "contains"
| "does-not-contain"
| "starts-with"
| "ends-with",
value: number | string, value: number | string,
highlight_row: boolean, highlight_row: boolean,
}; };
...@@ -142,6 +154,34 @@ function compileFormatter( ...@@ -142,6 +154,34 @@ function compileFormatter(
return v => (v === null ? color : null); return v => (v === null ? color : null);
case "not-null": case "not-null":
return v => (v !== null ? color : null); return v => (v !== null ? color : null);
case "contains":
return v =>
typeof value === "string" &&
typeof v === "string" &&
v.indexOf(value) >= 0
? color
: null;
case "does-not-contain":
return v =>
typeof value === "string" &&
typeof v === "string" &&
v.indexOf(value) < 0
? color
: null;
case "starts-with":
return v =>
typeof value === "string" &&
typeof v === "string" &&
v.startsWith(value)
? color
: null;
case "ends-with":
return v =>
typeof value === "string" &&
typeof v === "string" &&
v.endsWith(value)
? color
: null;
} }
} else if (format.type === "range") { } else if (format.type === "range") {
const columnMin = name => const columnMin = name =>
......
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