Привет, Хабр! Меня зовут Илья Кербатов, я старший консультант в компании «ДАР» (ГК «КОРУС Консалтинг»), занимаюсь BI на Qlik Sense.

В проекте с несколькими сотнями приложений Qlik Sense я часто сталкивался с такими вопросами, как: в каких приложениях Qlik Sense используется это поле и можно ли его удалить? В каких приложениях используется эта тема? Как сделать бекап скриптов приложений, не вынося их текст в .qvs?. Попытка найти ответы на эти вопросы раньше означала, что я проведу целый день ручных «раскопок».

Чтобы сегодня отвечать на подобные вопросы за минуты, я собрал браузерную панель, которая выгружает метаданные Qlik Sense (скрипты загрузки, меры, измерения, переменные, листы, закладки, мастер-объекты, общую информацию) по одному приложению, потоку или всему серверу. В прошлой статье я рассказывал, как консольные скрипты помогли поймать баг с переменными. В этой расскажу, как из стопки таких скриптов вырос один инструмент, полезный Qlik-разработчикам и админам, которые ведут десятки приложений.

Сразу пара цифр для масштаба: полная выгрузка информации из сотни приложений занимает около двух минут при трёх параллельных соединениях. Написан инструмент целиком моделью Claude Fable 5: из примерно тысячи строк кода я не набрал руками ни одной. Код открыт и лежит на GitHub.

Панель «Kerbatov UI Qlik Downloader», запущенная поверх хаба Qlik Sense.
Панель «Kerbatov UI Qlik Downloader», запущенная поверх хаба Qlik Sense.

Откуда взялась задача

Наш проект живет в изолированном контуре заказчика. Внешних коннекторов нет, CDN нет, а установка стороннего софта практически невозможна. При этом вопросы, для которых нужны метаданные, возникают каждую неделю: где используется вот это поле, в каких приложениях осталась старая тема оформления, в каком скрипте я полгода назад оставил комментарий с номером задачи? Штатного ответа у Qlik Sense на такие вопросы нет, а проверять по очереди несколько сотен приложений руками — вариант не очень подходящий. Готовые решения не выручали. Сторонний софт в такой контур не пронести, а встроенные средства отдают метаданные по одному объекту или приложению за раз, а не срезом по всему серверу.

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

Вторая причина в интерфейсе. Панель с галками и кнопкой «Запуск» откроет любой коллега без обучения, ему не придется тратить время, чтобы вникнуть в скрипты.

Как шла разработка

Собирал я панель в веб-чате Claude с моделью Fable 5, позже доделывал в Claude Code с Opus 5 в режиме Cowork (там дали 2× more usage). Мой вклад — постановка задач, прогон на живом сервере и NDA-safe скриншоты ошибок в ответ. На весь инструмент ушло порядка шести часов и пятнадцати содержательных промптов.

Разработка шла итерациями, и почти каждая упиралась в какую-нибудь особенность Qlik Sense. Расскажу про самые показательные.

Скрипт молчит

Первая версия выгрузки превью листов просто ничего не делала. В консоли висел "promise" в состоянии "pending". И всё. Оказалось, модель не предусмотрела обработку закрытия WebSocket. Если движок рвал соединение, "promise" не завершался никогда. Я отправил скриншот консоли, в ответ получил обработчики закрытия и подробное логирование каждого шага.

Сокет закрывается с кодом 1000

С логами стало видно, что соединение открывается, проходит аутентификацию и тут же закрывается на первом запросе. Это защита от Cross-Site WebSocket Hijacking, которую Qlik включил в релизе November 2024 (есть разбор в базе знаний Qlik): движок стал требовать CSRF-токен на WebSocket-соединениях.

Здесь проект чуть не умер. В прошлый раз мы обходили эту проверку, отключая параметр WebSocketCSWSHCheckEnabled на прокси через поддержку заказчика. В этот раз менять настройку поддержка не захотела, а без этого весь инструмент терял смысл. Выручила нейросеть, которая предложила путь без вмешательств в сервер. Перед подключением панель запрашивает токен с эндпоинта /qps/csrftoken и передает его параметром qlik-csrf-token прямо в URL сокета. Ничего переключать не нужно, защита остается включенной. Так у проекта появилась вторая жизнь.

Вот суть решения в двух функциях (полный код на GitHub):

// Забираем CSRF-токен: cookie текущей сессии, без отдельной аутентификации.
async function getCsrfToken(prefix) {
  const r = await fetch(`${location.origin}${prefix}/qps/csrftoken?Xrfkey=${xrf}`, {
    headers: { 'X-Qlik-Xrfkey': xrf },
    credentials: 'include',
  });
  if (!r.ok) return null;
  for (const [k, v] of r.headers.entries()) if (/csrf/i.test(k)) return v;
  return null;
}

// Собираем URL сокета: токен параметром qlik-csrf-token + уникальный identity.
function wsUrl(appId, token, identity) {
  const q = [`Xrfkey=${xrf}`];
  if (token) q.push(`qlik-csrf-token=${encodeURIComponent(token)}`);
  return `${WS}/app/${encodeURIComponent(appId)}/identity/${identity}?${q.join('&')}`;
}

Про xrf: это случайный 16-символьный ключ, он же уходит в заголовке «X-Qlik-Xrfkey».

Описание: errors
Консоль DevTools: слева соединение падает с close code=1000, справа рабочее подключение после того, как панель стала передавать CSRF-токен.

Приложение уже открыто в другом режиме

Выгрузка скрипта падала на приложении, которое я же держал открытым в редакторе загрузки. Движок не давал открыть одно приложение в двух режимах внутри одной сессии. Редактор держал его с данными, а экспортер открывал без них. Полечилось это уникальным "identity" в адресе сокета, вида /app/{id}/identity/{random}. Теперь каждое подключение получает изолированную сессию движка и не конфликтует с открытыми вкладками.

Внезапный 403

Через пару дней панель перестала работать — запрос токена стал возвращать 403. Оказалось, я запускал ее из редактора загрузки, а адрес шел через виртуальный прокси со своим префиксом. Ранняя версия определяла префикс по словам "hub" и "sense" в адресе и про "dataloadeditor" не знала, поэтому стучалась на дефолтный прокси, где моей сессии не было. Модель переписала определение на перебор кандидатов: панель пробует префиксы, пока /qps/csrftoken не ответит 200, и дальше работает через правильный.

Интерфейс

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

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

Что умеет инструмент

Он умещается в один JS-файл без внешних зависимостей, не ходит ни на какие CDN и шлет запросы только к вашему же серверу Qlik Sense. Для закрытого контура это принципиально.

Запустить панель можно за минуту:

  1. Откройте Qlik Sense под своей учёткой (хаб, приложение или редактор загрузки, не важно).

  2. Откройте консоль DevTools клавишей F12.

  3. При первом использовании один раз разрешите вставку командой "allow pasting".

  4. Скопируйте текст JS-файла с GitHub.

  5. Вставьте содержимое файла и нажмите Enter, в углу появится панель.

  6. Выберите источник приложений, типы данных и формат и нажмите «Запуск».

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

Описание: настройки
Четыре блока настроек панели: приложения, данные, формат и дополнительно.

В режиме "Экспорт метаданных" четыре блока настроек.

  • Приложения. Четыре источника: текущее открытое приложение, список appId, все приложения потока по имени, все приложения сервера. Для потока и сервера работает маска имени с подстановочными символами, например «*Финанс*» или «Отчет_20??».

  • Восемь типов данных. Меры, измерения, переменные, листы с идентификаторами и ссылками на превью, закладки, мастер-объекты с типом визуализации, общая информация о приложении (имя, поток, тема, признак публикации, дата последней перезагрузки, превью) и скрипты загрузки целиком.

  • Формат. CSV с любым разделителем, JSON или XML. По умолчанию разделитель — вертикальная черта.

  • Дополнительно. Упаковка всех файлов в один ZIP, режим проверки без скачивания и число параллельно обрабатываемых приложений.

Галки «Отдельный файл на каждое приложение» и «Отдельный файл для каждого вида данных» комбинируются. Если обе выключены, получается один общий файл. Если обе включены — файлы на каждое приложение и каждый тип. Если часть приложений отвалилась по таймауту на нагруженном сервере, кнопка «Повтор ошибок» перегоняет только их, не трогая успешные.

Как это устроено внутри

Под капотом панель работает как клиент Engine API поверх WebSocket в формате JSON-RPC, тем же протоколом, которым говорит сам клиент Qlik Sense.

Последовательность такая:

  1. Сначала панель перебором находит рабочий префикс виртуального прокси и берет CSRF-токен с /qps/csrftoken: отдельная аутентификация не нужна, используется cookie текущей сессии браузера.

  2. На каждое приложение открывается свой WebSocket с уникальным "identity". Внутри вызывается OpenDoc с флагом qNoData, поэтому приложение открывается без данных, и объем модели почти не влияет на скорость выгрузки метаданных.

  3. Списки объектов забираются через Session Objects: CreateSessionObject с определениями VariableList, MeasureList, DimensionList и так далее.

  4. Cкрипт отдает метод GetScript одной строкой. Состав потоков и признак публикации приходят из QRS REST API одним запросом на весь прогон.

  5. Если прав на QRS нет, список приложений берется из движка методом GetDocList, но уже без информации о потоках.

Две детали, которые вручную заняли бы день, а с моделью собрались между делом. ZIP-архив пишется по спецификации формата прямо в браузере: локальные заголовки, central directory, CRC32 на чистом JS, потому что подтянуть JSZip с CDN в закрытый контур нельзя. А скрипты в JSON-выгрузке хранятся массивом строк scriptLines, потому что спецификация JSON не допускает сырых переносов внутри строкового значения, иначе код превращается в нечитаемую простыню из \r\n.

Зачем это в реальной работе

  • Найти, где используется поле или переменная. Выгрузка мер, измерений и скриптов целого потока в один CSV, дальше обычный поиск по файлу. Вопрос «можно ли выкинуть это поле из модели?» из дня «раскопок» превращается в пару минут. При скорости около двух минут на сотню приложений это реально делать по запросу, а не откладывать на потом. У меня был реальный случай, когда поле пропало в источнике, и надо было понять, где оно еще используется в скриптах. Поиск по сотням приложений занял несколько минут и выдал готовый список приложений, где нужно править скрипт, вместо ручного обхода каждого приложения.

  • Найти доработку по номеру задачи. У нас правки в скриптах помечаются комментарием с номером задачи из Jira. Выгрузка скриптов всех приложений сервера в один файл, и поиск по номеру быстро показывает, куда легла доработка двухлетней давности. В обратную сторону работает так же. Перед переносом на прод видно, во все ли нужные приложения попала правка.

  • Паспорт потока. Полная выгрузка потока в JSON с разбивкой по приложениям, готовый слепок для передачи проекта другой команде или для аудита. Полгода спустя по нему видно, что и когда поменялось.

  • Сравнение dev и prod. Можно сразу сравнить данные с двух серверов и увидеть расхождения в мерах и скриптах.

  • Альтернативный хаб. Самое неожиданное применение выросло из выгрузки общей информации и превью. Я собрал отдельный дашборд Qlik Sense и загрузил в него метаданные и превью листов примерно по 60 приложениям, которые заказчик попросил свести в один навигатор. Плюсом добавил присланную заказчиком дополнительную информацию. Получился навигатор по всем этим приложениям — сразу видно, что где лежит, как выглядит и к какому потоку относится. Не нужно открывать каждое приложение отдельно. По сути, получился альтернативный хаб поверх штатного.

Описание: Альтернативный хаб
Альтернативный хаб — приложение-навигатор с превью листов и метаданными примерно по 60 приложениям, а также с добавленной информацией от заказчика. Изображение обезличено через ИИ.

Что добавилось потом

Когда базовая выгрузка устоялась, я добавил два режима поиска подстроки прямо в панели: по скриптам (в каких приложениях и потоках встречается) и по объектам (в определениях мер, переменных и в выражениях внутри объектов листов, с разбором свойств). Это тот же кейс «где используется X», только без промежуточного CSV. Поиск по скриптам сотни приложений укладывается примерно в минуту, поиск по объектам глубже и медленнее. Те же сто приложений в три потока проходят примерно за две с половиной минуты.

Пример, ради которого этот режим и делался: надо поправить мастер-меру, и поиск по объектам сразу показывает, в каких объектах приложений она используется. Это готовый пул для тестирования, потому что видно, где отзовется правка. Оба режима поиска пока помечены как test.

Описание: Режим поиска подстроки по объектам приложений
Режим поиска: список приложений, потоков и объектов, где встречается искомая подстрока.

Когда инструмент не поможет

Панель только читает метаданные и ничего не пишет, сломать приложение ей нельзя. Но границы есть. Права остаются вашими: скрипт выгрузится там, где у учетки есть доступ, а состав потоков требует прав на чтение через QRS. Живые миниатюры листов, которые Qlik рендерит на лету, не отдает ни один API. В выгрузку попадают только заранее сохраненные превью. И это инструмент для Qlik Sense Enterprise on Windows (проверял на November 2025 Patch 3): в Qlik Cloud другой API и другая аутентификация, там панель не заработает.

Отдельно про безопасность. Отключать WebSocketCSWSHCheckEnabled на прокси теперь не нужно, потому что панель проходит проверку правильно, забирая CSRF-токен. Трогать этот параметр есть смысл, только если движок почему-то не отвечает на WebSocket. После работ обязательно возвращайте значение обратно.

Что забираю с собой

От одноразового скрипта на выгрузку одной переменной до панели, которая закрывает десяток регулярных задач, прошло несколько вечеров диалога с моделью. Сейчас панель у нас в ежедневном обиходе: вопросы, на которые раньше уходил день, закрываются за пару минут. На сами промпты уходили минуты. Почти все остальное время съедали прогоны на живом сервере и проверка выгрузок: сформулировать задачу легко, а вот убедиться, что результат верный, дольше. Руками я не написал ни одной строки.

Два наблюдения сверх самого инструмента:

  1. В закрытом контуре вайбкодинг раскрывается по-особому. Готовый софт туда не пронести, а текст скрипта проходит через почту или мессенджер, и один самодостаточный файл без зависимостей оказался удобным форматом поставки.

  2. Самый полезный промпт не «добавь фичу», а «проверь свой код и найди ошибки». Модель, которую отдельно об этом попросили, находит дефекты, которые в потоке разработки уехали бы в прод.

Умение поставить задачу нейросети и проверить результат стало для меня рабочим навыком наравне со знанием скриптов загрузки. Код лежит в открытом доступе: берите, дорабатывайте под свои задачи. Если что-то не заработает, пишите в issues на GitHub: планирую поддерживать и развивать инструмент по обратной связи и проектным задачам.

А чем метаданные Qlik вытаскиваете вы?

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