Гриды — Справочник фильтров¶
Справочник соответствия типов колонок грида и применяемых фильтров.
Справочник описывает, какой фильтр применяется для каждого типа колонки в гриде задач и в ДП Таблица: текстовые фильтры, фильтры по пользователям, по подзадачам и вопросам, по дополнительным параметрам, а также фильтры колонок ДП Таблица и особую обработку сквозных ДП (Through).
Два контекста фильтрации¶
Каждый фильтр работает в одном или обоих контекстах:
- Грид задач — фильтрация списка задач категории;
- ДП Таблица — фильтрация строк внутри табличного дополнительного параметра.
Окно выбора задачи по ДП «Ссылка» и «Множественная ссылка» относится к первому контексту, но с оговоркой по правам: категории, задачи которых оно показывает, в параметры выборки попадают позже проверки доступа, поэтому для критерия доступа к колонкам ДП они подставляются отдельно — из описания лукапа, из контекста вызова или из категории-владельца. В саму выборку, сортировку и агрегаты этот набор не пишется. Если ни один из источников категорий не сработал, набор остаётся пустым и операции по любой колонке ДП запрещены; для сводного раздела параметр считается закрытым, если он закрыт хотя бы в одной из категорий-источников.
Общий контракт отбора по параметрам в представлениях. Отбор по дополнительным параметрам и основным колонкам в «Канбане», «Календаре», «Ресурсах» и «Ленте» собирается единым контрактом: по идентификаторам выбранных значений, а подпись значения хранится отдельно и нужна только для чипа над списком. У диаграммы Ганта та же кнопка фильтров, что и в канбане: список полей строится по настройкам табличного вида категории и включает как основные колонки, так и дополнительные параметры, а выбранные условия уходят в запрос данных Ганта. Табличное представление категории в этот набор не входит: оно остаётся на собственной фильтрации грида.

Фильтры гридов задач¶
Требования к имени колонки¶
Имя колонки проверяется дважды, и правила у этих проверок разные.
Проверка входа запроса. Имена колонок сортировки и ключи фильтров, приходящие в запросе, проверяются до проверки на признаки SQL-инъекций: имя должно быть не длиннее 128 символов и не содержать апострофа, точки с запятой, --, /*, */, управляющих символов и переводов строк. Пробел, кириллица, запятая, скобки, точка, слеш и дефис допустимы — под них заведены живые имена колонок. Не прошедшее имя отклоняет запрос целиком, а не очищается. Проверка одинакова для табличного представления, ленты задач, поиска по ленте, произвольных источников данных и фильтра Ганта; пустая колонка сортировки — и строка из одних пробелов — означает отсутствие сортировки и проверку не проходит; такой элемент сортировки отбрасывается, а запрос обрабатывается по остальным элементам.
Проверка критерия отбора. Имя колонки, по которой строится критерий отбора, проверяется перед привязкой к SQL белым списком идентификаторов: допускаются обычные и квалифицированные имена (до трёх частей через точку, в скобочной или кавычечной форме) длиной до 400 символов, одиночные внутренние дефисы разрешены — их содержат имена параметров планировщика ресурсов. Не проходят: пробелы, скобки и прочие спецсимволы внутри имени, сдвоенные и хвостовые дефисы. Последствие зависит от пути: в критерии отбора невалидное имя приводит к тому, что критерий молча отбрасывается (фильтр перестаёт влиять на выборку), на пути получения безопасного имени колонки — к ошибке «Filter … has an invalid column name for SQL criteria».
Симптом для поддержки: фильтр настроен, но выборка его игнорирует — проверить имя колонки в настройке фильтра; таблица или лента не грузится сразу после правки запроса, отбора или вида — проверить имя колонки в запросе: недопустимое имя теперь отклоняет запрос, а не очищается.
Фильтры колонок ДП Таблица¶
Эти фильтры работают только в контексте ДП Таблица:
| Тип колонки | Тип фильтра | Описание |
|---|---|---|
| Lookup-колонка в таблице | TextFilter |
Текстовый поиск по значению lookup-связи внутри ДП Таблица (по тексту связанной задачи и отображаемым полям справочника), как в гриде списка задач; не ограничен числом записей справочника. Зависит от LookupParamSettings. |
| Виртуальная (вычисляемая) колонка | TextFilter / NumberFilter |
Текстовые операции ищут по отображаемому значению колонки — тому, что видно в таблице, а не по идентификатору связанной задачи. Может не поддерживать все операции. |
| "Выбор пользователей" в таблице | SetFilter |
Фильтр по пользователям/группам внутри ДП Таблица. |
Общее меню фильтров по дополнительным параметрам (канбан, календарь, ресурсы, гант, лента)¶
Одно и то же меню фильтров работает в представлениях категории — канбане, календаре, ресурсах, ганте и ленте (а также в табличном представлении, где оно встроено в тулбар): оно строит список доступных полей, даёт выбрать поле и операцию и превращает выбранное значение в модель запроса.
Меню открывается в модальном окне, и на всех экранах, где есть кнопка фильтра, — сводный букинг, режим задач, виджет портала, лента событий, поиск по всем задачам, канбан, динамические виджеты, — открывается одинаково. Если по кнопке фильтра экран только затемняется, а окна нет (в консоли браузера при этом ошибка NG0201), это дефект, а не настройка фильтра.
Список доступных полей. В меню попадают и дополнительные параметры категории, и основные поля задачи, на которые у пользователя есть права просмотра. Список строится по названию поля, состав основных полей берётся из настроек колонок табличного представления; поле доступно для фильтрации только при поддерживаемом типе, а набор операций ограничен допустимыми для этого поля.
Ключ строки меню. Строка меню различается составным ключом «источник — категория — поле»: один и тот же ДП в двух категориях даёт разные строки меню и отдельное состояние, поэтому выбор фильтра в одной категории не подсвечивается в другой. В модель запроса уезжает прежний ключ: ExtParam{id} для дополнительного параметра и имя поля для основной колонки представления.
Смена категории. При переключении на другую категорию без перезагрузки страницы меню очищает выбранные чипы (в том числе уже применённый фильтр) и перечитывает список доступных полей для новой категории.
Формы запроса. Значение фильтра превращается в одну из форм filterModel:
| Вид фильтра | Форма запроса |
|---|---|
| Текст | { filterType: 'text', type, filter } |
| Число | { filterType: 'number', type, filter } |
| Дата | { filterType: 'date', type, dateFrom, dateTo? } |
| Справочник и мультисправочник | { filterType: 'text', type: 'equals', filter: '<подписи>', customInfo: { option: '<префикс>:<id,id>' } } |
| Флажок | { filterType: 'set', values } |
Операции без значения (blank, notBlank) уезжают формой поля; пустой фильтр в запрос не попадает. Для справочника подписи идут в filter, идентификаторы — в customInfo.option с префиксом: any: — достаточно совпадения с любым из выбранных значений, all: — в задаче должны быть все выбранные значения (мультисправочник).
Маппинг: тип ДП → фильтр¶
Какой фильтр применяется для каждого типа дополнительного параметра:
| Тип ДП | Грид задач | ДП Таблица | Примечание |
|---|---|---|---|
String, MultiLineString |
TaskTextFilter (или стандартный) |
стандартный | Текстовый |
Number, Decimal, Money |
стандартный | стандартный | Числовой |
DateTime |
agDateColumnFilter |
agDateColumnFilter |
Дата и время: штатный фильтр даты AG Grid — время можно указать в самом фильтре, год ограничен диапазоном 1000–9999. Подробности — ниже |
Boolean |
стандартный | стандартный | Чекбокс |
LookUpField |
LookupFilter |
LookupColumnFilter |
Lookup |
MultiSlctSubcatTasks |
MultiSelectFilter |
стандартный | Мультилукап |
SelectUsersType, SelectGroupsType, SelectOrgUnit |
SelectUsersExtParamFilters |
SelectUsersColumnFilter |
Выбор пользователей |
MultiCheckbox, DropDown |
MultiSelectFilter |
стандартный | Множественный выбор |
Table |
Ограничено (не через ShowTasksFeed) |
N/A (Table — это сам грид) | Фильтрация задач возможна через HTML-представление при настройке шаблона. См. support-guide §1.3 |
Through |
Особая обработка | N/A | Сквозной ДП: действующий тип сервер берёт у параметра-источника в конце цепочки и по нему готовит значение фильтра, но в хранимую процедуру передаёт тип Through — процедура сама определяет целевой тип и применяет правильную логику. Замена на SelectUsersOrgUnits/SelectUsersGroups происходит только при группировке, не при фильтрации. Колонка, которая ссылается на удалённый или недоступный параметр, фильтр не ломает: условие по ней не применяется, остальные условия работают как обычно. Если у сквозного параметра источник не определяется и колонка доступна, фильтр прерывается сообщением о неверной настройке сквозного параметра — цепочку правит администратор. Типичные применения: Through → ДП «Выбор пользователей» (Менеджер клиента, РП, BDM и др.) |
File |
ИСКЛЮЧЁН | N/A | Файловый ДП |
Numerator |
стандартный | стандартный | Нумератор |
Virtual (колонка таблицы) |
N/A | VirtualColumnFilter |
Только внутри ДП Таблица |
Тип фильтра как настройка колонки. В настройках табличного вида категории тип фильтра колонки — самостоятельный параметр: в сохранённых настройках колонки может лежать явно выбранный тип, а не только выведенный из типа ДП. Допустимый набор значений зависит от типа ДП колонки (см. таблицу в business.md, раздел «Тип фильтра колонки»); при чтении сохранённого вида значение вне допустимого набора отбрасывается и подставляется значение по умолчанию. Тип «Двойной выбор значений» строится на тех же операторах, что и одиночный фильтр, но с двумя условиями и переключателем «И» / «ИЛИ».
Фильтр по дате и по дате-времени. Колонки «дата» и «дата и время» фильтруются штатным agDateColumnFilter AG Grid. Если время задано, операторы сравнивают дату вместе с ним; если время не указано — сравнение идёт по дате. Время вводится с точностью до минуты: секунды в пикере скрыты, поэтому условие «18:29» находит и значение 18:29:37. Оператор по умолчанию — «Равно», год в пикере ограничен диапазоном 1000–9999. Набор операторов зависит от источника данных грида: в гридах задач доступны «Равно», «Не равно», «Меньше», «Больше» и «Диапазон», а на формах — только «Равно» и «Диапазон», потому что на формах сервер отбрасывает время и остальные операторы дали бы неверную выборку. Отбор «Нет значения» и «Есть значение» доступен в обоих случаях.
Обратный диапазон не применяется и объясняется на языке интерфейса. Если верхняя граница диапазона раньше нижней, отбор не выполняется, а под полем фильтра выводится подсказка на языке интерфейса; дата в ней показана в привычном формате приложения. Подсказка — не часть модели фильтра: её текст и формат даты задаёт платформа (см. frontend.md, раздел «Локализация подписей грида»), а не библиотека. Набор операторов и логика выборки при этом не меняются: условия с верными границами применяются как раньше.
День, выбранный без времени, — это сутки. При выборе дня в календаре и по кнопке «Сегодня» в поле времени подставляется 00:00, а не текущее время, и условие отбирает записи за весь выбранный день, а не за одну минуту. Суточную семантику задаёт клиент: условие «Равно» с полуночью он превращает в диапазон от 00:00:00 этого дня до 00:00:00 следующего, а сервер сравнивает границы ровно такими, какими их получил. Поэтому в отбор попадают сутки целиком — последняя секунда дня входит, а первые сутки следующего дня уже нет. По той же причине конечный день «Диапазона» входит целиком, а не до последней минуты суток. Сам фильтр при этом не переписывается: в сохранённом представлении и в чипе над таблицей условие остаётся «Равно» с 00:00. Набранное вручную время сравнивается точно: условие «12:30» отбирает записи этой минуты. У колонок «только дата» поля времени нет, и фильтр по-прежнему сравнивает даты.
Границы «Диапазона» по колонкам «дата и время». Диапазон работает как полуинтервал: начальная граница включается, конечная — нет. Конец дня клиент переводит в полночь следующих суток, поэтому выбранный последним день попадает в отбор целиком. Если задать обе границы одинаковыми, верхняя не включается и выборка остаётся пустой. По колонкам «только дата» конечная дата входит в диапазон целиком.
Пустое поле — это отсутствие значения. При выводе колонки поле пустое, текущая дата в него не подставляется; пустое поле означает, что значение не задано, а не полночь текущих суток. Повторная установка той же даты после очистки применяется так же, как первая: очистка сбрасывает состояние фильтра, поэтому значение с датой и временем 00:00 отличается от пустого состояния, и отбор запускается без перезагрузки страницы.
«Не равно» по выбранному дню. День, выбранный в календаре без набранного времени, исключается целиком — и по колонке «только дата», и по колонке «дата и время»: клиент отправляет отрицание отдельной операцией «не в диапазоне» с границами от начала дня до начала следующих суток, а сервер отрицает переданный полуинтервал целиком. Из выборки уходят все записи этого дня, а не только полночь; записи без значения остаются. В интерфейсе условие при этом остаётся «Не равно» с 00:00, а не превращается в отдельный пункт списка операций. «Не равно» с набранным вручную временем (например, 12:30) сравнивается точно и исключает только эту минуту.
Если у колонки Лукап или Мультилукап выбран тип фильтра «Выбор значений» (для обоих типов он же и по умолчанию, второй возможный — «Текстовый»), отбор идёт по самим выбранным задачам, а не по их отображаемому тексту. Клиент присылает фильтр с дискриминатором filterType = taskIdSet; в DTO TaskIdSetFilter помимо общих полей ColumnFilter есть собственные поля: taskIds — список числовых идентификаторов задач, blank — признак отбора задач без значения ДП (описан ниже) и labels — необязательный список подписей выбранных задач, параллельный taskIds. В построении отбора labels не участвует: сервер хранит подписи вместе с фильтром в составе представления и возвращает их при чтении без изменений, чтобы чип над таблицей показывал названия, а не номера. Запрос без labels разбирается как раньше — поле десериализуется в null. Сервер строит из него один узел фильтра: операция =, в ParamType — обозначение типа колонки, которое понимает хранимая функция: LookupValue для Лукапа и MultiSlctSubcatTasks для Мультилукапа (в кэше ДП тип Лукапа называется LookUpField; ColumnFilterService.GetFilter переименовывает его в LookupValue до разбора фильтра, поэтому в разборе taskIdSet встречается уже переименованное значение), в DecimalValue — набор идентификаторов через запятую с префиксом any: — у Лукапа и Мультилукапа одинаково: подходит задача, у которой стоит хотя бы одно из отмеченных значений (объединение по «или»), поэтому несколько отмеченных значений расширяют выборку, а не сужают её. Текстовое значение (RawValue) не заполняется. Хранимая функция сопоставляет набор с SelectedTaskID в таблице выбранных задач ExtParamValueSelectedTasks.
Перевод сохранённых настроек. Настройки таблиц категории — и административные наборы колонок, и пользовательские виды — при обновлении на 2.268 один раз переведены с типа фильтра «Текстовый» на «Выбор значений» у колонок Лукап и Мультилукап. Замена выполнена в самих сохранённых настройках, подмены при чтении нет, и колонки остальных типов не затронуты. Перевод одноразовый: если после обновления вернуть колонке тип «Текстовый» и сохранить вид, этот выбор сохраняется.
Откуда берётся список значений. Значения для выбора отдаёт отдельная точка POST api-core/datasource/{type}/{entityId}/lookup-values: список строится из фактических значений колонки у задач, которые пользователь видит в этом гриде, с тем же отбором и тем же пользователем, что и страница грида. Поэтому в списке нет значений из недоступных задач, и наоборот — права на категорию-источник для показа списка не требуются. Запрос принимает имя колонки, строку поиска и постраничные skip и take (по умолчанию 50); ответ содержит страницу значений и признак hasMore, общее число не считается. Колонка дополнительного параметра с ограничением доступа даёт пустой список, как и при группировке. Операции по такой колонке проверяются на сервере, поэтому запрет не обходится прямым запросом мимо интерфейса. Колонка не того типа, параметр вне категории или источник типа «Таблица» — ошибка 400, отсутствие прав на грид — 403.
Когда уходит запрос и где работает этот источник. Список запрашивается при первом раскрытии попапа фильтра, а не при открытии представления: у сохранённого вида с уже применённым отбором «Выбор значений» запрос уходит только когда пользователь откроет фильтр. Источник подключён у грида категории и грида раздела; остальные хосты грида берут список прежним способом — задачами категории-источника под правами пользователя на неё, поэтому там пустой список у пользователя без прав на категорию-источник остаётся штатным поведением.
Перед построением набор нормализуется: повторы схлопываются, нулевые и отрицательные идентификаторы отбрасываются. Остальные случаи дают ошибку запроса (HTTP 400), а не молчаливый показ неотфильтрованного грида:
taskIdsне передан и признакblankне выставлен — ошибка «Фильтр taskIdSet требует список TaskIds». В живом запросе данных такой фильтр не отбрасывается при разборе и доходит до этой проверки; при чтении сохранённого представления он отбрасывается санитайзером, чтобы вид открывался;- тип ДП колонки не Лукап и не Мультилукап — ошибка «Фильтр taskIdSet не поддерживается для типа ДП …»; тип проверяется раньше содержимого набора;
- после нормализации не осталось ни одного положительного идентификатора, а исходный набор был непустой — ошибка «Фильтр taskIdSet требует хотя бы один положительный TaskID». Из интерфейса такой набор не приходит: состояние «ничего не отмечено» уходит пустым списком без мусорных значений;
- идентификаторов больше предела — тело ответа содержит код
taskIdSetLimitExceededи предельное число вmaxAllowed; набор не усекается. Предел задаёт настройкаTaskIdSetFilterMaxItems, при отсутствии или неположительном значении он равен 1000. - значение поля
taskIds,labels,excludeTaskIdsилиexcludeLabelsприслано в форме, которую контракт не разбирает, — ошибка запроса400с именем колонки и поля. Разбор мягкий, как у самого контракта: строки-числа в списке идентификаторов и числа в списке подписей принимаются. Проверка идёт до обращения к данным; в чтение сохранённого представления она не попадает — негодный фильтр там отбрасывает санитайзер.
Признак «без значения ДП». Поле blank в TaskIdSetFilter включает отбор задач, у которых у ДП нет значения. Признак работает только для колонок Лукап и Мультилукап: тип ДП проверяется раньше содержимого запроса, поэтому для остальных типов запрос отклоняется той же ошибкой про неподдерживаемый тип. JSON без поля blank разбирается в false, так что для клиентов, которые признак не присылают, поведение прежнее.
blank |
taskIds |
Что строит сервер |
|---|---|---|
не передан или false |
непустой | один узел = по набору идентификаторов, как описано выше |
не передан или false |
не передан | ошибка «Фильтр taskIdSet требует список TaskIds» |
не передан или false |
передан пустой | заведомо ложное условие 1 = 0 — ноль строк: состояние «ничего не отмечено» |
true |
не передан или пустой | один узел is null по тому же ParamType — задачи без значения ДП |
true |
непустой | два узла под «или»: is null и обычный отбор по набору, с префиксом any: — у Лукапа и Мультилукапа одинаково |
true |
непустой, но после нормализации пустой | ошибка «Фильтр taskIdSet требует хотя бы один положительный TaskID» |
Семантика объединения внутри набора (any:) та же, что и без признака. Признак blank отправляет и интерфейс: в фильтре «Выбор значений» колонок «Ссылка» и «Множественная ссылка» он отвечает за пункт «(Пусто)» — отмеченный пункт уходит как blank: true, снятый в режиме «всё, кроме снятых» — как blank: false. Отбор задач без значения ДП пользователь закрывает и другим способом: тип фильтра «Текстовый», оператор «Нет значения».
Режим «всё, кроме снятых». Фильтр «Выбор значений» у колонки Лукап работает в двух режимах: «Все» — отмечено всё, кроме снятых, и «Только отмеченные» — прежнее поведение. Режим задаёт параметр фильтра колонки, который ставит только маппер колонок Лукапа; у Мультилукапа и в гриде подписей параметра нет, и режим там прежний. В режиме «Все» клиент присылает в том же TaskIdSetFilter вместо taskIds два поля: excludeTaskIds — числовые идентификаторы снятых задач (сам факт наличия поля означает режим исключений) и excludeLabels — параллельный список подписей снятых задач, он участвует только в хранении и показе чипа. taskIds и excludeTaskIds в одном фильтре не допускаются.
excludeTaskIds |
blank |
Что строит сервер |
|---|---|---|
| непустой | не передан или false |
is not null под «и» с исключением набора: строка проходит, если у ДП есть значение и ни одно её значение не входит в набор |
| непустой | true |
то же исключение под «или» с is null: строки без значения ДП остаются в выборке |
| пустой | false |
один узел is not null — только строки со значением ДП |
| пустой | true |
ошибка «Фильтр taskIdSet с пустым excludeTaskIds и blank=true не ограничивает выборку»; интерфейс такой фильтр не отправляет |
Режим исключений работает только для колонок Лукап: у Мультилукапа и в гриде подписей его нет — параметр, переводящий фильтр в «всё, кроме снятых», ставит только маппер колонок Лукапа. Оба списка сразу и набор из одних неположительных идентификаторов — ошибка запроса 400, как и прежде. Предел набора не изменился: настройка TaskIdSetFilterMaxItems, при отсутствии или неположительном значении — 1000; превышение даёт taskIdSetLimitExceeded с предельным числом в maxAllowed.
Чего режим не требует. Условие собирается штатными узлами разбора — <> с набором any: (как «Исключая значение» у текстового фильтра Лукапа) и is null / is not null, — поэтому под режим «всё, кроме снятых» новых объектов в базе и миграции не требуется.
Сохранённый вид с негодным отбором. При чтении настроек санитайзер отбрасывает ровно те taskIdSet-фильтры, на которых живой отбор ответил бы ошибкой, — иначе вид открывался бы, а первый же запрос данных по нему падал. Отбрасываются: taskIds не передан и blank не выставлен; после нормализации не осталось ни одного положительного идентификатора, а исходный набор был непустой — в обоих режимах; оба списка сразу; taskIdSet на колонке, для которой контракт не заведён (для списка отмеченных — не Лукап и не Мультилукап, для режима исключений — не Лукап); пустой excludeTaskIds с blank: true; идентификаторов больше предела. Отборы, законные на живом запросе, остаются в виде как есть, в том числе taskIds: [] без blank («ничего не отмечено», ноль строк) и пустой excludeTaskIds с blank: false (только строки со значением ДП).
«Выбрать все». Без поиска пункт переключает два состояния: «всё отмечено» и «ничего не отмечено» (ноль строк). При заданном поиске — у Лукапа и Мультилукапа — меняются только найденные значения, причём со всех страниц: клиент догружает их страницами по 500 (верхний предел точки lookup-values), обычная прокрутка списка при этом остаётся по 50. Если найденных больше 1000, отметки не меняются, а в фильтре выводится сообщение о необходимости уточнить поиск. Состояние значений вне поиска сохраняется; в режиме «Только отмеченные» новые отметки объединяются с прежним выбором.
Пункты контекстного меню на таких колонках. «Фильтр по значению» и «Исключая значение» на колонке Лукап или Мультилукап с фильтром «Выбор значений» шлют не taskIdSet, а текстовую модель: filterType — text, операция — equals или notEqual, а идентификаторы значения ячейки лежат в customInfo ({ taskId, option }). Разбирает её MultiSelectFilter (core/Valhalla.Services/AgGridServices/Filters/MultiSelectFilter.cs): список из option подменяет собой текст фильтра и уходит в условие по идентификаторам задач (DecimalValue), поэтому сравнение идёт по связанным задачам, а не по отображаемому тексту. Префикс в option задаёт режим отбора (any: при его отсутствии, all:, exact: — см. выше); Мультилукап в обоих режимах отправляет exact:<ID>,<ID> — набор значений ячейки сравнивается целиком, Лукап при исключении отправляет голый список идентификаторов. Исключение строится как «значение пустое ИЛИ условие не выполнено», поэтому строки с незаполненной ячейкой остаются в выборке. Несовместимое значение option (пустой список после префикса, нечисловой остаток) фильтром не считается: условие не строится, в журнал уходит предупреждение.
Поле taskId в текстовой модели фильтра необязательно. В модели, которая приходит из пункта меню ячейки, идентификатор задачи лежит в customInfo.taskId, но приходит он не всегда: значение может быть пустым, например у значения фильтра без задачи. Разбор ведёт отдельная модель, и её поле объявлено обнуляемым. Раньше поле было обычным целым, и такая модель роняла разбор ошибкой приведения null к числу — запрос данных грида отвечал 500. Теперь такой вход разбирается штатно: отбор строится по самому значению фильтра так же, как при заполненной задаче, — на выборку поле не влияет.
Дерево FilterNode¶
Фильтры строят дерево FilterNode:
GeneralFilter (root, AND)
├── FilterNode (column=State, operand=IN, values=[1,3])
├── FilterNode (column=ExtParam42, operand=CONTAINS, value="test")
└── GeneralFilter (OR)
├── FilterNode (column=Owner, operand=EQUALS, value=123)
└── FilterNode (column=Performer, operand=EQUALS, value=456)
Дерево передаётся в SP ShowTasksFeed / ShowExtParamTableData и транслируется в WHERE-clause.