使用 Airtable 和 React 創建實時儀表板

已發表: 2022-03-11

無論公司是大型企業還是初露頭角的初創公司,從用戶和客戶那裡收集數據,並報告或可視化這些數據對業務至關重要。

我最近在巴西的一家遠程醫療初創公司工作。 它的使命是通過將患者與醫療專業人員和健康教練聯繫起來,提供遠程護理和監控。 核心需求是為教練和衛生專業人員創建一個界面,以便輕鬆查看患者的信息和與其特定情況相關的最重要指標:儀表板。

輸入 Typeform 和 Airtable。

打字機

Typeform 是首選數據收集工具之一,可為完成調查的用戶提供響應式 Web 體驗。 它還具有一些使調查更加智能的功能,尤其是在結合使用時:

  • 邏輯跳轉
  • 隱藏字段

調查可以通過 URL 共享,這些 URL 可以預先植入隱藏字段的值,然後可以用於實現邏輯跳轉並通過鏈接改變用戶的調查行為。

空氣桌用途

Airtable 是一個電子表格數據庫混合和協作雲平台。 它專注於點擊功能,這意味著非技術用戶無需編碼即可對其進行配置。 Airtable 在任何業務或項目中都有大量用例。

您可以將 Airtable 底座用於:

  • CRM (客戶關係管理)
  • HRIS (人力資源信息系統)
  • 項目管理
  • 內容策劃
  • 活動策劃
  • 用戶反饋

還有更多潛在的用例。 您可以在此處探索 Airtable 案例研究。

如果你不熟悉 Airtable,概念數據模型分解如下:

  • 工作區- 由基地組成
  • 基礎- 由表格組成
  • - 由字段(列)和行組成
  • 視圖- 使用可選過濾器和減少字段的表格數據透視圖
  • 字段- 具有字段類型的表的列; 有關字段類型的更多信息,請參見此處

除了提供具有熟悉的電子表格功能的雲託管數據庫外,以下是該平台如此強大的一些原因:

描述使用 Airtable 的技術和非技術用戶。

對於非技術用戶,Airtable 提供:

  • 易於使用的前端界面
  • 可以通過點擊式配置創建自動化,以發送電子郵件、處理數據行、在日曆中安排約會等
  • 允許團隊在相同的基礎和表上協作的多種類型的視圖
  • 可以從市場安裝以增強基礎的 A​​irtable 應用程序

對於開發者,Airtable 提供:

  • 有據可查的後端 API
  • 允許開發人員在 Base 中自動執行操作的腳本環境
  • 自動化也可以觸發在 Airtable 環境中運行的自定義開發腳本,擴展自動化的功能

您可以在此處了解有關 Airtable 的更多信息。

入門:Typeform 到 Airtable

客戶已經配置了 Typeform 調查,下一步是計劃如何將這些數據放在 Airtable 中,然後將其轉換為儀表板。 在任何數據庫之上創建儀表板時需要考慮許多問題:我們應該如何構建數據? 在可視化之前需要處理哪些數據? 我們是否應該將 Base 與 Google Sheets 同步並使用 Google Data Studio? 我們應該導出並找到另一個第三方工具嗎?

對開發人員來說幸運的是,Airtable 不僅提供自動化和腳本來處理數據處理步驟,而且還可以使用 Airtable 應用程序在 Airtable Base 之上構建自定義應用程序和界面。

Airtable 中的自定義應用程序

自 2018 年初發布 Airtable Blocks SDK 以來,Airtable 中的自定義應用程序就一直存在,最近更名為 Apps。 Blocks 的發布意義重大,這意味著創作者現在有能力開發,正如 Airtable 所說,“一個無限可組合的樂高套件”。

最近隨著應用程序的變化,Airtable Marketplace 也使得公開共享應用程序成為可能。

Airtable 應用程序為企業提供了可無限組合的樂高套件,他們可以根據自己的需求進行定制。

為了在 Airtable 中構建自定義應用程序,JavaScript 開發人員必須知道如何使用 React,這是用於構建用戶界面的最流行的 JavaScript 庫之一。 Airtable 提供了一個功能性 React 組件和鉤子的組件庫,這對於快速構建一致的 UI 並確定您將如何管理應用程序及其組件中的狀態提供了巨大幫助。

查看 Airtable 的入門文章了解更多信息,查看 GitHub 上的 Airtable 了解應用示例。

Airtable儀表板要求

在與客戶團隊審查儀表板模型後,要使用的數據類型很明確。 我們需要一系列儀表板組件,這些組件將在儀表板上顯示為文本,以及可以隨時間跟踪的不同指標的圖表。

教練和醫療專業人員需要能夠為每位患者構建自定義儀表板,因此我們需要一種靈活的方式來添加和刪除圖表。 無論選擇何種患者,都將顯示與每位患者相關的其他靜態數據。

在這種情況下,儀表板部分歸結為:

  • 一般信息- 患者姓名、電子郵件、電話號碼、聯繫方式、出生日期、年齡
  • 目標- 患者根據調查結果制定的目標
  • 一些統計數據- BMI、身高和體重
  • 藥物使用- 列出患者已使用的所有處方藥
  • 疾病家族史- 有助於診斷某些疾病
  • 圖表- Airtable 儀表板用戶可以在其中添加圖表並配置它將隨時間可視化的指標的部分

顯示 Airtable 儀表板模型的圖像。

處理除圖表之外的所有部分的一種方法是將目標、藥物使用和家族史的所有列硬編碼到儀表板中。 但是,這將不允許客戶團隊在沒有開發人員更新定制應用程序的情況下向 Typeform 調查添加新問題,也不允許向 Airtable 表添加新列以在儀表板上顯示該數據。

應對這一挑戰的更優雅和可擴展的解決方案是找到一種方法,將列標記為與特定儀表板部分相關,並使用 Airtable 在使用表和字段模型時公開的元數據檢索這些列。

這是通過使用字段描述作為將表中的列標記為與要顯示給用戶的儀表板部分相關的位置來實現的。 然後,我們可以確保只有具有創建者角色(管理員)的 Base 才能修改這些字段描述以更改儀表板上顯示的內容。 為了說明這個解決方案,我們將主要關註一般信息中的項目以及如何呈現圖表。

創建#TAG#系統

鑑於儀表板部分,為某些部分製作可重複使用的標籤並為某些列製作特定標籤是有意義的。 對於患者姓名、電子郵件和電話號碼等項目, #NAME##EMAIL##PHONE#分別添加到每個字段的描述中。 這將允許通過表元數據檢索該信息,如下所示:

 const name = table ? table.fields.filter(field => field.description?.includes("#NAME#"))

對於需要從許多標記列中繪製的儀表板區域,我們將為每個儀表板部分使用以下標記:

  • OBJ - 目標
  • FAM - 家族史
  • MED - 藥物使用
  • CAN - 特定於癌症的家族史
  • 圖表 - 任何應該用於添加圖表的列; 必須是數量

此外,將表中列的名稱與其在儀表板上收到的標籤分開很重要,因此收到#TAG#的任何內容也可以在其字段描述中收到兩個#LABEL#標籤. 字段描述如下所示:

顯示在字段描述中使用標籤的屏幕截圖。

如果缺少#LABEL#標籤,我們將顯示表格中的列名。

在使用前面的代碼示例檢索字段後,我們可以使用這樣的簡單函數解析出描述中的標籤集:

 // utils.js export const setLabel = (field, labelTag = "#LABEL#") => { const labelTags = (field.description?.match(new RegExp(labelTag, "g")) || []).length; let label; if (labelTags === 2) label = field.description?.split(`${labelTag}`)[1]; if (!label || label?.trim() === '') label = field.name; return {...field, label, name: field.name, description: field.description}; }

通過這個#TAG#系統,我們實現了三個主要目標:

  • 可以根據需要更改表中的列名(字段)。
  • 儀表板中的數據標籤可以不同於列名。
  • 目標、藥物使用、家族史和圖表的儀表板部分可以由客戶團隊更新,而無需觸及一行代碼。

在 Airtable 中持久化狀態

在 React 中,我們使用 state 並將其作為 props 傳遞給組件,以便在組件狀態發生變化時重新渲染該組件。 通常這與為儀表板組件提供燃料的 API 調用相關聯,但在 Airtable 中,我們已經擁有所有數據,只需要根據我們正在查看的患者過濾我們正在顯示的內容。 此外,如果我們使用狀態,它不會將數據保存在儀表板本身的刷新之後。

那麼,我們如何在刷新後保持值以保持儀表板被過濾? 幸運的是,Airtable 為此提供了一個名為useGlobalConfig的鉤子,它在其中為儀表板上的應用程序安裝維護一個鍵值存儲。 我們只需要實現在應用加載時從該鍵值存儲中檢索值的邏輯,以支持我們的儀表板組件。

使用useGlobalConfig鉤子更有用的是,當設置它的值時,儀表板組件及其子組件會重新渲染,因此您可以像在典型的 React 實現中使用狀態變量一樣使用全局配置。

介紹圖表

Airtable 通過其簡單圖表應用程序提供圖表示例,該應用程序使用 React Charts,一個在 Chart.js (chart-ception) 上的 React 包裝器。

在簡單圖表應用程序中,我們為整個應用程序提供了一個圖表,但在我們的儀表板應用程序中,我們需要用戶能夠從他們自己的儀表板中添加和刪除他們自己的圖表。 更重要的是,在與客戶團隊的討論中,似乎某些指標在同一張圖表上會更好地查看(例如舒張壓和收縮壓的讀數)。

有了這個,我們有以下項目需要解決:

  • 為每個用戶的圖表保留狀態(甚至更好地使用全局配置)
  • 每個圖表允許多個指標

這就是 Global Config 派上用場的地方,因為我們可以使用鍵值存儲來維護選定的指標以及關於我們的圖表列表的任何其他內容。 當我們在 UI 中配置圖表時,圖表組件本身將由於全局配置的更新而重新渲染。 對於儀表板的圖表部分,這裡有一個組件供參考的要點,重點是儀表板charts.js 和單個chart.js。

傳遞給每個圖表的是用於其元數據以查找字段的表,而傳遞的記錄已經由在導入dashboard_charts/index.js的頂級儀表板組件中選擇的患者過濾。

請注意,圖表下拉列表中作為選項列出的字段是使用我們之前提到的#CHART#標籤提取的,此行位於useEffect掛鉤中:

 // single_chart/index.js … useEffect(() => { (async () => { ... if (table) { const tempFieldOptions = table.fields.filter(field => field.description?.includes('#CHART#')).map(field => { return { ...setLabel(field), value: field.id } }); setFieldSelectOptions([...tempFieldOptions]); } })(); }, [table, records, fields]); ...

上面的代碼顯示了前面引用的setLabel函數如何與#TAG#一起使用,以添加在#LABEL#標記中提供的任何內容,並將其顯示在字段下拉列表中的選項中。

我們的圖表組件利用了 Chart.js 提供的多軸功能,這與 React Charts 一起顯示。 我們只是通過 UI 擴展了它,讓用戶能夠添加數據集和圖表類型(折線或條形)。

在這種情況下,使用 Global Config 的關鍵是要知道每個鍵只能保存一個字符串 | 布爾值 | 號碼 | 空 | 全局配置數組 | GlobalConfigObject(請參閱全局配置值參考)。

每個圖表我們需要維護以下項目:

  • chartTitle是自動生成的,可以由用戶重命名
  • fields數組,其中每個項目具有:
    • 字段作為 Airtable 中的 fieldId
    • chartOption為一行 | 條形如 Chart.js 文檔所示
    • 顏色作為來自 colorUtils 的 Airtable 顏色
    • hex作為與 Airtable 顏色相關的十六進制代碼

為了解決這個問題,我發現將這些數據作為對象進行字符串化而不是一直設置全局配置鍵和值是最方便的。 請參閱下面的示例(gist 中的 globalConfig.json),其中包括用於過濾患者記錄的全局配置值和一些用於支持 typeahead 過濾組件的相關變量(感謝 react-bootstrap-typeahead):

 { "xCharts": { "chart-1605425876029": "{\"fields\":[{\"field\":\"fldxLfpjdmYeDOhXT\",\"chartOption\":\"line\",\"color\":\"blueBright\",\"hex\":\"#2d7ff9\"},{\"field\":\"fldqwG8iFazZD5CLH\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 2:37:56 AM\"}", "chart-1605425876288": "{\"fields\":[{\"field\":\"fldGJZIdRlq3V3cKu\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 2:37:56 AM\"}", "chart-1605425876615": "{\"fields\":[{\"field\":\"fld1AnNcfvXm8DiNs\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"},{\"field\":\"fldryX5N6vUYWbdzy\",\"chartOption\":\"line\",\"color\":\"blueDark1\",\"hex\":\"#2750ae\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 2:37:56 AM\"}", "chart-1605425994036": "{\"fields\":[{\"field\":\"fld9ak8Ja6DPweMdJ\",\"chartOption\":\"line\",\"color\":\"blueLight2\",\"hex\":\"#cfdfff\"},{\"field\":\"fldxVgXdZSECMVEj6\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 2:39:54 AM\"}", "chart-1605430015978": "{\"fields\":[{\"field\":\"fldwdMJkmEGFFSqMy\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"},{\"field\":\"fldqwG8iFazZD5CLH\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"}],\"chartTitle\":\"New Chart\"}", "chart-1605430916029": "{\"fields\":[{\"field\":\"fldCuf3I2V027YAWL\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"},{\"field\":\"fldBJjtRkWUTuUf60\",\"chartOption\":\"line\",\"color\":\"blueDark1\",\"hex\":\"#2750ae\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 4:01:56 AM\"}", "chart-1605431704374": "{\"fields\":[{\"field\":\"fld7oBtl3iiHNHqoJ\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 4:15:04 AM\"}" }, "xPatientEmail": "[email protected]", "xTypeaheadValue": "Elle Gold ([email protected])", "xSelectedValue": "[{\"label\":\"Elle Gold ([email protected])\",\"id\":\"[email protected]\",\"name\":\"Elle Gold\",\"email\":\"[email protected]\"}]" }

注意:上麵包含的所有數據,以及下面動畫中包含的數據,都不是真實的患者數據。

下面看一下最終結果:

Airtable 儀表板 UI 的動畫顯示。

Typeahead 怎麼樣?

為了按患者過濾,我們需要一種方法來選擇患者,然後根據該患者過濾記錄。 在本節中,我們將回顧這是如何實現的。

對於 typeahead,react-bootstrap-typeahead 是一個簡單的選擇,因為剩下的唯一步驟是準備 typeahead 的選項,將其與 Airtable 輸入混合以進行樣式設置和加載引導程序,以及我們菜單的一些其他樣式。 將組件從您最喜歡的組件庫中拖放到 Airtable 應用程序中並不像典型的 React Web 開發那樣簡單; 然而,只有幾個額外的步驟可以讓一切看起來像您期望的那樣。

這是最終結果:

動畫 GIF 展示了按患者過濾的功能。

為了渲染 Airtable 輸入並保持我們所有的樣式一致,react-bootstrap-typeahead 帶有一個 renderInput 屬性。 在此處查看有關如何修改組件渲染的更多信息。

對於引導樣式和覆蓋我們的菜單項,Airtable 使用了以下兩個實用程序:

  • loadCSSFromString
  • loadCSSFromURLAsync

有關 typeahead 實現的摘錄,請參閱要點中的 frontend.js。

此行用於全局加載引導程序:

 // frontend/index.js loadCSSFromURLAsync('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css');

您會注意到一些附加邏輯,例如處理懸停樣式更改或重新設置鏈接 ( <a></a> ) 以獲得熟悉的引導程序外觀。 這還包括處理為預輸入設置全局配置值和過濾記錄,以便如果用戶離開儀表板、刷新頁面或希望與其他人共享此儀表板,應用程序會將所選患者保留在儀表板中應用程序。 這還允許用戶在同一個 Airtable 儀表板中並排安裝同一應用程序的多個副本,並選擇不同的患者或使用不同的圖表。

請記住,Airtable 中的儀表板也可供 Base 的所有用戶使用,因此無論哪些用戶同時查看儀表板,儀表板上的這些自定義應用程序安裝都將被過濾到相同的患者和圖表。

讓我們回顧一下我們迄今為止所涵蓋的內容:

  1. Airtable 允許非技術用戶和技術用戶在 Airtable 中進行協作。
  2. Typeform 帶有一個 Airtable 集成,允許非技術用戶將 Typeform 結果映射到 Airtable。
  3. Airtable Apps 提供了一種強大的方式來增強其 Airtable Base,無論是從市場中選擇還是構建自定義應用程序。
  4. 開發人員可以使用這些應用程序以幾乎任何可以想像的方式快速擴展 Airtable。 我們上面的例子只用了三週的時間來設計和實現(當然,在現有庫的巨大幫助下)。
  5. #TAG#系統可用於修改儀表板,而無需開發人員更改代碼。 有更好和更壞的用例。 如果使用此策略,請務必將權限限制為 Creator 角色。
  6. 使用 Global Config 允許開發人員在應用程序安裝中保留數據。 將其混合到您的狀態管理策略中,為您的組件播種數據。
  7. 不要期望將其他庫和項目中的組件直接拖放到您的 Airtable 應用程序中。 可以使用 Airtable 提供的loadCSSFromStringloadCSSFromURLAsync加載樣式。

面向未來

使用更複雜的中間件

使用 Typeform 和 Airtable,可以輕鬆且經濟高效地配置問題到列的映射。

但是,有一個很大的缺點:如果您有超過 100 個問題的調查映射到 Airtable,並且您需要修改映射,則必須刪除整個映射並重新開始。 這顯然不理想,但是對於自由集成,我們可以處理這個問題。

其他選項是讓 Zapier(或類似的)集成管理 Typeform 和 Airtable 之間的數據。然後您可以修改任何問題到任何列的映射,而無需從頭開始。 這也將有其自身的成本考慮因素。

希望這裡學到和交流的一些經驗教訓將幫助其他希望使用 Airtable 構建解決方案的人。

最後,您可以查看本文中討論的文件的要點。