State Explorer: как исследовать историю состояний в React
Я вернулся к старой идее из Azure и сделал из неё открытый React-компонент. Вся соль — в маленьком окошке снизу.
Представьте: API всё утро работал нормально, потом пять минут сбоил, а затем снова работал как ни в чём не бывало. На графике за шесть часов этот небольшой участок легко проглядеть. В таблице будут точные отметки времени, но придётся ещё разобраться, как эти пять минут соотносятся со всей остальной историей.
Хотелось видеть и общую картину, и подробности интересующего участка. Причём одновременно, без постоянного переключения туда-сюда.
Для этого я использовал две связанные временные шкалы: сверху - подробный вид, снизу - обзор всей истории. Окно на нижней шкале определяет, какой участок показан на верхней.
Можно сразу покрутить живое демо: подвигайте окно, потяните за левый или правый край, наведите курсор на интервал. Там есть 3 примера - состояния сервисов, этапы сборки и состояния устройств. Компонент во всех случаях один и тот же.
Лупа для временной шкалы
На обзорной шкале снизу виден весь заданный диапазон времени. Синее окно внутри него отвечает за период, который мы рассматриваем подробнее.
Тянем за середину — перемещаемся по истории, сохраняя длительность видимого участка. Тянем за край — меняем эту длительность. Второй край при этом остаётся на месте. Верхний график перестраивается, и короткий участок с изменившимся состоянием становится проще рассмотреть.
По ощущениям — будто водишь лупой по временной шкале. Только у этой лупы есть ручки по краям, и со стола она, к счастью, не укатывается.
При наведении на интервал появляется подсказка: состояние, исходное время начала и окончания. Те же сведения доступны при фокусе с клавиатуры. Это важно, если интервал частично выходит за пределы выбранного периода: в подсказке остаются его настоящие границы, а не только видимый кусочек.
В демо кнопка Reset view возвращает начальный диапазон. У окна есть и клавиатурное управление — таскать его мышкой необязательно.
Идея, которая тянется с 2015 года
В 2015 году я подключился к внутреннему проекту Azure. Он собирал логи, метрики и ошибки ресурсов, в том числе виртуальных машин. Нужна была страница доступности, и я постоянно возвращался к одному вопросу: как показать историю за целый день, но при этом дать возможность разобраться с коротким сбоем?
Я довольно надолго увяз в этой задаче. Даже пропустил поездку с друзьями в Калифорнию на День благодарения — всё возился с интерфейсом. В итоге сделал двухуровневую шкалу на jQuery и D3.js: полная история снизу, выбранный участок крупнее сверху. Позже проект перешёл на Angular, и компонент пришлось собрать заново уже под него.
Так выглядел ранний Availability Explorer. Его упрощённая самостоятельная версия и демо сохранились на GitHub.
В 2021 году я узнал, что компонентом всё ещё пользуются. Приятный сюрприз: код за это время менялся, а сам способ навигации оказался живучим.
State Explorer — новая реализация той же идеи, уже для разных историй состояний. Сервисы могут работать нормально или сбоить, сборки — переходить между этапами, устройства — появляться в сети и пропадать. Смысл состояний и цвета задаёт приложение.
Внутри два пакета. TypeScript-ядро @state-explorer/core проверяет временные диапазоны и проецирует интервалы в относительные координаты. @state-explorer/react рисует SVG-шкалу и обзорный навигатор. Сущности, интервалы и оформление задаёт приложение.
Подключаем в React
Оба пакета опубликованы в npm. Устанавливаем их в приложение на React 18 или 19:
npm install @state-explorer/core @state-explorer/react
Ниже небольшой пример: один API и пятиминутный период деградации. Компонент Example нужно вывести в вашем React-приложении обычным способом.
import { useState } from "react";
import { StateTimeline } from "@state-explorer/react";
import type { Entity, StateInterval, TimeRange } from "@state-explorer/core";
const at = (time: string) => Date.parse(`2026-09-10T${time}:00Z`);
const domain: TimeRange = { start: at("08:00"), end: at("14:00") };
const entities: Entity[] = [{ id: "api", label: "API" }];
const intervals: StateInterval<"healthy" | "degraded">[] = [
{ entityId: "api", start: at("08:00"), end: at("10:20"), state: "healthy" },
{ entityId: "api", start: at("10:20"), end: at("10:25"), state: "degraded" },
{ entityId: "api", start: at("10:25"), end: at("14:00"), state: "healthy" },
];
export function Example() {
const [viewport, setViewport] = useState<TimeRange>({
start: at("10:00"), end: at("12:00"),
});
return (
<StateTimeline
entities={entities}
intervals={intervals}
domain={domain}
viewport={viewport}
onViewportChange={setViewport}
label="API state history"
getStateAppearance={interval => interval.state === "healthy"
? { fill: "#167d73", label: "Healthy" }
: { fill: "#b87916", label: "Degraded" }}
/>
);
}Обзорная шкала охватывает период с 08:00 до 14:00, а в подробном виде сначала показан период с 10:00 до 12:00. Все времена здесь — UTC. Пятиминутный участок начинается в 10:20.
Два диапазона заданы отдельно: domain — вся история на обзорной шкале, viewport — участок, который сейчас рассматриваем. Если не передать domain, останется только подробный вид.
Диапазоном просмотра управляет приложение. Когда пользователь двигает окно или меняет его ширину, компонент вызывает onViewportChange с предлагаемыми границами. В примере setViewport сразу принимает их, после чего обновляются подробный график и окно на обзорной шкале.
Приложение может ограничить или вообще отклонить предложенный диапазон. Пока новые значения не пришли обратно через viewport, график никуда не сдвинется. Это удобно, например, если загружена история только за определённый период.
В Quick Start есть пример побольше — с двумя сущностями.
Где пригодится и чего пока не умеет
State Explorer пригодится там, где несколько объектов со временем переходят между дискретными состояниями. Например: «работает / деградирует / недоступен», «в очереди / собирается / развёрнут», «в сети / не в сети».
Данные нужно передать самому. Компонент не ходит за ними на сервер и не решает, что означает пробел в истории. Нет интервала — остаётся пробел. Состояния разных объектов он показывает на общей временной оси.
Пока это ранняя версия, API ветки 0.1.x экспериментальный. Агрегации и виртуализации нет. Производительность на больших объёмах данных пока не проверена. Проверки на реальных сенсорных устройствах, в других браузерных движках и с программами экранного доступа тоже ещё впереди. На очень короткий интервал бывает сложно попасть мышью. Подсказки могут обрезаться у края экрана или внутри контейнера с обрезкой содержимого.
Мне интересно, где такая штука окажется полезной, а где будет только мешать. Попробуйте демо, загляните в исходники и документацию или откройте issue со своим случаем. Особенно любопытно, какие истории состояний вам приходится разбирать и как вы сейчас это делаете.