Opentrons 协议设计器更新:批量编辑概述

Opentrons 很高兴为协议设计器发布改进的批量编辑选项。 以下是您需要了解的有关其工作原理的所有信息。

此功能升级直接来自客户反馈。 谢谢你! 我们很高兴听到您的想法——请继续提出!

当协议设计器 (PD) 首次创建时,Opentrons 设想了一个平台,科学家可以在其中快速构建基本的移液协议,而无需编写任何代码。 随着 PD 越来越多地被我们社区所利用,它成为一种工具,使科学家能够构建冗长而复杂的方案,以解决 DNA 提取、蛋白质纯化和 COVID-19 诊断等应用。 当我们开始看到科学家使用 PD 构建包含 100 多个步骤的方案时,我们知道我们需要为他们提供更好的方法来优化和编辑他们的方案。 科学家们有足够多的担忧:我们不希望他们花时间一遍又一遍地乏味地改变相同的设置。

所以,我们刚刚向 PD 添加了批量编辑模式! 用户现在可以选择、复制、删除和编辑多个步骤,从而能够更快地迭代和优化科学协议。

在产品开发团队开始考虑如何完成此功能之前,我们就知道这将是一个巨大的工程挑战。 扩展 PD 这样的应用程序以支持完全不同的用户流程(例如批量编辑)将需要大量重构,这会影响 Protocol Designer 的核心部分。 这篇文章将从工程角度重点讨论此功能,但我也想指出完成此功能所面临的巨大设计挑战。 PD 的核心原则之一是易用性,打造易于使用、直观的用户体验来构建和优化复杂的科学协议绝非易事。

专为一次编辑一个步骤而设计

PD 旨在从头开始构建科学方案,依次创建和编辑步骤。 流程如下:

  1. 用户创建一个步骤(转移、混合、加热等)
  2. 在表格中填写相关步骤信息(移液器、实验室器皿、液体体积等)
  3. 保存表格
  4. 创建下一步
  5. 等等
协议设计器

这意味着在任何给定时间,PD 中的主“设计”选项卡都会显示以下部分/全部内容:

  1. 协议中所有步骤的列表
  2. 协议特定步骤的视觉快照
  3. 供用户填写的表格,以指定该步骤应执行的操作

PD 的 UI 组件(内置于 React)和全局状态管理存储(在 Redux 中实现)就是为了适应这种用户流程而构建的。 这意味着 PD 的 redux 存储会跟踪“选定步骤”之类的内容,以便用户可以直观地看到协议的特定部分实际发生的情况。 它还会跟踪“未保存的表单”等内容,在提交之前跟踪待处理的更改。

请注意上面捕获的数据的奇异性(选定的步骤,未保存的形式)。 PD 的 redux 存储和 React 组件旨在与一个步骤交互,而不是多个步骤。 为了说明这一点,让我们看看 PD 如何根据选择的步骤填充实验室器皿上的孔(这就是上图中孔显示为绿色的方式)。

  1. 用户点击一个步骤
  2. 调度一个名为“SELECT_STEP”的 redux 操作,更新“所选项目”reducer
  3. 侦听“所选项目”更改的 redux 选择器将搜索该特定步骤中正在使用的实验室器皿和孔
  4. 孔信息将传递到名为“LabwareRender”的组件中,然后该组件将使用该信息对孔进行着色

但是,当我们开始跟踪多个选定步骤而不是仅跟踪一个步骤时,会发生什么情况呢? 特别是,上面的步骤 3 会发生什么? 如果我们选择了多个项目,我们该怎么办?

为了防止同时选择/编辑多个步骤时组件损坏,我们必须对数据的表示和转换方式进行核心更改。 让我们回顾一下我们所做的事情。

跟踪多个选定的步骤

在此功能之前,我们仅在 Redux 存储中跟踪单个“selectedItem”。 我们的“selectedItem”reducer 类型接口类似于下面的代码片段。 请注意,本文中的所有代码片段均采用 JavaScript 编写,并使用 flow js 键入。

Type SelectableItem = SingleSelectedItem
const selectedItem = (state: SelectItemState, action: SelectedItemAction): ?SelectableItem 

减速器函数“selectedItem”接受状态和操作,并返回所选项目(保存所选项目相关信息的对象),如果没有所选项目,则返回 null。

为了避免在批量编辑模式下添加新的缩减器来保存多个步骤,我们选择修改“selectedItem”缩减器以适应返回单个步骤和多个步骤。

Type SelectableItem = SingleSelectedItem | MultipleSelectedItem
const selectedItem = (state: SelectItemState, action: SelectedItemAction): ?SelectableItem 

“selectedItem”的返回类型已修改为能够保存包含单个步骤 id 的对象(代表单一选择类型),或包含多个步骤 id 列表的对象(代表多重选择类型) 。 为了告诉 redux 我们已经选择了多个步骤,我们创建了一个名为“SELECT_MULTIPLE_STEPS”的操作,“selectedItem”reducer 函数将接受该操作,并更新其值以表示多个步骤(请参阅类型“MultipleSelectedItem”)

名为“selectedItem”的减速器肯定会有些尴尬,它可能保存表示多个项目的数据,但我们最终决定,这种权衡是值得的,不必添加额外的减速器来表示多个选定的项目,从而必须取消一个或多个项目 另一个是在单个编辑模式和批量编辑模式之间切换时。

为了防止过去只接受一个步骤作为 props 的组件被破坏,我们能够利用 redux 选择器模式将来自减速器的数据转换为我们的组件可以接受的格式。 将有关所选步骤的信息提供给我们的组件的主选择器称为“getSelectedStepId”,它曾经执行以下操作:

const getSelectedStepId: ?string = (state: State) => state.selectedItem

这是选择器过去所做的事情的简化,但你明白了——它基本上进入了“selectedStep”减速器并返回其中的任何内容。 因为我们的组件从选择器而不是减速器获取选定的步骤,所以我们能够首先转换减速器中保存的数据,然后再将其输入到我们的组件中。

这意味着我们所要做的就是修改“getSelectedStepId”以在reducer保存“单选择类型”时返回步骤id,否则返回null:

const getSelectedStepId: ?string = (state: State) => state.selectedItem.selectionType === SINGLE_STEP_SELECTION_TYPE ? state.selectedItem.id : null

由于我们现有的组件现在能够处理选择的多个步骤,因此我们添加了一个名为“getMultiSelectItemIds”的新选择器,它类似于“getSelectedStepId”,但在批量编辑模式下返回步骤 ID 列表,否则返回 null。 该选择器将用于告诉 PD 在批量编辑模式下选择了哪些步骤。

const getMultiSelectItemIds: ?Array<string> = (state: State) => state.selectedItem.selectionType === MULTI_STEP_SELECTION_TYPE ? state.selectedItem.ids : null

从减速器 => 选择器 => 组件中获取数据流确实对我们有帮助,因为我们能够更改减速器的结构,而不必担心组件损坏。 此外,由于我们使用 reselect 将选择器组合在一起,因此所有使用“getSelectedStepId”的高阶选择器仍然可以正常工作。

填充批量编辑表单

PD 根据规则矩阵确定多个步骤中的哪些字段是可编辑的。 例如,如果用户选择两个转移步骤,并且这两个步骤具有不同的移液器,则他们不应能够修改两个步骤之间共享的移液器流速设置。

使用规则矩阵,我们创建了另一个名为“getMultiSelectDisabledFields”的 redux 选择器,顾名思义,它确定在多选模式下应禁用哪些字段。 它会迭代所选表单中的所有字段,并确定表单是否共享相同的移液器、实验室器具等。根据每个字段的规则,它将返回哪些字段被禁用的映射以及每个字段被禁用的原因。 字段被禁用。 然后,批量编辑表单组件可以使用此信息来填充哪些字段可编辑,哪些字段不可编辑。

批量编辑表单

跟踪批量编辑更改

填充批量编辑表单的字段后,需要在用户修改它们时跟踪其值的更改。 这是出于以下几个原因:

  1. 我们需要知道用户是否对表单进行了任何更改,因为如果他们进行了更改,我们想提醒他们,如果他们尝试退出表单,他们将丢失这些更改。
  2. 一旦他们完成更改,我们需要将他们所做的未保存的更改(影响多个步骤)合并到 PD 全局状态内已保存的步骤映射中。

对于单一编辑模式,有另一个名为“unsavedForm”的减速器,它将所有信息保存在单个未保存的表单中,但我们决定不在批量编辑模式中重用它,因为:

1.批量编辑表单保存多个表单的信息,而不仅仅是一个表单

  1. 在批量编辑模式下,仅保存有关哪些表单字段已更改的信息要有用得多。 这样,当用户保存批量编辑表单时,我们所要做的就是将更改扩展到保存所有已保存表单信息的“savedStepForms”缩减程序中的每个受影响的步骤。 这也意味着只要表示更改的对象不为空,我们就知道用户已进行更改。

为了实现这一点,我们创建了一个名为“batchEditFormChanges”的新减速器,它包含一个纯 JavaScript 对象,将编辑的字段名称表示为键,以及关联的字段值。

重用表单组件

PD 的表单组件非常“智能”,因为它们连接到 redux,因此可以访问表单数据。 问题在于“智能”组件中的逻辑直接与单一编辑模式相关。 为了消除单一编辑模式的依赖,我们决定在单一编辑模式和批量编辑模式下注入表单组件,并使用一组共享名为“FieldProps”的通用 API 的 props

export type FieldProps = {|
  disabled: boolean,
  errorToShow: ?string,
  isIndeterminate?: boolean,
  name: string,
  onFieldBlur: () => mixed,
  onFieldFocus: () => mixed,
  tooltipContent?: ?string,
  updateValue: mixed => void,
  value: mixed,
|}

为了实现这一目标,我们创建了两个单独的函数(一个用于单一编辑模式,一个用于批量编辑模式),负责计算上面的每个“FieldProps”。 它们被恰当地命名为“makeSingleEditFieldProps”和“makeBatchEditFieldProps”。 单一编辑表单的主要父组件使用前者,而批量编辑表单的对应组件则使用后者。

这两个纯函数都获取相应的单编辑/批量编辑状态信息(例如每个表单中包含哪些信息),执行必要的逻辑,并返回一个包含与上面相同的“FieldProps”接口的对象。 这意味着只要我们所有的表单组件接受“FieldProps”接口,它们就可以用于单一编辑模式和批量编辑模式。

将我们现有的表单组件从耦合的单一编辑模式逻辑中迁移出来是一项相当大的工作(而且我们还有更多工作要做),但是创建这个通用的 props 接口允许我们重用现有的表单字段组件,同时绘制一个 单一编辑模式逻辑和批量编辑模式逻辑之间的清晰界限。

要点

上述所有工作(以及更多)花了我们由三名工程师、一名设计师、一名产品经理和一名质量检查工程师组成的团队大约三个月的时间才完成。 您可以在此处查看我们用于选择多个步骤的史诗,以及用于批量编辑表单特定工作的史诗。 我们的所有工作都是开源的,因此请随意浏览我们的代码库。

在开始开发此功能之前,我们问自己整个季度的投资是否值得。 由于批量编辑是迄今为止用户最常见的功能请求,因此我们最终决定如此。 然而,值得注意的是,用户想要同时编辑多个步骤的原因是因为他们的协议通常包含许多步骤,这些步骤更容易批量编辑,而不是一次编辑一个。 随着越来越多的科学家使用 PD 来解决越来越复杂的问题,包含许多步骤的协议问题将会增加。

展望未来,我们希望更好地了解用户到底在使用 PD 构建什么,以及我们可以采取哪些措施来帮助他们最大限度地减少创建步骤。 随着 PD 的不断发展和发展,我们将努力回答这些问题。 虽然我们对此功能感到非常兴奋,但我们的使命是让科学家能够更快地采取行动,并解决我们迫切需要他们解决的问题,这一使命还远未结束。

联系我们

经验丰富的服务团队和强大的生产支持团队为客户提供无忧的订单服务。