From 76f98ff9cb49ef4b3f77c36e7bbb92e50d1de3fd Mon Sep 17 00:00:00 2001 From: Tom Robinson <tlrobinson@gmail.com> Date: Thu, 13 Sep 2018 12:10:13 -0700 Subject: [PATCH] Add markdown_template column setting, replaces prefix/suffix --- frontend/src/metabase/lib/formatting.js | 35 ++++++++++++------- .../components/settings/ChartSettingInput.jsx | 3 +- .../visualizations/lib/settings/column.js | 20 +++++++---- .../visualizations/visualizations/Scalar.jsx | 13 +++++-- package.json | 1 + yarn.lock | 4 +++ 6 files changed, 53 insertions(+), 23 deletions(-) diff --git a/frontend/src/metabase/lib/formatting.js b/frontend/src/metabase/lib/formatting.js index d026a665fbc..77684b6f81c 100644 --- a/frontend/src/metabase/lib/formatting.js +++ b/frontend/src/metabase/lib/formatting.js @@ -7,6 +7,9 @@ import Humanize from "humanize-plus"; import React from "react"; import { ngettext, msgid } from "c-3po"; +import Mustache from "mustache"; +import ReactMarkdown from "react-markdown"; + import ExternalLink from "metabase/components/ExternalLink.jsx"; import { @@ -437,21 +440,29 @@ function formatStringFallback(value: Value, options: FormattingOptions = {}) { return value; } +const MARKDOWN_RENDERERS = { + // eslint-disable-next-line react/display-name + link: ({ href, children }) => ( + <ExternalLink href={href}>{children}</ExternalLink> + ), +}; + export function formatValue(value: Value, options: FormattingOptions = {}) { - const { prefix = "", suffix = "", jsx } = options; const formatted = formatValueRaw(value, options); - if (prefix || suffix) { - if (jsx && typeof formatted !== "string") { - return ( - <span> - {prefix} - {formatted} - {suffix} - </span> - ); + if (options.markdown_template) { + if (options.jsx) { + // inject the formatted value as "value" and the unformatted value as "raw" + const markdown = Mustache.render(options.markdown_template, { + value: formatted, + raw: value, + }); + return <ReactMarkdown source={markdown} renderers={MARKDOWN_RENDERERS} />; } else { - // $FlowFixMe: formatted will always be a string if jsx = false but flow doesn't know that - return `${prefix}${formatted}${suffix}`; + // FIXME: render and get the innerText? + console.warn( + "formatValue: options.markdown_template not supported when options.jsx = false", + ); + return formatted; } } else { return formatted; diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.jsx index c5bdbe249f2..f7705f1931a 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.jsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.jsx @@ -1,7 +1,8 @@ import React from "react"; -const ChartSettingInput = ({ value, onChange }) => ( +const ChartSettingInput = ({ value, onChange, ...props }) => ( <input + {...props} className="input block full" value={value} onChange={e => onChange(e.target.value)} diff --git a/frontend/src/metabase/visualizations/lib/settings/column.js b/frontend/src/metabase/visualizations/lib/settings/column.js index 5561f08c65a..6127c0161ba 100644 --- a/frontend/src/metabase/visualizations/lib/settings/column.js +++ b/frontend/src/metabase/visualizations/lib/settings/column.js @@ -1,4 +1,5 @@ import { t } from "c-3po"; +import _ from "underscore"; import ChartSettingColumnSettings from "metabase/visualizations/components/settings/ChartSettingColumnSettings"; @@ -168,13 +169,12 @@ export const NUMBER_COLUMN_SETTINGS = { }; const COMMON_COLUMN_SETTINGS = { - prefix: { - title: t`Add a prefix`, - widget: "input", - }, - suffix: { - title: t`Add a suffix`, + markdown_template: { + title: t`Markdown template`, widget: "input", + props: { + placeholder: "{{value}}", + }, }, }; @@ -190,7 +190,13 @@ export function getSettingDefintionsForColumn(column) { export function getComputedSettingsForColumn(column, storedSettings) { const settingsDefs = getSettingDefintionsForColumn(column); - return getComputedSettings(settingsDefs, column, storedSettings); + const computedSettings = getComputedSettings( + settingsDefs, + column, + storedSettings, + ); + // remove undefined settings since they override other settings when merging object + return _.pick(computedSettings, value => value !== undefined); } export function getSettingsWidgetsForColumm( diff --git a/frontend/src/metabase/visualizations/visualizations/Scalar.jsx b/frontend/src/metabase/visualizations/visualizations/Scalar.jsx index bbc3b0d14fd..cf1a5bbf4fc 100644 --- a/frontend/src/metabase/visualizations/visualizations/Scalar.jsx +++ b/frontend/src/metabase/visualizations/visualizations/Scalar.jsx @@ -17,13 +17,20 @@ import _ from "underscore"; import type { VisualizationProps } from "metabase/meta/types/Visualization"; -function getLegacyScalarSettings(settings) { - return _.chain(settings) +// convert legacy `scalar.*` visualization settings to format options +function legacyScalarSettingsToFormatOptions(settings) { + const o = _.chain(settings) .pairs() .filter(([key, value]) => key.startsWith("scalar.") && value !== undefined) .map(([key, value]) => [key.replace(/^scalar\./, ""), value]) .object() .value(); + // `prefix`/`suffix` replaced with `markdown_template` + if (o.prefix || o.suffix) { + o.markdown_template = `${o.prefix || ""}{{value}}${o.suffix || ""}`; + delete o.prefix, o.suffix; + } + return o; } export default class Scalar extends Component { @@ -133,7 +140,7 @@ export default class Scalar extends Component { const column = cols[0]; const formatOptions = { - ...getLegacyScalarSettings(settings), + ...legacyScalarSettingsToFormatOptions(settings), ...settings.column(column), jsx: true, }; diff --git a/package.json b/package.json index f39b8d74ce6..c88f81d2da5 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "leaflet.heat": "^0.2.0", "lodash.memoize": "^4.1.2", "moment": "2.19.3", + "mustache": "^2.3.2", "node-libs-browser": "^2.0.0", "normalizr": "^3.0.2", "npm": "^5.8.0", diff --git a/yarn.lock b/yarn.lock index 1a7e08ab460..e3777a20bb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7185,6 +7185,10 @@ multicast-dns@^6.0.1: dns-packet "^1.0.1" thunky "^0.1.0" +mustache@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" + mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" -- GitLab