iT邦幫忙

2024 iThome 鐵人賽

DAY 16
2

https://ithelp.ithome.com.tw/upload/images/20240930/20152073pFpjchZVLC.png

前言

為了讓開發者可以處理更複雜的視覺化呈現,本篇文章將介紹 Grafana 的一些進階功能。首先,我們會介紹 addActivationHandler 函數,一個能夠輕鬆地為核心 Scenes 元件添加外部行為。接著,我們會討論如何使用 Behaviors 來實現自定義 Scenes 邏輯,這些邏輯可以在 Scenes Object 啟動時作為初始化執行。此外,我們還會介紹 URL sync 功能,這是一種能夠讓 Scenes Object 的狀態與 URL 同步的強大工具,並深入了解如何利用 SceneByFrameRepeater 進行多重的 Scenes 圖表更新。這些進階功能不僅可以提升 Grafana 的可用性,也能滿足更複雜的視覺化需求。

進階功能一 - addActivationHandler

addActivationHandlerSceneObject 中的一個函數,讓使用更自由地為 Scenes 元件添加外部行為。addActivationHandler 會在核心的 Scenes 元件或 SceneObject 被啟動執行,並返回一個停用(deactivation)的處理函數,在 SceneObject 卸載時會調用並將 addActivationHandler 中的行為清除。而一個 SceneObject 啟動的時機可能在於初渲染、因數據資料改變或使用者行為觸發,因此這個機制與 React 中的 useEffect 非常相似。

在 Scenes 的元件中也大量地使用這個函數,以 EmbeddedScene 為例,調用 addActivationHandler 做初始化處理並做清理的機制:

public constructor(state: EmbeddedSceneState) {
  super(state);

  this.addActivationHandler(() => {
    const unsetGlobalScene = setWindowGrafanaSceneContext(this);
    return () => {
      unsetGlobalScene();
    };
  });
}

💡NOTICE
在此函數功能新增之前,若要實現相同功能需要覆蓋 activate 然後調用 super.activate() 的自定義元件,實作起來複雜很多。相關更新可以參考 https://github.com/grafana/scenes/pull/77

特點

  • 生命週期管理:開發者可以在 Scenes 元件或 SceneObject 被啟用時執行特定的程式碼。適用於初始化、資料載入、設定訂閱等操作。
  • 自定義行為:開發者可以定義自己的邏輯,這些邏輯會在 Scenes 變為啟用狀態時執行。這提供了極大的客製化彈性。
  • 清理機制addActivationHandler 會返回一個清理函數,這個函數會在 Scenes 停用時自動執行。確保資源能被正確釋放,避免潛在的記憶體洩漏。

範例

以下範例為 SceneQueryRunner 的 addActivationHandler 會在 customObject 狀態變化時更新 queryRunner 的狀態,進而依據更新的狀態進行請求。其中會使用 subscribeToState 去監聽 customObject 的狀態,並在最後清除這個監聽的行為。

Grafana Scene 提供 subscribeToState 的函數為 SceneObject 監聽狀態。

const queryRunner = new SceneQueryRunner({...})

queryRunner.addActivationHandler(() => {
	// 開始監聽
  const sub = customObject.subscribeToState((newState) => {
    queryRunner.setState({
      queries: [
        { ...queryRunner.state.queries[0], seriesCount: newState.counter },
      ],
    });
    queryRunner.runQueries();
  });
	
	// 清除監聽的行為
  return () => {
    sub.unsubscribe();
  };
});

進階功能二 - Behaviors

Behaviors 可以用來實現自定義的 Scenes 邏輯,這些邏輯作為 effect 執行。就像 utils 或 hooks 一樣,你可以在這個 SceneObject 中定義任何自定義的邏輯,例如有條件地隱藏 Scenes 中的元素,或者在不同的 SceneObject 之間添加共享功能。

每一個 SceneObject 都有一個 $behaviors 屬性,可以包含多個 Behaviors 實例或函數。當父層 SceneObject 被活化時,這些 Behaviors 也會同步活化,因此可以同時取得自身以及父層的狀態。Behaviors 可以通過兩種方式來實現:

(一)純函數方式實現 Behaviors

將 Behavior 定義為一個無狀態的函數,當其父層 Object 被活化時自動調用。純函數可以返回一個清理函數,用於在父層 Object 被卸載或停用時進行清理或執行收尾操作,例如釋放資源或移除監聽器。這種無狀態特性適合用於日誌記錄、簡單事件處理或在特定生命週期點執行的一次性操作。

(二)SceneObject方式實現 Behaviors

建立一個完整的 SceneObject,擁有自己的生命週期和狀態管理,適合需要維護內部狀態或處理複雜邏輯的情況。不僅可以在活化和去活化時執行操作,還可以響應其他 Scene 事件,並根據需要更新自身狀態。例如監視狀態變化、影響父層 Object 的行為或修改其狀態。

使用方法

上述的兩種實現方法是屬於開發者自定義的模式,不過為了方便操作,Grafana Scenes 也提供了幾個已經封裝好的 API,為因應一些常用的情境,包括 ActWhenVariableChanged、CursorSync、SceneQueryController 以及 LiveNowTimer,以下為各自的使用介紹:

ActWhenVariableChanged

這個 Behavior 用於監聽 Variable 的變化並觸發相應的動作。當 SceneObject 中的 Variable 發生變化時,執行特定的操作,如重新請求數據資料或更新圖表。

此 API 有兩個屬性:指定的變數名稱 variableName 以及當變數改變時所執行的操作 onChange,其中可以在 onChange 取得兩個參數:variable 以及 behavior

  • variable:代表指定變數名稱所更新的變數。
  • behavior:代表當前的 ActWhenVariableChanged 實例,可以取得包含這個 behavior 的父層 SceneObject,也可以使用 sceneGraph 以當前的 behavior 位置查找其他的 SceneObject,例如同層的 $data、$variables、$timeRange。
  • return:如果有執行任何異步操作,例如發出查詢,則返回一個取消函數

原理

當 Scene 被活化後,這個 behavior 開始監聽指定變數的變化。每當變數值發生改變,就會觸發預設的回調函數。這個回調函數可能會返回一個取消函數,用於清理可能的異步操作。在變數再次改變之前,如果存在之前的取消函數,系統會先執行它,以確保舊的操作被適當地終止。這種機制適用於網路請求或複雜的計算操作。可以確保只有最新的操作會被完整執行,有效地管理資源並維護系統的響應性。

範例

$behaviors: [
  new behaviors.ActWhenVariableChanged({
    variableName: 'subTab',
    onChange: (variable) => {
      const value = variable.getValue();
      const subTab = subTabs.find(({ option }) => option === value);

      subTabBody.setState({ body: subTab?.getPanels() });
    },
  }),
],

CursorSync

建立一個共享的游標範圍,用於同步多個圖表的游標位置。例如在 Dashboard 上有多個 time series 圖表時,使用者在一個圖表上移動游標,其他圖表也會同步顯示相同時間點的數據。此 behavior 有一個 sync 的屬性,而 sync 的模式有三種:Crosshair、Off 及 Tooltip:

  • Crosshair:在一個圖表上移動游標時,會在有同步的游標位置顯示一個垂直和水平的交叉線條。
    https://ithelp.ithome.com.tw/upload/images/20240930/20152073NZqoRhPLQB.png

  • Tooltip:除了 Crosshair 的行為,也會同時展開顯示 tooltip。

    https://ithelp.ithome.com.tw/upload/images/20240930/20152073VALfsOIblX.png

  • Off:為預設狀態,不顯示 Tooltip 也不顯示 Crosshair。

特點

  1. 同步設置:通過 sync 屬性設置同步模式,但預設為 DashboardCursorSync.Off
  2. 父層 SceneObject 依賴:不能作為獨立的 SceneObject 使用,CursorSync 需要附加到一個父層 SceneObject 上才能正常工作。
  3. 同步原理:同步事件的發布和訂閱都是通過 PanelContextEventBus 來管理的。當一個 Panel 被建立時,它會向上查詢其作用域(祖先層)是否存在 CursorSync behavior。如果找到了 CursorSync,Panel 會使用它來取得 Event Bus。CursorSync 會首先確認自身是否有父層(parent 屬性),然後創建一個新的 PanelContextEventBus 實例,將 CursorSync 的 parent 實例和 Panel 本身作為參數提供給 PanelContextEventBus 進行事件的監聽和管理。

範例

由於 CursorSync 有父層依賴的特性,所以通常會將 new behaviors.CursorSync 設置於 SceneFlexLayout 的層級:

new SceneFlexLayout({
  direction: 'column',
  $behaviors: [new behaviors.CursorSync({ key: 'metricCrosshairSync', sync: DashboardCursorSync.Crosshair })],
  children: [
    new SceneFlexItem({
      minHeight: MAIN_PANEL_MIN_HEIGHT,
      maxHeight: MAIN_PANEL_MAX_HEIGHT,
      body: new AutoVizPanel({}),
    }),
    new SceneFlexItem({
      ySizing: 'content',
      body: new MetricActionBar({}),
    }),
  ],
});

SceneQueryController

這個 Behavior 功能提供了一個中央機制來追蹤和管理所有正在進行的 Query。

原理

當 Query 被註冊時,Controller 會包裝原始的 Observable 物件,添加額外的邏輯來追蹤 Query 的運行狀態。此外還提供了取消所有 Query 的功能(cancelAll),可使用在需要快速重置 Scenes 狀態的情況。而 SceneQueryController 處理異步操作、 Query 的執行和清理,皆是使用 RxJS 的 Pipe 和訂閱機制。

範例

通常在 Scenes 中使用這個 behavior 時,會是 Query 有 lazy 的請求狀態,且 Refresh picker 的元件會有 Cancel 顯示,點擊後使用者可以取消 Query 行為:

https://github.com/grafana/scenes/pull/513

https://ithelp.ithome.com.tw/upload/images/20240930/20152073RUhL9mZ0Jg.png

$behaviors: [new behaviors.SceneQueryController()]

LiveNowTimer

LiveNowTimer 提供一個即時更新的計時器。在 Dashboard 中顯示當前時間,或者定期觸發數據 refresh。

原理

  • 啟用和禁用計時器:可以通過 enable() 和 disable() 方法來啟用或禁用計時器。
  • 定時更新:當計時器啟用時,會每隔一定時間(REFRESH_RATE)查找所有的 VizPanel 並調用它們的 forceRender() 方法,強制它們重新渲染。
  • 狀態管理:通過 isEnabled 屬性來檢查計時器是否啟用,並且使用 setState 方法來更新內部狀態。

範例

在 Scenes 中使用時建議使用最小的 Refresh 頻率最為自動更新的速率。最後顯示的效果會像跑馬燈一樣取得當前的 timeRange 動態更新 Panel 到 ‘now’ 的時間點。

https://github.com/grafana/grafana/pull/37769

https://imgur.com/c5R2NUx

const timer = new behaviors.LiveNowTimer({ enabled: true });

const scene = new EmbeddedScene({
  $behaviors: [timer],
  body: new SceneFlexLayout({
    children: [
      new SceneFlexItem({
        body: new VizPanel({}),
      }),
      new SceneFlexItem({
        body: new VizPanel({}),
      }),
    ],
  }),
});

const timerHandler = () => timer.disable()

進階功能三 - getUrlSyncManager

getUrlSyncManager 是藉由 UrlSyncManager 建立的 uitls,UrlSyncManager 是通過監聽SceneObject 的狀態變化和 URL 的變化來實現雙向同步。當 SceneObject 的狀態發生變化時,UrlSyncManager 會更新 URL 參數;反之,當 URL 發生變化時,它會相應地更新 SceneObject 的狀態。也提供了一系列方法如 initSync、cleanUp、handleNewLocation 和 handleNewObject 來管理同步過程的生命週期。UrlSyncManager 在處理 URL 參數被移除的情況時可以更加靈活,開發者可以在 updateFromUrl 方法中自定義行為。

功能

  • initSync

    目的是初始化 URL 同步,會訂閱 Root SceneObject 的狀態變化事件,需要在 Scene 渲染之前調用,以確保 SceneObject 在被活化之前處於同步狀態。這可以避免一些不必要的 re-render,並確保按需發出變數查詢。

    useEffect(() => {
      if (!isInitialized) {
        getUrlSyncManager().initSync(scene);
        setIsInitialized(true);
      }
    }, [scene, isInitialized]);
    
  • cleanUp

    清理 URL 同步相關的資源。例如:取消訂閱狀態變化事件或重設內部狀態。

    public _onActivate() {
    
      return () => {
        getUrlSyncManager().cleanUp(this);
      };
    }
    
  • handleNewLocation

    當 URL 發生變化時,將 URL 狀態同步 SceneObject。使用時會以 @grafana/runtime 的 locationService 取得最新的位置,在作為其參數:

    useEffect(() => {
      const latestLocation = locationService.getLocation();
      const locationToHandle = latestLocation !== location ? latestLocation : location;
    
      urlSyncManager.handleNewLocation(locationToHandle);
    }, [sceneRoot, urlSyncManager, location]);
    
  • handleNewObject

    將 URL 狀態同步到新添加的 SceneObject。

範例

const urlSyncManager = getUrlSyncManager();

urlSyncManager.initSync(sceneRoot);
urlSyncManager.handleNewLocation(location);
urlSyncManager.handleNewObject(newSceneObject);

參考資料

https://play.grafana.org/a/grafana-app-observability-app/services/service/article-service?var-prometheus=grafanacloud-prom&var-environmentValue=$__all&compareWith=baseline

https://github.com/grafana/scenes/pull/618

https://community.grafana.com/t/refresh-live-dashboards/84959


上一篇
靠 Grafana 吃飯的第十五天 - Grafana Dashboard 的 controls 元件
下一篇
靠 Grafana 吃飯的第十七天 - 實作全客製化的 Scenes App(一)
系列文
論前端工程師如何靠 Grafana 吃飯:從 Grafana App 到前端可觀測性30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言