Из общего обзора — в конкретные детали одним кликом (Deep-linking фильтров как альтернатива Drill-Through в Microsoft  Power BI, Tableau и Qlik).

За один клик из сводного дашборда — на «дочерний» с уже выставленными фильтрами. Разберём, как в Superset прокидывать выбранные значения через URL-параметр native_filters в формате Rison и собирать ссылку Jinja-макросами.

Каждый, кто работал с дашбордами, знает эту боль: смотришь на сводный отчёт, находишь интересный сегмент — и дальше приходится вручную проставлять те же фильтры в отдельном дашборде с детализацией. В Superset можно обойтись без этого рутинного «прокликивания»: достаточно один раз настроить deep-linking с помощью формата Rison и шаблонов Jinja. Тогда переход в дочерний дашборд будет происходить уже с выбранным контекстом (страна, клиент, продукт, временной период и т. д.). Такой deep-linking заметно ускоряет аналитику и делает работу с данными гораздо удобнее. По сути, это  аналог Drill-Through, давно ставшего стандартом в коммерческих BI-системах — лидерах рынка — Microsoft Power BI, Tableau и Qlik , хотя в Superset требуется больше усилий для его настройки.

На этом коротком видео показан некий родительский дашборд (cars_rison_dash(parent)) , на котором можно выбрать значения различных фильтров (в том числе кросс-фильтров) и после клика на кнопку «Перейти в детализированный дашборд» происходит переход в более детализированный дочерний дашборд (cars_rison_dash(child)) с пробросом значений фильтров из родительского дашборда.

Далее в статье на примере этих двух дашбордов рассмотрим как все это работает на практике с небольшими отступлениями в теорию.

Тот, кто захочет повторить может скачать себе датасет cars отсюда: https://anonymfile.com/BXZKQ/cars.rar  (на нем для простоты построены все чарты обоих дашбордов).

Итак, немного теории.

Что такое rison-формат и для чего он используется в Superset

Rison — это компактный формат сериализации данных, который похож на JSON, но гораздо более удобен для использования в URL.

Достаточно посмотреть на следующие примеры, чтобы понять основы Rison.

В JSON массив выглядит так:

[«Canada», «China»]

В Rison то же самое запишется компактно:

!('Canada','China')

Объект в JSON:

{

«name»: «Lacazette»,

«age»: 27,

«stats»: {

«appearances»: 20,

«goals»: 7,

«assists»: 5

}

}

В Rison:

(name:Lacazette,age:27,stats:(appearances:20,goals:7,assists:5))

В Rison скобки (...) обозначают объект , который в JSON указывается в фигурных скобках {…}, а восклицательный знак с круглыми скобками !(...) в Rison обозначает массив, который в JSON указывается в квадратных скобках […]. 

Строки можно писать без кавычек, если они не содержат пробелов или специальных символов вроде :, ().

Главное удобство Rison в том, что такие строки можно без проблем вставлять в URL как параметры запроса — без дополнительного кодирования. Конечно, если внутри окажутся «опасные» символы, их придётся закодировать. Но сама спецификация Rison специально придумана так, чтобы свести к минимуму необходимость кодирования. Более того, некоторые символы, которые в обычных URL пришлось бы экранировать, в Rison остаются «чистыми» и читаемыми.

Для примера рассмотрим один и тот же упрощенный адрес страницы с параметром в Rison в сравнении с этим же адресом страницы со стандартным URI (Uniform Resource Identifier)-кодированием пробелов, кавычек, запятых и проч.

Чистая Rison-строка в адресе страницы:

http://localhost:8089/superset/dashboard/67/?native_filters=(country:!('Canada','China'),year:2024)   

Эта строка удобно читается и видно, что в параметре native_filters  прописано , что фильтруется country по двум значениям 'Canada','China' , а год year=2024.

Та же самая строка с URI-кодированием в адресе страницы:

http://localhost:8089/superset/dashboard/67/?native_filters=%28country%3A%21%28%27Canada%27%2C%27China%27%29%2Cyear%3A2024%29

Здесь:

  • ( превратился в %28,

  • : в %3A,

  • ! в %21,

  • , в %2C,

  • ' в %27,

  • ) в %29.

Как видно, закодированный вариант громоздкий и плохо читается. Поэтому Rison и ценен: он подобран так, чтобы по максимуму избежать лишнего кодирования и оставить URL компактным и понятным не только машине, но и человеку.

Теперь рассмотрим для чего Rison используется в Superset.

В Apache Superset этот формат в контексте нашей задачи используется для передачи состояния фильтров через URL. Ключевой параметр здесь — native_filters:

  • в нём закодирован выбранный пользователем контекст (например, страна, продукт, временной диапазон и другие фильтры дашборда);

  • Rison позволяет уместить всю структуру в одну строку без «раздувания» URL.

Иными словами: вы вставляете Rison-строку в параметр native_filters, браузер передаёт её как текст, а Superset на стороне фронтенда «знает», что в native_filters лежит именно Rison, и умеет её правильно распарсить и далее применять все переданные параметры фильтров для дашборда.

Благодаря этому можно формировать deep-links: ссылки, которые открывают дочерний дашборд с уже предустановленными фильтрами. 

Подытоживая теоретическую часть по Rison, стоит отметить, что существуют библиотеки- парсеры Rison для языков программирования: JavaScript, Python, Golang и др.

Сравнение синтаксисов Rison vs JSON представлено в таблице ниже.

Страница на гитхаб с подробным описанием формата https://github.com/Nanonid/rison

Что такое Jinja Template и для чего это используется в Apache Superset.

Второй и заключительный теоретический блок касается механизма прокидывания значений фильтров дашборда в  SQL – запрос.

В Apache Superset для этого используется популярный шаблонизатор на Python Jinja. Он позволяет динамически подставлять значения фильтров дашборда прямо в SQL датасета. Благодаря этому один и тот же SQL -запрос становится «живым» — он адаптируется под выбор пользователя на фильтрах дашборда.

По сути, Jinja в Superset — это слой между интерфейсом дашборда и SQL:

  • пользователь кликает фильтр;

  • выбранные значения через макросы Jinja (filter_values, get_filters, from_dttm, to_dttm и другие) попадают в SQL;

  • Далее в контексте нашей задачи SQL генерирует результат или строку с параметрами для Rison, которая затем используется в deep-linking.

Актуальная документация по SQL-шаблонизации Jinja доступна на официальном сайте Superset:

? https://superset.apache.org/docs/configuration/sql-templating/

Хорошие разборы и практические примеры можно найти здесь:
? https://preset.io/blog/intro-jinja-templating-apache-superset/

Синтаксис Jinja минималистичный:

  • {{ ... }} — вывод переменной или выражения;

  • {% ... %} — управляющие конструкции (условия, циклы, переменные);

  • {# ... #} — комментарии.

В целях нашей задачи мы будем использовать следующие встроенные в Superset макросы Jinja

1) filter_values(name) → list  : [‘value1’ ,… , ’valueN’].

Что делает: возвращает питоновский список выбранных значений нативного фильтра дашборда типа Value. Параметр name внутри макроса filter_values – это имя колонки датасета, по которой сделан фильтр.
Когда использовать: когда нужно подставить значения IN (...) или собрать массив для Rison.

2) get_filters(name) → list[dict]

Что делает: возвращает список условий по фильтру со следующими деталями: оператор (op) и значение (val). Подходит для сложных случаев: диапазонные фильтры типа Numerical Range. Параметр name внутри макроса get_filters – это имя колонки датасета, по которой сделан фильтр.

Зачем: можно гибко разобрать, какая именно грань/оператор выбран на Numerical Range -фильтре, и собрать корректный Rison-узел или SQL-условие.

3) from_dttm / to_dttm → str (строки дат)

Что делает: предоставляют нижнюю/верхнюю границы выбранного пользователем временного диапазона (фильтр типа Time Range) в формате, готовом к подстановке в SQL.

4) time_grain → str (ISO-8601 длительность)

Что делает: содержит выбранный time grain (агрегационный шаг времени), например P1D (день), P1W (неделя), P1M (месяц), PT1H (час) и т. д.

Зачем: можно отобразить человекочитаемую подпись или передать time_grain ниже по конвейеру (например, в extraFormData Rison).

5) Вспомогательное: namespace(...) (Jinja-фича)

Что делает: создаёт мутабельный контейнер для накопления значений внутри цикла.

Зачем: Jinja по умолчанию не даёт «вынести» значения из цикла простым set; namespace помогает аккуратно собирать состояние.

Ну, довольно теории — пора перейти к рассмотрению практического примера.

Формулировка потребностей практического кейса:

  • мы хотим перейти из одного дашборда к другому дашборду с уже установленными фильтрами;

  • мы хотим поделиться своим дашбордом с кем-то с сохраненными фильтрами.

Вариант реализации в Superset от 3й версии и выше (все скрипты в этом кейсе протестированы и реализованы в версиях 4 и 5)

Допустим, у нас есть несколько нативных фильтров и мы хотим прокинуть выбранные значения в параметры ссылки на дашборд, чтобы при переходе по этой ссылке открывался сразу отфильтрованный дашборд.

Для фильтрации нам нужно подставить параметр native_filters в URL- дочернего дашборда следующим образом:

(подробное описание механизма тут : https://www.blef.fr/superset-filters-in-url/ ):

Тогда произойдёт перенаправление на страницу дочернего дашборда с параметром native_filters_key в URL. Это означает, что выбранные вами в родительском дашборде фильтры были сохранены в таблице метаданных key_value.

Структура Rison-параметра native_filters

Когда мы прокидываем состояние фильтров через URL, Superset кодирует их в параметре native_filters=(...). Внутри каждого блока встречаются одинаковые ключи, отвечающие за разные части состояния.

__cache (в нашем кейсе мы не будем использовать этот ключ)

Внутренний кэш состояния фильтра. Содержит «человекочитаемую» подпись (label) и текущее значение (value). Superset использует это, чтобы быстрее восстановить интерфейс фильтра при открытии дашборда.

extraFormData

Технический блок, где описывается, какое условие реально применять в SQL.

  • filters → список словарей вида (col:, op:<оператор>, val:<значения>).

  • Именно отсюда Superset понимает, какую колонку фильтровать, каким оператором и какими значениями.

filterState

Главный блок состояния фильтра. Содержит:

  • label — подпись для отображения в UI (обычно совпадает с выбранным значением или их списком),

  • value — собственно выбранные значения,

  • validateStatus — служебный флаг проверки корректности состояния (!f = false, то есть валидация отключена).

ownState — зарезервированная часть для локальных состояний и расширений. Обычно остаётся пустой (), но Superset оставляет её для будущих фич (например, сложных композитных фильтров).

Ключевые элементы внутри блоков

  • <id> — уникальный идентификатор фильтра на дашборде (NATIVE_FILTER-...). Генерируется Superset автоматически. Используется для связи Rison-записи с конкретным фильтром.

Вы можете получить NATIVE_FILTER-<id> , в свойствах дочернего дашборда или в консоли разработчика веб-браузера (описано ниже)

  • <column>— имя колонки в источнике данных (датасете), к которой будет применяться фильтр. Важно: это имя колонки, а не название фильтра в UI. (Чтобы избежать путаницы можно задавать название фильтра в UI в панели фильтров , такое же как и имя колонки, по которой этот фильтр создан)

  • validateStatus — статус валидации. Если !f (false), Superset не проверяет дополнительно выбранное состояние.

  • val или <value> — список выбранных значений фильтра. В Rison-массиве это выглядит как !('Canada','China').

  • op — оператор сравнения (IN, =, >=, <=, IS NULL, BETWEEN и др.). Определяет логику фильтрации.

В результате Superset получает полное описание фильтра: и то, что нужно отобразить пользователю в интерфейсе (__cache, filterState), и то, что нужно реально применить к данным в SQL (extraFormData).

Вот как выглядит ссылка в формате RISON с учетом выбранных в родительском дашборде в панели фильтров значений.

На скрине цвет прямоугольника вокруг фильтра, совпадает с цветом выделения текста в Rison-ссылке ниже , где указаны параметры фильтра.

Где еще можно посмотреть id-фильтров и ключ/значения , которые Superset использует для передачи состояния фильтров дашборда?

В консоли разработчика вашего веб-браузера.

Зайдите в консоль разработчика веб-браузера, выставьте нужные значения фильтров в панели фильтров детского (!) дашборда  и нажмите Apply filters. В консоли разработчика найдите вкладку Network  и среди прочих запросов найдите  запрос , который начинается на filter_state и во вкладке payload этого запроса можно увидеть json , который описывает состояние фильтров дашборда (id -фильтров, названия колонок и выбранные значения). Этот json тоже можно использовать как образец для формирования ссылки в формате rison.  

Итак, мы получили валидную ссылку в формате RISON.

Как использовать сформированный URL ?

Для того , чтобы сформированная вами ссылка в формате RISON стала кликабельной на дашборде её необходимо обернуть в HTML-тэг <a href="ваша ссылка в формате RISON"> текст ссылки </a> Пример таблицы с кликабельной ссылкой может выглядеть так:

SELECT *, '<a href="' "ваша ссылка в формате RISON" '">' 'имя ссылки , которое будет видно пользователю на дашборде' '</a>' as link

Идем дальше.

Создаем два дашборда:

  • родительский дашборд , то есть тот дашборд, от которого мы будем прокидывать фильтры в детский дашборд.

Например такой:

  • детский дашборд , то есть тот дашборд , на который мы будем прокидывать фильтры с родительского дашборда.

Например такой:

Фильтры в обоих дашбордах одинаковые.

В данном примере мы будем работать со всеми пятью типами фильтров , которые возможно установить на дашборде в Superset.

На дашбордах установлены следующие фильтры:

  • фильтр типа Numerical range по столбцу amount_pct

  • 2 фильтра типа Value по столбцам country и car

  • фильтр типа Time range, в котором выбирается период дат для фильтрации столбца с датой.

  • фильтр типа Time grain, в котором выбирается гранулярность (день, месяц, год, квартал и т. д..) для группировки столбца с датой.

  • фильтр типа Time column*, в котором выбирается один из двух столбцов с датой, к которому будет применяться фильтрация и группировка по датам . В нашем датасете есть два столбца с датами: date и date_of_shipment

*фильтр Time column родительского дашборда сделан на вспомогательном датасете под названием rison_time_column с одной колонкой time_column_rison  и двумя строками со значениями date и date_of_shipment и имеет тип Value , так как в версии Superset 5.0 макрос jinja time_column не всегда работает как ожидается и поэтому для получения значения фильтра  time_column в родительском дашборде используется макрос filter_values.

  • Скрипт датасета rison_time_column на основе которого создан фильтра time_column в родительском дашборде:

Значения выбранные в фильтрах родительского дашборда будут проброшены в параметры URL дочернего дашборда в RISON-формате и таким образом мы получим ссылку на дочерний дашборд с сохранением всех выбранных на родительском дашборде фильтров.

Ниже краткий обзор родительского и дочернего дашбордов.

На скрине представлен родительский дашборд, в котором на панели фильтров выбраны

некоторые значения для всех возможных типов фильтров ( Values, Numerical range, Time column, Time grain, Time range) .

Эти выбранные в фильтрах значения проброшены параметрами в ссылку URL в формате RISON, ведущую на дочерний дашборд.

Дочерний дашборд при переходе по параметризованной ссылке из родительского дашборда наследует все значения всех фильтров , установленных на родительском дашборде (здесь мы принимаем во внимание , что на дочернем дашборде в панели фильтров установлены точно такие же фильтры, как на родительском дашборде).

Приступаем к завершительной части.

Конструктор чарта родительского дашборда , который содержит нашу ссылку в формате rison выглядит так:

Датасет лежащий в основе этого чарта имеет название rison_test и следующий код:

-- базовый URL дочернего дашборда; к нему допишем rison-параметр ?native_filters=(...) --

-- ссылка на дочерний дашборд

{% set host_link = 'http://localhost:8089/superset/dashboard/68/' %}

 

-- ====== COUNTRY (values) ====== --

-- читаем список выбранных стран из нативного фильтра 'country' (если пусто — []) --

{% set country_values = filter_values('country') or [] %}

-- склеиваем массив в строку вида 'A','B','C' для rison-формата val:!('A','B','C') --

{% set joined_country_values = ("'" ~ (country_values|join("'',''")) ~ "'")

    if (country_values and not (country_values|length == 1 and country_values[0] == ''))

    else '' %}

-- флаг: есть ли что отправлять по country --

{% set has_country = joined_country_values|length > 0 %}

-- ID фильтра в дочернем дашборде --

{% set country_filter_id = 'NATIVE_FILTER-KCSiou_mmLo7GC0HhDgg2' %}

-- имя поля, применяемого в extraFormData.filters (col:...) --

{% set country_filter_col = 'country' %}

 

-- ====== CAR (values) ====== --

-- список выбранных значений фильтра 'car' --

{% set car_values = filter_values('car') or [] %}

-- склейка в 'Ford','BMW' ...; если фильтр пуст — оставляем ''. --

{% set joined_car_values = ("'" + (car_values|join("'',''")) + "'")

    if (car_values and not (car_values|length == 1 and car_values[0] == ''))

    else '' %}

-- флаг наличия значений --

{% set has_car = joined_car_values|length > 0 %}

-- ID и колонка для блока car --

{% set car_filter_id = 'NATIVE_FILTER-jnWaKxTGzde9QKlaWkhFw' %}

{% set car_filter_col = 'car' %}

 

-- ====== AMOUNT_PCT (numerical range) ====== --

-- «сырые» условия числового фильтра (список словарей с op/val) --

{% set amount_values = get_filters('amount_pct') %}

-- контейнер для границ (>=, <=) --

{% set ns = namespace(amt_gte=none, amt_lte=none) %}

-- разбираем условия и сохраняем нужные значения --

{% for f in amount_values %}

  {% if f.get('op') == '>=' %}{% set ns.amt_gte = f.get('val') %}

  {% elif f.get('op') == '<=' %}{% set ns.amt_lte = f.get('val') %}

  {% endif %}

{% endfor %}

-- флаги наличия левой/правой границ --

{% set has_amt_gte = ns.amt_gte is not none %}

{% set has_amt_lte = ns.amt_lte is not none %}

-- фильтр активен, если задана хотя бы одна граница --

{% set has_amt = has_amt_gte or has_amt_lte %}

-- ID фильтра и имя колонки на детском дашборде --

{% set amount_pct_filter_id = 'NATIVE_FILTER-9mhVcLwwwcgcQGr9AAz6U' %}

{% set amount_pct_filter_col = 'amount_pct' %}

 

-- ====== TIME RANGE (строго обе даты или пусто) ====== --

-- системные переменные Superset: начало/конец временного интервала --

{% set tr_start = from_dttm or none %}

{% set tr_end   = to_dttm   or none %}

-- ID и «ключ» для extraFormData --

{% set time_range_filter_id = 'NATIVE_FILTER-d1X38vvpWK7xi0TsRk2CD' %}

{% set time_range_filter_col = 'time_range' %}

-- считаем валидным только если заданы обе границы --

{% set has_tr = (tr_start is not none) and (tr_end is not none) %}

-- итоговая строка в формате 'start : end' для rison --

{% set tr_value = tr_start ~ ' : ' ~ tr_end if has_tr else '' %}

 

-- ====== TIME GRAIN ====== --

-- выбранный time grain (ISO-8601 длительность) или '' --

{% set tg = time_grain or '' %}

-- ID и ключ для extraFormData --

{% set time_grain_filter_id = 'NATIVE_FILTER-hfCh5sDNmhZqhx3uOWiVZ' %}

{% set time_grain_filter_col = 'time_grain_sqla' %}

-- активен ли фильтр --

{% set has_tg = tg|length > 0 %}

-- словарь для человекочитаемых подписей к кодам grain --

{% set tg_label_map = {

  'PT1S':'Second',

  'PT2S':'2 second',

  'PT5S':'5 second',

  'PT15S':'15 second',

  'PT30S':'30 second',

  'PT1M':'Minute',

  'PT5M':'5 minute',

  'PT10M':'10 minute',

  'PT15M':'15 minute',

  'PT0.5H':'30 minute',

  'PT1H':'Hour',

  'P1D':'Day',

  'P1W':'Week',

  'P1M':'Month',

  'P3M':'Quarter',

  'P1Y':'Year'

} %}

-- читаем подпись для UI (если нет в словаре — оставляем исходный код) --

{% set tg_label = tg_label_map.get(tg, tg) %}

 

-- ====== TIME COLUMN (granularity_sqla) ====== --

-- берём выбранную пользователем «колонку времени» из спец. фильтра-датасета --

{% set tc = filter_values('time_column_rison')[0] or [] %}

-- активен ли фильтр time column --

{% set has_tc = tc|length > 0 %}

-- ID фильтра и ключ для extraFormData --

{% set time_column_filter_id = 'NATIVE_FILTER-UEUKhTtR9gYdgLoXK5eyi' %}

{% set time_column_filter_col = 'granularity_sqla' %}

 

-- ====== SQL: вспомогательный CTE с min/max для amount_pct ====== --

WITH min_max_amt AS (

  SELECT MIN(amount_pct) AS amt_min, MAX(amount_pct) AS amt_max

  FROM cars

),

-- ====== SQL: сборка ссылки с rison в CTE filters ====== --

filters AS (

SELECT

    -- стартуем со статического URL; далее при необходимости допишем ?native_filters=(...) --

    '{{ host_link }}'

    -- открываем rison, только если есть хотя бы один активный фильтр --

    {% if has_country or has_car or has_amt or has_tr or has_tg or has_tc %}

    || '?native_filters=('

 

      -- --- COUNTRY --- --

      {% if has_country %}

      || '{{ country_filter_id }}'

      || ':(extraFormData:(filters:!((col:{{ country_filter_col }},op:IN,val:!('

      '''' {{ joined_country_values }} '''' '))))'

      || ',filterState:(label:''{{ country_values|join(",") }}'',value:!('

      '''' {{ joined_country_values }} '''' ')),ownState:())'

      {% endif %}

 

      -- --- CAR --- --

      {% if has_car %}{% if has_country %} || ',' {% endif %}

      || '{{ car_filter_id }}'

      || ':(extraFormData:(filters:!((col:{{ car_filter_col }},op:IN,val:!('

      '''' {{ joined_car_values }} '''' '))))'

      || ',filterState:(label:''{{ car_values|join(",") }}'',value:!('

      '''' {{ joined_car_values }} '''' ')),ownState:())'

      {% endif %}

 

      -- --- AMOUNT_PCT (>= / <=) --- --

      {% if has_amt %}{% if has_country or has_car %} || ',' {% endif %}

      || '{{ amount_pct_filter_id }}'

      || ':(extraFormData:(filters:!('

        {% if has_amt_gte %}|| '(col:{{ amount_pct_filter_col }},op:''>='',val:' {{ ns.amt_gte }} ')'{% endif %}

        {% if has_amt_gte and has_amt_lte %} || ',' {% endif %}

        {% if has_amt_lte %}|| '(col:{{ amount_pct_filter_col }},op:''<='',val:' {{ ns.amt_lte }} ')'{% endif %}

      || '))'

      || ',filterState:('

        {% if has_amt_gte and has_amt_lte %}

           'value:!(' {{ ns.amt_gte }} ',' {{ ns.amt_lte }} '),label:' '''' {{ ns.amt_gte }} ' ≤ x ≤ ' {{ ns.amt_lte }} ''''

        {% elif has_amt_gte %}

           'value:!(' {{ ns.amt_gte }} ',' mm.amt_max '),label:' '''' {{ ns.amt_gte }} ' ≤ x ≤ ' mm.amt_max ''''

        {% else %}

           'value:!(' mm.amt_min ',' {{ ns.amt_lte }} '),label:' '''' mm.amt_min ' ≤ x ≤ ' {{ ns.amt_lte }} ''''

        {% endif %}

      || '),ownState:())'

      {% endif %}

 

      -- --- TIME RANGE (обе даты) --- --

      {% if has_tr %}{% if has_country or has_car or has_amt %} || ',' {% endif %}

      || '{{ time_range_filter_id }}'

      ':(extraFormData:(time_range:' '''' '{{ tr_value }}' '''' || '),'

      'filterState:(value:' '''' '{{ tr_value }}' '''' || '),ownState:())'

      {% endif %}

 

      -- --- TIME GRAIN --- --

      {% if has_tg %}{% if has_country or has_car or has_amt or has_tr %} || ',' {% endif %}

      || '{{ time_grain_filter_id }}'

      ':(extraFormData:({{ time_grain_filter_col }}:' '''' '{{ tg }}' '''' || '),'

      'filterState:(label:' '''' '{{ tg_label }}' '''' ',value:!(''' '{{ tg }}' || ''')),ownState:())'

      {% endif %}

 

      -- --- TIME COLUMN --- --

      {% if has_tc %}{% if has_country or has_car or has_amt or has_tr or has_tg %} || ',' {% endif %}

      || '{{ time_column_filter_id }}'

      ':(extraFormData:({{ time_column_filter_col }}:' '''' '{{ tc }}' '''' || '),'

      'filterState:(value:!(''' '{{ tc }}' '''' ')),ownState:())'

      {% endif %}

 

    || ')'

    {% endif %}

    AS link_plain

FROM min_max_amt mm

)

-- финальный селект: отдаём «сырую» ссылку и HTML-якорь для клика --

SELECT *,

  '<a href="' link_plain '" target="_blank">link</a>' AS link

FROM filters;

В коде представленном выше формируется таблица, в которой в столбце link_plain формируется ссылка на дочерний дашборд в формате RISON и кликабельная ссылка  '<a href="' link_plain '" target="_blank">link</a>' в столбце link.

На этом всё.

Спасибо, что дочитали до конца! Надеюсь, статья будет полезной.

Комментарии (0)