Перейти к содержанию

Гриды — Справочник фильтров

Справочник соответствия типов колонок грида и применяемых фильтров.

Справочник описывает, какой фильтр применяется для каждого типа колонки в гриде задач и в ДП Таблица: текстовые фильтры, фильтры по пользователям, по подзадачам и вопросам, по дополнительным параметрам, а также фильтры колонок ДП Таблица и особую обработку сквозных ДП (Through).

Два контекста фильтрации

Каждый фильтр работает в одном или обоих контекстах:

  • Грид задач — фильтрация списка задач категории;
  • ДП Таблица — фильтрация строк внутри табличного дополнительного параметра.

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

Фильтр колонки в ДП Таблица: операторы «Содержит», «Начинается с», «Пусто» и другие

Фильтры гридов задач

Требования к имени колонки

Имя колонки, по которой строится критерий отбора, проходит проверку на допустимость перед привязкой к SQL. Разрешены обычные идентификаторы (в том числе квалифицированные — до трёх частей через точку, в скобочной или кавычечной форме) длиной до 400 символов. Одиночные внутренние дефисы допустимы — их содержат имена ДП планировщика ресурсов.

Не проходят проверку: пробелы, скобки и прочие спецсимволы внутри имени, сдвоенные и хвостовые дефисы. Последствие зависит от пути: в критерии отбора невалидное имя приводит к тому, что критерий молча отбрасывается (фильтр перестаёт влиять на выборку), на пути получения безопасного имени колонки — к ошибке «Filter '…' has an invalid column name for SQL criteria».

Симптом для поддержки: фильтр настроен, но выборка его игнорирует, либо запрос падает с этой ошибкой — проверить имя колонки в настройке фильтра.

Текстовые и поисковые

Фильтры по текстовым полям задачи:

Колонки Тип фильтра Описание
TaskText, Description TextFilter Фильтр по тексту задачи. Поддерживает contains, startsWith, endsWith, equals. Также используется для быстрого фильтра.
SubcategoryName TextFilter Фильтр по названию категории. Условие «равно» обслуживает SubcategoryTextFilter: категория ищется по названию, а если у источника уже задан список категорий — среди них, и берётся первая найденная. В обычном режиме фильтр сужает SubcatIds до этой категории, у источника со сводными категориями вдобавок добавляет узел FlexExpr с условием $TASKS$.SubcatID = N. В расширенном поиске SubcatIds не меняется: в дерево фильтров добавляется только узел FlexExpr, поэтому условие участвует в логике «и/или» наравне с остальными. Если категория по названию не нашлась, в расширенном поиске запрос завершается ошибкой GridNotSupportedOperationException (кроме быстрого фильтра), в остальных режимах фильтр не применяется.
PerformerOrgUnit PerformerOrgUnitFilter Фильтр по орг. единице исполнителя. Принимает текстовый фильтр с операциями «равно», «содержит», «начинается с», «заканчивается на», «не равно», «не содержит», «пусто», «не пусто». В хранимой процедуре колонке соответствует Tasks.DeptName.

Колонка «Орг. единица исполнителя» обслуживается отдельным билдером и сравнивает не значение колонки, а результат подзапроса по идентификаторам задач: от ответственных исполнителей берётся основное назначение в оргструктуре, от него — родительское подразделение, имя которого и сопоставляется с введённым значением (с учётом локализации на язык сессии). Для обычного фильтра колонки подзапрос добавляется в CustomFlex, то есть отбор выполняется до основного запроса ShowTasksFeed и в дереве FilterNode такой фильтр не отображается. В быстром и расширенном фильтрах подзапрос попадает в дерево как flex-выражение TaskID in (…), для отрицающих операций — not in. Пустое значение фильтра не применяется, операции вне перечисленного списка приводят к ошибке «Filter '…' not supported for performerOrgUnit».

Пользователи и роли

Фильтры по участникам задачи:

Колонки Тип фильтра Описание
Owner, OwnerName SetFilter Фильтр по заказчику задачи.
Performer SetFilter Фильтр по единственному исполнителю.
Performers SetFilter Фильтр по множественным исполнителям.
Acceptors SetFilter Фильтр по согласующим.
ExtParam{id} (SelectUsers type) SetFilter Фильтр для ДП "Выбор пользователей/групп".

Подзадачи и вопросы

Числовые фильтры по подзадачам и вопросам:

Колонки Тип фильтра Описание
Subtasks NumberFilter Фильтр по наличию подзадач.
ActiveSubtasks NumberFilter Количество активных подзадач.
TotalSubtasks NumberFilter Общее количество подзадач.
MyQuestions NumberFilter Мои вопросы в задаче.
QuestionsToMe NumberFilter Вопросы адресованные мне.
QuestionsFromMe NumberFilter Вопросы от меня.

ДП и специальные

Фильтры по дополнительным параметрам и специальные фильтры:

Колонки Тип фильтра Описание
ExtParam{id} (MultiSelect types) SetFilter Универсальный фильтр для ДП с множественным выбором (MultiSlctSubcatTasks, MultiCheckbox, etc.).
ExtParam{id} (LookUpField) SetFilter / TextFilter Фильтр для lookup ДП. Фильтрует по связанной задаче.
ExtParam{id} (LookUpField, MultiSlctSubcatTasks) в режиме «Выбор значений» TaskIdSetFilter Отбор по идентификаторам выбранных задач (SelectedTaskID), а не по отображаемому тексту. Клиент присылает filterType = taskIdSet, список отмеченных taskIds либо список снятых excludeTaskIds (режим «всё, кроме снятых» у колонки Лукап) и признак blank для отбора задач без значения ДП. Подробнее — в разделе «Маппинг: тип ДП → фильтр».
Любая SmartFilter Фильтр через smart-выражение (ESQL).
Signatures* SetFilter Фильтры по подписям задач.
Custom AdvancedFilter Произвольный гибкий фильтр.
Table columns SetFilter Мультивыбор строк в ДП Таблица. Работает в обоих контекстах.

Пустой набор значений в SetFilter. Набор с пустой строкой (values: [""]) отбирает строки без значения, а пустой набор (values: []) не подходит ни одной строке: сервер подставляет заведомо ложное условие 1 = 0 (FilterHelpers.CreateAlwaysFalseFilter), и таблица возвращает ноль строк. В гриде задач и в виртуальных колонках ДП Таблица правило действует всегда; внутри ДП Таблица оно применяется только к фильтрам, пришедшим в filterModel от клиента, а служебные фильтры группировки по-прежнему строят условие «нет значения». Колонка состояния разбирается раньше остальных: пустой набор в фильтре state даёт ранний выход с пустым ответом, это поведение не менялось. Интерфейс пустой набор не отправляет: при снятии всех отметок фильтр колонки обычно снимается целиком. Исключение — фильтр «Выбор значений» у колонки «Ссылка»: состояние «ничего не отмечено» уходит как taskIds: [], то есть отбор на ноль строк, а повторный клик по «Выбрать все» снимает отбор совсем.

Фильтры колонок ДП Таблица

Эти фильтры работают только в контексте ДП Таблица:

Тип колонки Тип фильтра Описание
Lookup-колонка в таблице TextFilter Текстовый поиск по значению lookup-связи внутри ДП Таблица (по тексту связанной задачи и отображаемым полям справочника), как в гриде списка задач; не ограничен числом записей справочника. Зависит от LookupParamSettings.
Виртуальная (вычисляемая) колонка TextFilter / NumberFilter Текстовые операции ищут по отображаемому значению колонки — тому, что видно в таблице, а не по идентификатору связанной задачи. Может не поддерживать все операции.
"Выбор пользователей" в таблице SetFilter Фильтр по пользователям/группам внутри ДП Таблица.

Маппинг: тип ДП → фильтр

Какой фильтр применяется для каждого типа дополнительного параметра:

Тип ДП Грид задач ДП Таблица Примечание
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 Только внутри ДП Таблица

Фильтр по дате и по дате-времени. Колонки «дата» и «дата и время» фильтруются штатным agDateColumnFilter AG Grid. Если время задано, операторы сравнивают дату вместе с ним; если время не указано — сравнение идёт по дате. Время вводится с точностью до минуты: секунды в пикере скрыты, поэтому условие «18:29» находит и значение 18:29:37, а полночь означает «время не задано». Оператор по умолчанию — «Равно», год в пикере ограничен диапазоном 1000–9999. Набор операторов зависит от источника данных грида: в гридах задач доступны «Равно», «Не равно», «Меньше», «Больше» и «Диапазон», а на формах — только «Равно» и «Диапазон», потому что на формах сервер отбрасывает время и остальные операторы дали бы неверную выборку. Отбор «Нет значения» и «Есть значение» доступен в обоих случаях.

Время по умолчанию и суточная семантика «Равно». При выборе дня в календаре и по кнопке «Сегодня» в поле времени подставляется 00:00, а не текущее время. Так как полночь в фильтре означает «время не задано», условие «Равно» без набранного времени отбирает записи за весь выбранный день: клиент разворачивает его в интервал от 00:00:00 этого дня до 00:00:00 следующего, то есть берёт сутки полуинтервалом — последняя секунда дня в отбор попадает. Границы «Диапазона» разворачиваются так же, поэтому конечный день входит в отбор целиком, а не до последней минуты суток. Сам фильтр при этом не переписывается: в сохранённом представлении и в чипе над таблицей условие остаётся «Равно» с 00:00. Набранное вручную время суточное правило не отменяет — условие «12:30» сравнивается по минуте. У колонок «только дата» поля времени нет, и фильтр по-прежнему сравнивает даты.

Пустое поле — это отсутствие значения. При выводе колонки поле пустое, текущая дата в него не подставляется; пустое поле означает, что значение не задано, а не полночь текущих суток. Повторная установка той же даты после очистки применяется так же, как первая: очистка сбрасывает состояние фильтра, поэтому значение с датой и временем 00:00 отличается от пустого состояния, и отбор запускается без перезагрузки страницы.

Суточная семантика «Не равно». Правило «полночь означает время не задано» разворачивает в сутки и утвердительные условия, и отрицательное, но результат зависит от типа колонки. По колонке «только дата» условие «Не равно» исключает выбранный день целиком. По колонке «Дата и время» действует то же правило, что и для «Равно»: день, выбранный в календаре без набранного времени, грид отправляет операцией notInRange — отрицанием полуинтервала от начала дня до полуночи следующих суток (контракт операции — в backend.md). В интерфейсе условие при этом остаётся «Не равно» с 00:00, а не превращается в отдельный пункт списка операций. «Не равно» с набранным вручную временем (например, 12:30) уходит как notEqual и исключает только эту минуту.

Если у колонки Лукап или Мультилукап выбран тип фильтра «Выбор значений» (для обоих типов он же и по умолчанию, второй возможный — «Текстовый»), отбор идёт по самим выбранным задачам, а не по их отображаемому тексту. Клиент присылает фильтр с дискриминатором 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 (пустой список после префикса, нечисловой остаток) фильтром не считается: условие не строится, в журнал уходит предупреждение.

Дерево 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.