Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
M
Metabase
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Engineering Digital Service
Metabase
Commits
99ad6c05
Unverified
Commit
99ad6c05
authored
2 years ago
by
Noah Moss
Committed by
GitHub
2 years ago
Browse files
Options
Downloads
Patches
Plain Diff
Optional blocks in Markdown cards (#24491)
parent
a2de3c7c
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
shared/src/metabase/shared/parameters/parameters.cljc
+97
-19
97 additions, 19 deletions
shared/src/metabase/shared/parameters/parameters.cljc
shared/test/metabase/shared/parameters/parameters_test.cljc
+46
-0
46 additions, 0 deletions
shared/test/metabase/shared/parameters/parameters_test.cljc
with
143 additions
and
19 deletions
shared/src/metabase/shared/parameters/parameters.cljc
+
97
−
19
View file @
99ad6c05
(
ns
metabase.shared.parameters.parameters
"Util functions for dealing with parameters"
"Util functions for dealing with parameters. Primarily used for substituting parameters into variables in Markdown
dashboard cards."
#
?
@
(
:clj
[(
:require
[
clojure.string
:as
str
]
...
...
@@ -113,36 +114,92 @@
[
text
]
(
str/replace
text
escaped-chars-regex
#
(
str
\\
%
)))
(
defn-
replacement
[
tag->param
locale
match
]
(
let
[
tag-name
(
second
match
)
param
(
get
tag->param
tag-name
)
(
defn-
value
[
tag-name
tag->param
locale
]
(
let
[
param
(
get
tag->param
tag-name
)
value
(
:value
param
)
tyype
(
:type
param
)]
(
if
value
(
when
value
(
try
(
->
(
formatted-value
tyype
value
locale
)
escape-chars
)
(
catch
#
?
(
:clj
Throwable
:cljs
js/Error
)
_
;; If we got an exception (most likely during date parsing/formatting), fallback to the default
;; implementation of formatted-value
(
formatted-value
:default
value
locale
)))
;; If this parameter has no value, return the original {{tag}} so that no substitution is done.
(
first
match
))))
(
defn-
normalize-parameter
"Normalize a single parameter by calling [[mbql.normalize/normalize-fragment]] on it, and converting all string keys
to keywords."
[
parameter
]
(
->>
(
mbql.normalize/normalize-fragment
[
:parameters
]
[
parameter
])
first
(
reduce-kv
(
fn
[
acc
k
v
]
(
assoc
acc
(
keyword
k
)
v
))
{})))
(
formatted-value
:default
value
locale
))))))
(
def
^
:private
template-tag-regex
"A regex to find template tags in a text card on a dashboard. This should mirror the regex used to find template
tags in native queries, with the exception of snippets and card ID references (see the metabase-lib function
`recognizeTemplateTags` for that regex)."
`recognizeTemplateTags` for that regex).
If you modify this, also modify `template-tag-splitting-regex` below."
#
"\{\{\s*([A-Za-z0-9_\.]+?)\s*\}\}"
)
;; Represents a variable parsed out of a text card. `tag` contains the tag name alone, as a string. `source` contains
;; the full original syntax for the parameter)
(
defrecord
^
:private
TextParam
[
tag
source
]
Object
(
toString
[
x
]
(
or
(
:value
x
)
source
)))
(
defn-
TextParam?
[
x
]
(
instance?
TextParam
x
))
(
def
^
:private
template-tag-splitting-regex
(
let
[
base
"\\{\\{\\s*[A-Za-z0-9_\\.]+?\\s*\\}\\}"
]
;; Use lookahead and lookbehind to retain matches in split
(
re-pattern
(
str
"(?<="
base
")|(?="
base
")"
))))
(
defn-
split-on-tags
"Given the text of a Markdown card, splits it into a sequence of alternating strings and TextParam records."
[
text
]
(
let
[
split-text
(
str/split
text
template-tag-splitting-regex
)]
(
map
(
fn
[
text
]
(
if-let
[[
_,
match
]
(
re-matches
template-tag-regex
text
)]
(
->TextParam
match
text
)
text
))
split-text
)))
(
defn-
join-consecutive-strings
"Given a vector of strings and/or TextParam, concatenate consecutive strings and TextParams without values."
[
strs-or-vars
]
(
->>
strs-or-vars
(
partition-by
(
fn
[
str-or-var
]
(
or
(
string?
str-or-var
)
(
not
(
:value
str-or-var
)))))
(
mapcat
(
fn
[
strs-or-var
]
(
if
(
string?
(
first
strs-or-var
))
[(
str/join
strs-or-var
)]
strs-or-var
)))))
(
defn-
add-values-to-variables
"Given `split-text`, containing a list of alternating strings and TextParam, add a :value key to any TextParams
with a corresponding value in `tag->normalized-param`."
[
tag->normalized-param
locale
split-text
]
(
map
(
fn
[
maybe-variable
]
(
if
(
TextParam?
maybe-variable
)
(
assoc
maybe-variable
:value
(
value
(
:tag
maybe-variable
)
tag->normalized-param
locale
))
maybe-variable
))
split-text
))
(
def
^
:private
optional-block-regex
#
"\[\[.+\]\]"
)
(
def
^
:private
non-optional-block-regex
#
"\[\[(.+?)\]\]"
)
(
defn-
strip-optional-blocks
"Removes any [[optional]] blocks from individual strings in `split-text`, which are blocks that have no parameters
with values. Then, concatenates the full string and removes the brackets from any remaining optional blocks."
[
split-text
]
(
let
[
s
(
->>
split-text
(
map
#
(
if
(
TextParam?
%
)
%
(
str/replace
%
optional-block-regex
""
)))
str/join
)]
(
str/replace
s
non-optional-block-regex
second
)))
(
defn
^
:export
tag_names
"Given the content of a text dashboard card, return a set of the unique names of template tags in the text."
[
text
]
...
...
@@ -152,6 +209,14 @@
#
?
(
:clj
tag-names
:cljs
(
clj->js
tag-names
))))
(
defn-
normalize-parameter
"Normalize a single parameter by calling [[mbql.normalize/normalize-fragment]] on it, and converting all string keys
to keywords."
[
parameter
]
(
->>
(
mbql.normalize/normalize-fragment
[
:parameters
]
[
parameter
])
first
(
reduce-kv
(
fn
[
acc
k
v
]
(
assoc
acc
(
keyword
k
)
v
))
{})))
(
defn
^
:export
substitute_tags
"Given the context of a text dashboard card, replace all template tags in the text with their corresponding values,
formatted and escaped appropriately."
...
...
@@ -165,4 +230,17 @@
(
assoc
acc
tag
(
normalize-parameter
param
)))
{}
tag->param
)]
(
str/replace
text
template-tag-regex
(
partial
replacement
tag->normalized-param
locale
))))))
;; Most of the functions in this pipeline are relating to handling optional blocks in the text which use
;; the [[ ]] syntax.
;; For example, given an input "[[a {{b}}]] [[{{c}}]]", where `b` has no value and `c` = 3:
;; 1. `split-on-tags` =>
;; ("[[a " {:tag "b" :source "{{b}}"} "]] [[" {:tag "c" :source "{{c}}"} "]]")
;; 2. `add-values-to-variables` =>
;; ("[[a " {:tag "b" :source "{{b}}" :value nil} "]] [[" {:tag "c" :source "{{c}}" :value 3} "]]")
;; 3. `join-consecutive-strings` => ("[[a {{b}}]] [[" {:tag "b" :source "{{c}}" :value 3} "]])
;; 4. `strip-optional-blocks` => "3"
(
->>
text
split-on-tags
(
add-values-to-variables
tag->normalized-param
locale
)
join-consecutive-strings
strip-optional-blocks
)))))
This diff is collapsed.
Click to expand it.
shared/test/metabase/shared/parameters/parameters_test.cljc
+
46
−
0
View file @
99ad6c05
...
...
@@ -185,3 +185,49 @@
"{{foo}}"
{
"foo"
{
:type
:date/month-year
:value
"2019-08"
}}
"agosto\\, 2019"
))))
(
t/deftest
substitute-tags-optional-blocks-test
(
t/testing
"Optional blocks are removed when necessary"
(
t/are
[
text
tag->param
expected
]
(
=
expected
(
params/substitute_tags
text
tag->param
))
"[[{{foo}}]]"
{}
""
"[[{{foo}}]]"
{
"foo"
{
:type
:string/=
:value
"bar"
}}
"bar"
"Customers[[ with over {{order_count}} orders]]"
{
"order_count"
{
:type
:number/=
:value
nil
}}
"Customers"
"Customers[[ with over {{order_count}} orders]]"
{
"order_count"
{
:type
:number/=
:value
10
}}
"Customers with over 10 orders"
;; Optional block is retained when *any* parameters within are substituted
"[[{{foo}} {{baz}}]]"
{
"foo"
{
:type
:string/=
:value
"bar"
}}
"bar {{baz}}"
;; Make sure `join-consecutive-strings` retains consecutive non-strings (this was a bug during implementation)
"[[{{foo}}{{foo}}]]"
{
"foo"
{
:type
:string/=
:value
"foo"
}}
"foofoo"
"[[{{foo}}]] [[{{bar}}]]"
{
"foo"
{
:type
:string/=
:value
1
}
"bar"
{
:type
:string/=
:value
2
}}
"1 2"
"[[{{foo}}]"
{
"foo"
{
:type
:string/=
:value
"bar"
}}
"[[bar]"
"[{{foo}}]]"
{
"foo"
{
:type
:string/=
:value
"bar"
}}
"[bar]]"
;; Don't strip square brackets that are in parameter values
"{{foo}}"
{
"foo"
{
:type
:string/=
:value
"[[bar]]"
}}
"\\[\\[bar\\]\\]"
)))
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment