使用 Airtable 和 React 创建实时仪表板
已发表: 2022-03-11无论公司是大型企业还是初露头角的初创公司,从用户和客户那里收集数据,并报告或可视化这些数据对业务来说都是至关重要的。
我最近在巴西的一家远程医疗初创公司工作。 它的使命是通过将患者与医疗专业人员和健康教练联系起来,提供远程护理和监控。 核心需求是为教练和卫生专业人员创建一个界面,以便轻松查看患者的信息和与其特定情况相关的最重要指标:仪表板。
输入 Typeform 和 Airtable。
打字机
Typeform 是首选数据收集工具之一,可为完成调查的用户提供响应式 Web 体验。 它还具有一些使调查更加智能的功能,尤其是在结合使用时:
- 逻辑跳转
- 隐藏字段
调查可以通过 URL 共享,这些 URL 可以预先植入隐藏字段的值,然后可以用于实现逻辑跳转并通过链接改变用户的调查行为。
空气桌用途
Airtable 是一个电子表格数据库混合和协作云平台。 它专注于点击功能,这意味着非技术用户无需编码即可对其进行配置。 Airtable 在任何业务或项目中都有大量用例。
您可以将 Airtable 底座用于:
- CRM (客户关系管理)
- HRIS (人力资源信息系统)
- 项目管理
- 内容策划
- 活动策划
- 用户反馈
还有更多潜在的用例。 您可以在此处探索 Airtable 案例研究。
如果你不熟悉 Airtable,概念数据模型分解如下:
- 工作区- 由基地组成
- 基础- 由表格组成
- 表- 由字段(列)和行组成
- 视图- 使用可选过滤器和减少字段的表格数据透视图
- 字段- 具有字段类型的表的列; 有关字段类型的更多信息,请参见此处
除了提供具有熟悉的电子表格功能的云托管数据库外,以下是该平台如此强大的一些原因:
对于非技术用户,Airtable 提供:
- 易于使用的前端界面
- 可以通过点击式配置创建自动化,以发送电子邮件、处理数据行、在日历中安排约会等
- 允许团队在相同的基础和表上协作的多种类型的视图
- 可以从市场安装以增强基础的 Airtable 应用程序
对于开发者,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 仪表板用户可以在其中添加图表并配置它将随时间可视化的指标的部分
处理除图表之外的所有部分的一种方法是将目标、药物使用和家族史的所有列硬编码到仪表板中。 但是,这将不允许客户团队在没有开发人员更新定制应用程序的情况下向 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]\"}]" }注意:上面包含的所有数据,以及下面动画中包含的数据,都不是真实的患者数据。
下面看一下最终结果:
Typeahead 怎么样?
为了按患者过滤,我们需要一种方法来选择患者,然后根据该患者过滤记录。 在本节中,我们将回顾这是如何实现的。
对于 typeahead,react-bootstrap-typeahead 是一个简单的选择,因为剩下的唯一步骤是准备 typeahead 的选项,将其与 Airtable 输入混合以进行样式设置和加载引导程序,以及我们菜单的一些其他样式。 将组件从您最喜欢的组件库中拖放到 Airtable 应用程序中并不像典型的 React Web 开发那样简单; 然而,只有几个额外的步骤可以让一切看起来像您期望的那样。
这是最终结果:
为了渲染 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 的所有用户使用,因此无论哪些用户同时查看仪表板,仪表板上的这些自定义应用程序安装都将被过滤到相同的患者和图表。
让我们回顾一下我们迄今为止所涵盖的内容:
- Airtable 允许非技术用户和技术用户在 Airtable 中进行协作。
- Typeform 带有一个 Airtable 集成,允许非技术用户将 Typeform 结果映射到 Airtable。
- Airtable Apps 提供了一种强大的方式来增强其 Airtable Base,无论是从市场中选择还是构建自定义应用程序。
- 开发人员可以使用这些应用程序以几乎任何可以想象的方式快速扩展 Airtable。 我们上面的例子只用了三周的时间来设计和实现(当然,在现有库的巨大帮助下)。
-
#TAG#系统可用于修改仪表板,而无需开发人员更改代码。 有更好和更坏的用例。 如果使用此策略,请务必将权限限制为 Creator 角色。 - 使用 Global Config 允许开发人员在应用程序安装中保留数据。 将其混合到您的状态管理策略中,为您的组件播种数据。
- 不要期望将其他库和项目中的组件直接拖放到您的 Airtable 应用程序中。 可以使用 Airtable 提供的
loadCSSFromString和loadCSSFromURLAsync加载样式。
面向未来
使用更复杂的中间件
使用 Typeform 和 Airtable,可以轻松且经济高效地配置问题到列的映射。
但是,有一个很大的缺点:如果您有超过 100 个问题的调查映射到 Airtable,并且您需要修改映射,则必须删除整个映射并重新开始。 这显然不理想,但是对于自由集成,我们可以处理这个问题。
其他选项是让 Zapier(或类似的)集成管理 Typeform 和 Airtable 之间的数据。然后您可以修改任何问题到任何列的映射,而无需从头开始。 这也将有其自身的成本考虑因素。
希望这里学到和交流的一些经验教训将帮助其他希望使用 Airtable 构建解决方案的人。
最后,您可以查看本文中讨论的文件的要点。
