一个按钮,三个位置:我们如何通过更严格的 API 重构 Kibana 的页面页眉

作者:来自 Elastic Anton Dosov, Ryan Keairns, Krzysztof Kowalczyk, Alex Marhaba

我们为 Kibana 的共享外壳定义了 类 型化契约,这让设计系统治理成为默认方式,也解释了为什么新的页面页眉不再包含面包屑导航。

通过单一解决方案观察、保护和搜索你的数据。从应用监控到威胁检测,Kibana 都是满足关键使用场景的多用途平台。立即开始你的14 天免费试用

我们将 Kibana 中每个页面页眉(header)背后的开放式 React API 替换为类型化契约(typed contracts),从而将设计系统治理直接融入共享外壳。现在,几十个团队不再需要手动确保每个页眉都正确。页面只需要声明某个控件的含义,而共享外壳则决定它的外观以及位置。我们也在这个过程中移除了面包屑导航,因为重新设计后的导航已经能够告诉你当前所在的位置。新的界面框架目前已经在 Elastic Cloud Serverless 中上线,并将在 9.6 中发布到 Elastic Cloud Hosted 和自管理部署。

什么是 Kibana 界面框架?

界面框架是 Kibana 中构成应用外围框架的一切内容:全局页眉和导航,以及你当前所在页面的页眉。每个应用都会在其中进行渲染。由于界面框架是共享的,它的 API 决定了整个产品的一致性,以及下一次重新设计的难易程度。

用户看到的不一致页面页眉是什么样的

要了解这个问题,最直接的方法就是看看用户实际看到的内容:

以最基本的主要操作为例,也就是页面最希望你点击的那个按钮,例如 创建索引(Create index)。在这项工作之前,主要操作根据页面的不同,会出现在三个不同的位置:

  • 旧的应用页眉中。

  • 页面模板页眉中。

  • 页面正文的某个位置。

常用链接也存在同样的问题,分享、反馈和文档链接会出现在不同的位置,而且不同应用的样式也各不相同。许多页面的面包屑导航配置也存在问题。例如,有些页面会为页面中的每个标签页显示一个面包屑,有些面包屑无法点击。还有一些则会重复显示你已经所在页面的标题。

这些情况无一例外,都是一个出发点良好的团队使用灵活 API 时通常会产生的结果。用户不得不重新学习每个页面的布局,而每次尝试重新设计界面框架时,都必须考虑数百种局部差异。

页面页眉重新设计的目标

重新设计的目标很简单:页面应该表达它们需要什么(例如标题、徽章或主要操作),而共享外壳应该决定这些内容的外观和位置。常用链接和主要操作应该在所有地方都有统一的位置。如果它是主要操作,就始终位于相同的位置,并采用相同的样式。而一致性也不应该永远成为几十个团队需要手动确保正确的事情。

要实现这一目标,并不是简单地重新设计样式;它需要改变应用与平台之间的契约。

类型化属性如何取代 EuiPageHeader 开放式的 React 节点

每个页面页眉都很容易单独构建,而这些局部选择不断累积,最终形成了整个产品中肉眼可见的差异:不同页面之间的间距、标题样式和操作位置都不一致。

无论哪里出现这种问题,解决方法都是同一种机制:更严格的 API。控件不再接受任意的 React 节点,而是通过类型化属性声明其含义,由共享外壳决定它的外观和位置。这一项改变同时带来了两方面的好处:一致性,以及对共享界面允许包含哪些内容的治理能力。

以页面页眉为例。使用 EuiPageHeader 时,API 暴露了布局设置,并为几乎每个可见区域都接受 React 节点:

ini 复制代码
`

1.  <EuiPageHeader
2.    pageTitle={
3.      <>
4.        Index Management
5.        <EuiBadge color="accent">Beta</EuiBadge>
6.      </>
7.    }
8.    description={
9.      <>
10.        View and manage your Elasticsearch indices.{' '}
11.        <EuiLink href={docsUrl}>Learn more</EuiLink>
12.      </>
13.    }
14.    bottomBorder
15.    alignItems="top"
16.    responsive={false}
17.    tabs={tabs}
18.    rightSideItems={[
19.      <RefreshButton onClick={onRefresh} />,
20.      <CreateButton onClick={onCreate} />,
21.    ]}
22.  />

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

由于 pageTitledescriptionrightSideItems 都接受任意的 React 节点,因此每个团队都以不同的方式填充它们。一个页面会在标题旁边放置一个徽章,而另一个页面则自行设计一个标签。一个团队的操作是按某种顺序排列的普通按钮;下一个团队的操作则是另一种组合、另一种顺序,有些会折叠到菜单中,有些则不会。所有人都使用同一个外壳组件,但这些页眉看起来和使用起来却像是来自不同的产品。共享组件保证了外部包装,却无法保证其中包含的内容。

AppHeader API 则关闭了这种开放性。同一个页眉现在通过类型化属性来表达:

css 复制代码
`

1.  <AppHeader
2.    title="Index Management"
3.    badges={[4.      {5.        label: 'Beta',6.        color: 'accent',7.        tooltip: 'This feature is in beta.',8.      },9.    ]}
10.    description={{
11.      text: 'View and manage your Elasticsearch indices.',
12.      learnMoreUrl: docsUrl,
13.    }}
14.    tabs={tabs}
15.    menu={{
16.      primaryActionItem: {
17.        id: 'create',
18.        label: 'Create index',
19.        iconType: 'plusInCircle',
20.        run: onCreate,
21.      },
22.      items: [
23.        {
24.          id: 'refresh',
25.          label: 'Refresh',
26.          iconType: 'refresh',
27.          run: onRefresh,
28.        },
29.      ],
30.    }}
31.  />

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

现在,每个部分都只有一种表达方式,因此每个页面的渲染方式也都一致。徽章位于独立的类型化集合中,与标题分开。描述包含其文本,并可以选择提供一个"了解更多"URL。操作会声明自己是主要操作还是次要操作,而组件则决定它们的外观以及何时折叠。此外,更丰富的控件也遵循相同的结构。可编辑标题或收藏切换按钮现在都是结构化配置,而不是自定义的 React 树:

ini 复制代码
`

1.  <AppHeader
2.    title={{
3.      text: indexName,
4.      onSave: renameIndex,
5.    }}
6.    favorite={{
7.      status: favoriteStatus,
8.      onToggle: toggleFavorite,
9.    }}
10.  />

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

这种职责划分在整个系统中都是一致的;应用提供状态和行为(当前名称、重命名时执行什么操作、如何切换收藏状态),而页眉负责呈现。它负责渲染控件、处理键盘交互、显示验证错误,以及反映待处理的更改。由于这些职责都是明确的,布局和响应式行为就可以保留在共享组件内部,而应用不需要在每次页眉发生变化时都进行协调更新。

为什么共享 UI 外壳需要一组封闭的控件

全局外壳也存在类似的问题,只不过开放程度更高了一层。任何插件都可以在左侧或右侧注册任意内容,并通过一个数字选择其位置,而且无需与平台团队或设计师进行任何沟通:

markdown 复制代码
`

1.  chrome.navControls.registerLeft({
2.    content: <ProjectPicker />,
3.  });

5.  chrome.navControls.registerRight({
6.    order: 10,
7.    content: <AiAssistantButton />,
8.  });

10.  chrome.navControls.registerRight({
11.    order: 20,
12.    content: <FeedbackButton />,
13.  });

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

这个 registerRight API 就像一扇敞开的门。任何团队都可以添加控件,而页眉最终会堆满没有经过统一设计的元素。这些控件会争夺空间,并各自携带自己的样式;它们的顺序则由选择了更大数字的人决定。外壳只知道一个 React 节点排在另一个 React 节点之后;它无法判断一个控件用于打开 AI 助手,而另一个用于收集反馈,因此也无法对它们进行合理处理或保持整体一致。

新的外壳通过 chrome.controlschrome.help 下的一组封闭的命名角色,取代了这个开放画布。旧的 注册机制 目前还没有消失,因为在应用迁移期间,经典页眉仍然与新页眉并行运行,但新外壳中的任何内容都无法通过它访问:

ini 复制代码
`

1.  chrome.controls.projectPicker.set(<ProjectPicker />);
2.  chrome.controls.aiButton.register({ content: <AiAssistantButton /> });
3.  chrome.controls.globalSearch.set({ onClick: openGlobalSearch });
4.  chrome.help.registerFeedbackHandler(openFeedback);

`AI写代码

全局 chrome 页眉与应用页面页眉

旧版界面框架之所以难以演进,部分原因在于 "页眉" 实际上是多个相互纠缠的东西。重新设计后,我们明确区分了两个由不同所有者负责的界面:

界面框架(全局)页眉 应用页眉
所有者 平台 页面
范围 Kibana 中任何地方都成立的内容 当前页面上的内容
内容 导航、项目或部署选择器、搜索、帮助、AI 助手、反馈 标题、徽章、描述、标签页、页面操作
如何填充 chrome.controlschrome.help 下的命名插槽 AppHeader 类型化属性,直接渲染或通过 chrome.appHeader.set() 设置

由于每个界面都有唯一的所有者,并且两者之间通过类型化契约进行连接,平台就可以在不审查数百个页面的情况下重新设计界面框架,而应用也可以在不与全局控件发生冲突的情况下演进自己的页眉。设置页眉配置会返回一个清理回调,因此应用在卸载时可以清理自己添加的内容。

严格 API 强制产生的设计决策

更严格的 API 强制我们做出了一些设计决策,而灵活的 API 让每个人都可以一直推迟这些决定。其中有两个特别值得说明。

为什么我们从 Kibana 导航中移除了面包屑导航

从界面框架中移除面包屑导航是争议较大的决定之一,而实际数据让这件事比预期更容易。在实践中,Kibana 中的面包屑导航存在大量配置错误:

  • 有些页面会为页面中的每个标签页生成一个面包屑。

  • 有些面包屑无法点击。

  • 有些会重复显示当前页面的标题。

它们增加了视觉负担,却无法可靠地帮助用户确定当前位置。

与此同时,重新设计后的导航已经能够传达层级关系。主导航和次级导航会告诉你当前所在的位置,并提供直接返回上层的方式。保留面包屑意味着需要维护同一信息的第三种(而且经常是错误的)表达方式,因此我们停止绘制这条路径,让导航承担这项工作。

面包屑 API 本身并没有消失。应用仍然可以注册面包屑,而新外壳会读取这些面包屑来生成返回按钮;如果应用没有提供页面标题,它也会使用面包屑回退到页面标题。数据的职责发生了变化,而不是直接消失,这也是为什么将应用迁移到新页眉时,通常不需要一开始就把面包屑全部移除:

Before:

After:

页面页眉应该显示多少个操作按钮

另一个反复出现的争论是优先级。现在每个页面的操作都会通过同一个菜单结构呈现,那么哪些按钮应该直接显示,以及应该按照什么顺序显示?随着产品不断演进,优先级也会发生变化,因此这场讨论仍在继续。

在正式发布时,我们做了一个有意的简化:限制应用菜单中可以显示的按钮数量。一个页面可以有一个主要操作和数量受限的次要操作,剩余操作则折叠到菜单中。这一限制避免单个页面通过一整排按钮分散用户的注意力,同时也让优先级问题变得明确,而不是由下一个添加按钮的人自行决定。

我们经过了多次迭代才最终确定这个方案。最初,我们允许显示很多操作。一个页面最多可以有三个按钮,而且主要按钮左侧还可以有一个次要操作。这占用了太多空间,因此我们逐步简化布局,移除了次要操作,并减少了可见按钮的数量。我们还让溢出菜单变得更加结构化。例如,反馈和文档等一些项目现在会固定显示在溢出菜单的页脚中。

严格的设计系统治理给团队带来了什么成本

在旧 API 下,插件可以通过选择一侧、指定顺序并挂载一个 React 树来添加新的 UI。在更严格的 API 下,一种新的控件类型可能需要先具备共享能力,插件才能添加它。这会增加前期工作,并迫使团队做出过去可以回避的设计决策。

我们接受了严格 API 带来的成本,因为另一种选择只是把成本推迟到之后的每一次重新设计中。只要任意 React 树可以挂载到任意位置,界面框架的布局或无障碍能力每发生一次变化,就必须考虑所有这些内容。

这就是我们做出的权衡,而且它在两个方面都能带来回报。一致性不再是每个团队都必须手动确保正确的事情;表达一个控件只有一种方式,因此页面默认就是一致的。同时,共享界面也始终受到治理。它所包含的控件集合是一项有意做出的设计决策,而不是边缘位置不断累积的结果。应用描述其控件的含义,而外壳可以自由决定它们的呈现方式,无论是今天还是下一次重新设计。

重新设计的 Kibana 界面框架在哪里可用

重新设计的界面框架目前已经在 Elastic Cloud Serverless 中可用。打开任何项目,你就已经在使用它。对于 Elastic Cloud Hosted 和自管理用户,它将在 9.6 中发布。

本文所述任何功能或特性的发布及时间安排均由 Elastic 自行决定。目前尚未提供的任何功能或特性可能无法按时交付,也可能根本不会交付。

原文:Design system governance: Rebuilding Kibana's page headers | Elasticsearch Labs

相关推荐
Elasticsearch4 小时前
信任,但要进行基准测试:我们如何让 AI agent 优化 Elasticsearch
elasticsearch
艾莉丝努力练剑7 小时前
【Git:综合复盘】Git 原理与使用
大数据·人工智能·git·elasticsearch·面试
Elasticsearch1 天前
列式存储并不等同于列式数据库。Columnar 模式为 Elasticsearch 带来了什么
elasticsearch
Elastic 中国社区官方博客1 天前
将 Vercel 数据导入 Elastic:无需安装任何东西的无服务器可观测性
大数据·运维·elasticsearch·搜索引擎·云原生·serverless·全文检索
Elasticsearch1 天前
我们如何将 PromQL 构建到 Elasticsearch 中
elasticsearch
Elasticsearch1 天前
你和你的 AI agent 不应该使用 curl:介绍 Elastic CLI 和 Agent Skills
elasticsearch
yunqiz2 天前
ELK Stack生产环境部署指南:Filebeat + Elasticsearch + Logstash + Kibana
elk·elasticsearch
dongsdh3 天前
部署python
大数据·elasticsearch·搜索引擎
听到微笑3 天前
Elasticsearch 如何存储与检索海量向量
数据库·elasticsearch