iT邦幫忙

2024 iThome 鐵人賽

DAY 9
2

https://ithelp.ithome.com.tw/upload/images/20240923/20152073fCbyLhT6Sg.png

前言

在本章節中,我們將深入探討 Grafana Scenes 的概念。在上一部份中,我們已經熟悉一下 Grafana 的觀念,也認識了 Grafana Plugin 與 Scenes 之間的關聯,以及了解 Scene App 是透過結合多個資料來源的 Panel、Time range 和 Variable 來實現的。然而 Scene App 類型的 Plugin,無論是哪一層次或是哪一種 API,都是由 Scene Object 所構成的。因此,本章節將帶領讀者認識 Scenes 的核心——Scene Object,並逐步展示如何從初始化到完成一個 Hello World 的 Scene App。

猶如俄羅斯娃娃的 Scene Object

Grafana Scenes 中有非常多種功能性或展示性的元件,例如組成整個 APP 的 SceneApp、最通用的 Scene 元件 EmbeddedScene,以及其他 SceneQueryRunnerSceneFlexLayoutVizPanel 等等,每一種都是由 Scene Object 延伸,也可以交互組合更支援嵌套巢狀,只要使用 children 屬性即可達成俄羅斯娃娃般的應用程式。

要了解 Scene Object 的原理,可以將其分為三個關鍵部分:State(狀態)、Model(模型)和 Render(渲染),他們形成了 Scene Object 的完整生命週期和功能。

State(狀態)

interface DemoSceneState extends SceneObjectState {
	counter: number;
}

State 代表 Scene Object 的當前狀態,包含所有可變的資料。在 Grafana Scene 中,狀態通常會延伸 SceneObjectState 的型別來定義和管理。

💡 SceneObjectState
是 Grafana Scene 中最基本的型別,會被所有 Object 的元件延伸擴展,可以自行添加屬性達到狀態管理需求,同時又擁有最基本的幾個狀態,讓在巢狀或複雜的結構中都可以調用狀態。其中預設狀態會以 $ 符號:$timeRange、$data、$variables 以及 $behaviors。

Model

export class DemoScene extends SceneObjectBase<DemoSceneState> {
  public static Component = DemoSceneRenderer;

  public constructor(counter: number) {
    super({ counter });
  }

  public onIncrement = () => {
    this.setState({
      counter: this.state.counter + 1,
    });
  };
}

Model 是一個類(Class)其中定義了 Scene Object 的結構、行為以及生命週期,通常會繼承 SceneObjectBase,主要封裝了狀態管理、業務邏輯和與其他 Scene Objects 的交互作用。

在實際應用中,Model 可能代表一個圖表、一個表格、一個 Panel、一個 Button,或者任何其他視覺化元件。通過組合不同的 Models,開發者可以創造複雜的、交互式的資料視覺化應用程式。以下有幾個常在 Model 中實現的功能:

  • 提供更新狀態的方法,例如範例中的 onIncrement 調用 setState 進行狀態更新。

  • 實現資料處理和商業邏輯

  • 定義生命週期的一些 function(如 activate、deactivate)

      public _onActivate() {
        if (!this.state.mode) {
          this.setState({ mode: 'service_total' });
        }
    
        return () => {
          getUrlSyncManager().cleanUp(this);
        };
      }
    
  • 處理使用者交互和事件:例如 SceneObjectBase 中有 subscribeToEvent 和 publishEvent,處理 Scene Objects 訂閱和發布事件。

  • 定義與其他 Scene Objects 協調的 function:SceneObjectBase 中有 getRoot 和 forEachChild,可以讓 Scene Objects 與其父物件和子物件進行交互作用。

Render(渲染)

function DemoSceneRenderer({ model }: SceneComponentProps<DemoScene>) {
  const { counter } = model.useState();
  
  return <h1 onClick={model.onIncrement}>{counter}</h1>;
}

Render 在 Grafana Scene 中是將 Model 的狀態和邏輯轉換為視覺化 UI 元素的過程。會透過一個 Component 的靜態狀態實現,例如範例中的 DemoSceneRenderer ,而這個 Component 是一個 React 元件,接收 Scene Object 作為 prop,即為 model 物件。

Render 會以 model 的 useState hook 訂閱 Scene Object 的狀態變化,或是調用在 Model 中建立的 function,例如範例中的 model.onIncrement。也可以以 model 本身這個實例作為 Grafana Scenes 提供 API 的參數使用。

const variables = sceneGraph.getVariables(model).useState()

若未使用 model.onIncrement 而是希望直接對狀態進行更新修改,則可以調用 model.setstate 方法實現:

const onIncrement = () => model.setState({ counter: counter + 1 })

做一個範例

在了解 Scene Object 的實現原理後,我們可以建立一個具體的範例,體驗 Scenes 的實際應用。範例中會從安裝到最後顯示一個最簡單的 Hello World 畫面的實作,其中會包括 Scene 建立時的初始化,例如會需要使用什麼類型的 Scene Object,以及需要設定什麼屬性。由於 Grafana 已經設計了一套實現應用程式可以使用的元件,因此如何選擇使用是非常重要的!

安裝

當完成第八章中的 Grafana Plugin 安裝,若選擇 Scene App,只要執行 yarn install 即可開始開發,因此建議直接選擇此類型安裝:

https://ithelp.ithome.com.tw/upload/images/20240923/20152073AHVQTNHrq8.png

若安裝 Grafana Plugin 時所選擇的是 App 類型,則還需要另外安裝 Grafana Scene 才可以使用:

yarn add @grafana/scenes

NOTICE:
非常推薦先了解 Grafana Plugin 與 Grafana Scenes 之間的關係,再接下去安裝開發。
參考資料:靠 Grafana 吃飯的第八天 - Grafana 的百寶袋 - Plugin

安裝時選擇 Scene App 類型後,會建立一個含有四個路徑頁面的應用程式:Home、WithTabs、WithDrilldown 和 HelloWorld。主要開發皆會在 src 資料夾中,有 components、img、pages 和 utils 四個資料夾,以及集中常數的 constants、Plugin 應用程式初始設定的 module 檔案,最後是提供 Plugin 基本資料的 plugin.json 檔案。

Grafana Plugin 可以自由地新增具有路徑的頁面,可以在 components > Routes 中進行 React Router Dom 的路徑擴展設置,除此之外也必須在 plugin.json 檔中擴展 includes 屬性的內容:

{
  "type": "page",
  "name": "Hello world",
  "path": "/a/%PLUGIN_ID%/hello-world",
  "role": "Admin",
  "addToNav": true,
  "defaultNav": false
}

https://ithelp.ithome.com.tw/upload/images/20240923/20152073zSw3y5VCC0.png

建立一個 Scene

建立一個 Scene 有幾種搭配方法:

  1. 使用 SceneApp + SceneAppPage + EmbeddedScene:
    如果需要建立一個完整的多頁面應用程式,可以使用此組合。這提供了更多的結構和路由功能。
  2. 只使用 EmbeddedScene:
    如果在當下的 Grafana 頁面中只需要單一的 Dashborad 或是只有一個單頁的應用程式,則可以只使用 EmbeddedScene

SceneApp

SceneApp 是應用程式的最頂層頁面,用於管理多個頁面和路由的頂層容器,通常會設置一個屬性 pages ,即為包下所有 SceneAppPage objects 的陣列:

const getScene = () => new SceneApp({
  name: 'My Grafana App',
  pages: [
    // SceneAppPage 實例
  ],
});

SceneApp 的 Component 屬性即為一個路由元件,會取得 pages 中每個頁面的 url 進行管理,並以單一個 page SceneObject 作為實例屬性傳給 render 的 function,執行 React.createElement 以渲染:

<Switch>
  {pages.map((page) => (
    <Route
      key={page.state.url}
      exact={false}
      path={page.state.url}
      render={...}
    ></Route>
  ))}
</Switch>

SceneAppPage

SceneAppPage 代表 SceneApp 中的單個頁面。有著 SceneAppPageState 型別的狀態,包含了頁面的標題、副標題、URL(使用絕對路徑,i.e. /app/overview )以及該頁面的實例 getScene。另外也有提供特別的屬性:

  1. renderTitle:可以渲染客製化的標題,但需要返回一個未加樣式的 <h1> 標籤,以及需要的任何其他元素。

  2. hideFromBreadcrumbs:決定頁面是否應該在麵包屑路徑中顯示,例如下圖的麵包屑。

    https://ithelp.ithome.com.tw/upload/images/20240923/20152073TCqPdFkQ4I.png

  3. tabs:作為頁面頂部顯示的頁籤的 SceneAppPage 物件陣列,而標籤通常顯示在頁面頂部,使用者可以在相關內容之間快速切換。每個標籤都是一個 SceneAppPage 實例,可以擁有自己的標題、URL 和內容。

    https://ithelp.ithome.com.tw/upload/images/20240923/20152073cGfguRMXg8.png

  4. drilldown:讓使用者從高層次的圖表深入到更詳細的資訊。每個 drilldown 圖表定義了一個路由路徑(routePath)和一個取得頁面的 function(getPage),而這個路經通常為動態路徑,可以藉由 React Router 可以取得 URL 參數。另外 getPage 返回的是一個新的 SceneAppPage 實例,所以如果在每一層皆使用 drilldown 屬性,就可以無限巢狀深層頁面。使用場景為:

    1. 顯示服務端點的概觀,並透過 drilldown 展開更細節的資訊。
    2. 從高層級的統計資料,透過 drilldown 進入單一事件或記錄的詳細內容。

    例如下圖中可以點擊進入不同 Route Details 的介面:
    https://ithelp.ithome.com.tw/upload/images/20240923/20152073LS0foeD4hB.png


const getScene = () => new SceneApp({
  name: 'My Grafana App',
  pages: [
    new SceneAppPage({
		  title: 'Page with tabs',
		  url: '/my-page/tabs',
		  hideFromBreadcrumbs: true,
		  getScene: getTab1Scene,
		  tabs: [
		    new SceneAppPage({
		      title: 'Tab 2',
		      url: '/my-page/tabs',
		      getScene: getTab1Scene,
		    }),
		    new SceneAppPage({
		      title: 'Tab 1',
		      url: '/my-page/tabs/tab-two',
		      getScene: getTab2Scene,
		    }),
		  ],
		}),
  ],
});

EmbeddedScene

可以用來建立獨立的 Dashboard 或視覺化頁面,將複雜的視覺化嵌入其他 Grafana 頁面或外部應用,或作為更大型應用結構(如 SceneApp)中的一個頁面或圖表。EmbeddedScene 包括內建自己的資料源、時間範圍和變數以及以下兩個屬性:

  1. body:Scene 的主要內容,通常是 SceneFlexLayout 或其他布局元件。
  2. controls:可選的控制元件,如變數選擇器、時間範圍選擇器等,通常會與 $timeRange、$data、$variables 配合使用。
const getScene = () => new EmbeddedScene({
	body: new SceneFlexLayout({
		children: [
			new SceneFlexItem({
				body: new VizPanel({...}),
			}),
		],
	}),
	controls: [
		new VariableValueSelectors({}),
		new SceneTimePicker({}),
	],
});

渲染一個 Scene

建立的最終步驟,就是將定義的 getScene 調用,返回一個 SceneObject 實例,並利用這個實例中的 Component 的屬性渲染出一個應用程式:

export const DemoPluginPage = () => {
  const scene = getScene();

  return <scene.Component model={scene} />;
};

筆者語錄
就如同在一段落所比喻,Grafana Scenes 就像一個俄羅斯娃娃,每一層都是 Scene Object,從 SceneAppEmbeddedScene,再到最小的 SceneFlexItem,都能相互嵌套。搞懂它的 StateModelRender 後,其實也沒有那麼複雜,尤其是有了這麼多現成的元件,只要好好搭配,就能輕鬆構建一個 Grafana 的應用程式。而下一章節中,我們將認識 Grafana Scenes 又提供了什麼元件,讓開發者可以更輕鬆的打造一個 Scene App 的 Layout,並在之後的文章一篇一腳印的從資料取得、模板化到 Panel 呈現及進階設定,深度地剖析 Grafana Scenes。


上一篇
靠 Grafana 吃飯的第八天 - Grafana 的百寶袋 - Plugin
下一篇
靠 Grafana 吃飯的第十天 - 當 Scenes 成為 Grafana 的切版工具
系列文
論前端工程師如何靠 Grafana 吃飯:從 Grafana App 到前端可觀測性30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言