From 118125168cd28806f033b3a45935fbf3bc468e25 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Wed, 10 Jan 2024 21:28:52 +0100 Subject: [PATCH] [UnifiedDocViewer] Redesign header and make actions responsive (#173780) - Resolves https://github.com/elastic/kibana/issues/163239 - Resolves https://github.com/elastic/kibana/issues/164660 - Related PR https://github.com/elastic/kibana/pull/173765 ## Summary - large screen and max 3 items => it will render an icon and a label for each action: Screenshot 2024-01-03 at 18 02 23 - otherwise: max 3 icons and the rest in a context menu: Screenshot 2024-01-03 at 18 02 07 - small screen: all items are in the context menu Screenshot 2024-01-03 at 18 01 39 I also extended "Discover customization" example plugin to showcase more actions. For testing you can run kibana with `yarn start --run-examples` and update number of additional actions locally via https://github.com/jughosta/kibana/blob/690c38e689d8fb802cce8155c1a300f5ca9cb94f/examples/discover_customization_examples/public/plugin.tsx#L411 ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Davis McPhee --- .../public/plugin.tsx | 23 ++ .../src/components/data_table.scss | 8 - .../discover_grid_flyout.test.tsx | 121 ++++++++++- .../discover_grid_flyout.tsx | 45 ++-- .../discover_grid_flyout_actions.tsx | 201 ++++++++++++++++++ .../use_flyout_actions.tsx | 127 +++-------- .../flyout_customization.ts | 11 +- .../translations/translations/fr-FR.json | 4 - .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - 10 files changed, 403 insertions(+), 145 deletions(-) create mode 100644 src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout_actions.tsx diff --git a/examples/discover_customization_examples/public/plugin.tsx b/examples/discover_customization_examples/public/plugin.tsx index 9368c943532a4a..b66559b020d7a7 100644 --- a/examples/discover_customization_examples/public/plugin.tsx +++ b/examples/discover_customization_examples/public/plugin.tsx @@ -12,6 +12,7 @@ import { EuiFlexItem, EuiPopover, EuiWrappingPopover, + IconType, } from '@elastic/eui'; import { AppNavLinkStatus, @@ -401,6 +402,28 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin { }, }); + customizations.set({ + id: 'flyout', + size: '60%', + title: 'Example custom flyout', + actions: { + getActionItems: () => + Array.from({ length: 5 }, (_, i) => { + const index = i + 1; + return { + id: `action-item-${index}`, + enabled: true, + label: `Action ${index}`, + iconType: ['faceHappy', 'faceNeutral', 'faceSad', 'infinity', 'bell'].at( + i + ) as IconType, + dataTestSubj: `customActionItem${index}`, + onClick: () => alert(index), + }; + }), + }, + }); + return () => { // eslint-disable-next-line no-console console.log('Cleaning up Logs explorer customizations'); diff --git a/packages/kbn-unified-data-table/src/components/data_table.scss b/packages/kbn-unified-data-table/src/components/data_table.scss index e033206635eec0..5c3a6b14ecfa9f 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.scss +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -73,14 +73,6 @@ min-height: 0; } -.unifiedDataTable__flyoutHeader { - white-space: nowrap; -} - -.unifiedDataTable__flyoutDocumentNavigation { - justify-content: flex-end; -} - // We only truncate if the cell is not a control column. .euiDataGridHeader { diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx index 8d4e9bbee4ae10..485a3d2f8a4fe7 100644 --- a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx @@ -7,8 +7,8 @@ */ import React from 'react'; +import { EuiButtonIcon, EuiContextMenuItem, EuiPopover } from '@elastic/eui'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { EuiFlexItem } from '@elastic/eui'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { Query, AggregateQuery } from '@kbn/es-query'; import { DiscoverGridFlyout, DiscoverGridFlyoutProps } from './discover_grid_flyout'; @@ -36,6 +36,22 @@ jest.mock('../../customizations', () => ({ useDiscoverCustomization: jest.fn(), })); +let mockBreakpointSize: string | null = null; + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + useIsWithinBreakpoints: jest.fn((breakpoints: string[]) => { + if (mockBreakpointSize && breakpoints.includes(mockBreakpointSize)) { + return true; + } + + return original.useIsWithinBreakpoints(breakpoints); + }), + }; +}); + const waitNextTick = () => new Promise((resolve) => setTimeout(resolve, 0)); const waitNextUpdate = async (component: ReactWrapper) => { @@ -227,7 +243,7 @@ describe('Discover flyout', function () { const singleDocumentView = findTestSubject(component, 'docTableRowAction'); expect(singleDocumentView.length).toBeFalsy(); const flyoutTitle = findTestSubject(component, 'docTableRowDetailsTitle'); - expect(flyoutTitle.text()).toBe('Expanded row'); + expect(flyoutTitle.text()).toBe('Row'); }); describe('with applied customizations', () => { @@ -246,17 +262,32 @@ describe('Discover flyout', function () { describe('when actions are customized', () => { it('should display actions added by getActionItems', async () => { + mockBreakpointSize = 'xl'; mockFlyoutCustomization.actions = { getActionItems: jest.fn(() => [ { id: 'action-item-1', enabled: true, - Content: () => Action 1, + label: 'Action 1', + iconType: 'document', + dataTestSubj: 'customActionItem1', + onClick: jest.fn(), }, { id: 'action-item-2', enabled: true, - Content: () => Action 2, + label: 'Action 2', + iconType: 'document', + dataTestSubj: 'customActionItem2', + onClick: jest.fn(), + }, + { + id: 'action-item-3', + enabled: false, + label: 'Action 3', + iconType: 'document', + dataTestSubj: 'customActionItem3', + onClick: jest.fn(), }, ]), }; @@ -268,6 +299,88 @@ describe('Discover flyout', function () { expect(action1.text()).toBe('Action 1'); expect(action2.text()).toBe('Action 2'); + expect(findTestSubject(component, 'customActionItem3').exists()).toBe(false); + mockBreakpointSize = null; + }); + + it('should display multiple actions added by getActionItems', async () => { + mockFlyoutCustomization.actions = { + getActionItems: jest.fn(() => + Array.from({ length: 5 }, (_, i) => ({ + id: `action-item-${i}`, + enabled: true, + label: `Action ${i}`, + iconType: 'document', + dataTestSubj: `customActionItem${i}`, + onClick: jest.fn(), + })) + ), + }; + + const { component } = await mountComponent({}); + expect( + findTestSubject(component, 'docViewerFlyoutActions') + .find(EuiButtonIcon) + .map((button) => button.prop('data-test-subj')) + ).toEqual([ + 'docTableRowAction', + 'customActionItem0', + 'customActionItem1', + 'docViewerMoreFlyoutActionsButton', + ]); + + act(() => { + findTestSubject(component, 'docViewerMoreFlyoutActionsButton').simulate('click'); + }); + + component.update(); + + expect( + component + .find(EuiPopover) + .find(EuiContextMenuItem) + .map((button) => button.prop('data-test-subj')) + ).toEqual(['customActionItem2', 'customActionItem3', 'customActionItem4']); + }); + + it('should display multiple actions added by getActionItems in mobile view', async () => { + mockBreakpointSize = 's'; + + mockFlyoutCustomization.actions = { + getActionItems: jest.fn(() => + Array.from({ length: 3 }, (_, i) => ({ + id: `action-item-${i}`, + enabled: true, + label: `Action ${i}`, + iconType: 'document', + dataTestSubj: `customActionItem${i}`, + onClick: jest.fn(), + })) + ), + }; + + const { component } = await mountComponent({}); + expect(findTestSubject(component, 'docViewerFlyoutActions').length).toBe(0); + + act(() => { + findTestSubject(component, 'docViewerMobileActionsButton').simulate('click'); + }); + + component.update(); + + expect( + component + .find(EuiPopover) + .find(EuiContextMenuItem) + .map((button) => button.prop('data-test-subj')) + ).toEqual([ + 'docTableRowAction', + 'customActionItem0', + 'customActionItem1', + 'customActionItem2', + ]); + + mockBreakpointSize = null; }); it('should allow disabling default actions', async () => { diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx index 58d60466e17b08..40d47e1292f92f 100644 --- a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx @@ -8,6 +8,7 @@ import React, { useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; import type { DataView } from '@kbn/data-views-plugin/public'; import { EuiFlexGroup, @@ -29,6 +30,7 @@ import { useDiscoverServices } from '../../hooks/use_discover_services'; import { isTextBasedQuery } from '../../application/main/utils/is_text_based_query'; import { useFlyoutActions } from './use_flyout_actions'; import { useDiscoverCustomization } from '../../customizations'; +import { DiscoverGridFlyoutActions } from './discover_grid_flyout_actions'; export interface DiscoverGridFlyoutProps { savedSearchId?: string; @@ -189,14 +191,13 @@ export function DiscoverGridFlyout({ ); const defaultFlyoutTitle = isPlainRecord - ? i18n.translate('discover.grid.tableRow.textBasedDetailHeading', { - defaultMessage: 'Expanded row', + ? i18n.translate('discover.grid.tableRow.docViewerTextBasedDetailHeading', { + defaultMessage: 'Row', }) - : i18n.translate('discover.grid.tableRow.detailHeading', { - defaultMessage: 'Expanded document', + : i18n.translate('discover.grid.tableRow.docViewerDetailHeading', { + defaultMessage: 'Document', }); const flyoutTitle = flyoutCustomization?.title ?? defaultFlyoutTitle; - const flyoutSize = flyoutCustomization?.size ?? 'm'; return ( @@ -209,17 +210,24 @@ export function DiscoverGridFlyout({ ownFocus={false} > - -

{flyoutTitle}

-
- - - {!isPlainRecord && - flyoutActions.map((action) => action.enabled && )} + + +

{flyoutTitle}

+
+
{activePage !== -1 && ( )}
+ {isPlainRecord || !flyoutActions.length ? null : ( + <> + + + + )}
{bodyContent} diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout_actions.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout_actions.tsx new file mode 100644 index 00000000000000..a9b168ef7ae8e3 --- /dev/null +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout_actions.tsx @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { slice } from 'lodash'; +import { css } from '@emotion/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiContextMenuPanel, + EuiContextMenuItem, + EuiContextMenuItemIcon, + useIsWithinBreakpoints, + EuiText, + EuiButtonEmpty, + EuiButtonIcon, + EuiPopoverProps, + EuiToolTip, + useEuiTheme, +} from '@elastic/eui'; +import type { FlyoutActionItem } from '../../customizations'; + +const MAX_VISIBLE_ACTIONS_BEFORE_THE_FOLD = 3; + +export interface DiscoverGridFlyoutActionsProps { + flyoutActions: FlyoutActionItem[]; +} + +export function DiscoverGridFlyoutActions({ flyoutActions }: DiscoverGridFlyoutActionsProps) { + const { euiTheme } = useEuiTheme(); + const [isMoreFlyoutActionsPopoverOpen, setIsMoreFlyoutActionsPopoverOpen] = + useState(false); + const isMobileScreen = useIsWithinBreakpoints(['xs', 's']); + const isLargeScreen = useIsWithinBreakpoints(['xl']); + + if (isMobileScreen) { + return ( + setIsMoreFlyoutActionsPopoverOpen(!isMoreFlyoutActionsPopoverOpen)} + > + {i18n.translate('discover.grid.tableRow.mobileFlyoutActionsButton', { + defaultMessage: 'Actions', + })} + + } + isOpen={isMoreFlyoutActionsPopoverOpen} + closePopover={() => setIsMoreFlyoutActionsPopoverOpen(false)} + /> + ); + } + + const visibleFlyoutActions = slice(flyoutActions, 0, MAX_VISIBLE_ACTIONS_BEFORE_THE_FOLD); + const remainingFlyoutActions = slice( + flyoutActions, + MAX_VISIBLE_ACTIONS_BEFORE_THE_FOLD, + flyoutActions.length + ); + const showFlyoutIconsOnly = + remainingFlyoutActions.length > 0 || (!isLargeScreen && visibleFlyoutActions.length > 1); + + return ( + + + + + {i18n.translate('discover.grid.tableRow.actionsLabel', { + defaultMessage: 'Actions', + })} + : + + + + {visibleFlyoutActions.map((action) => ( + + {showFlyoutIconsOnly ? ( + + + + ) : ( + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {action.label} + + + )} + + ))} + {remainingFlyoutActions.length > 0 && ( + + + setIsMoreFlyoutActionsPopoverOpen(!isMoreFlyoutActionsPopoverOpen)} + /> + + } + isOpen={isMoreFlyoutActionsPopoverOpen} + closePopover={() => setIsMoreFlyoutActionsPopoverOpen(false)} + /> + + )} + + ); +} + +function FlyoutActionsPopover({ + flyoutActions, + button, + isOpen, + closePopover, +}: { + flyoutActions: DiscoverGridFlyoutActionsProps['flyoutActions']; + button: EuiPopoverProps['button']; + isOpen: EuiPopoverProps['isOpen']; + closePopover: EuiPopoverProps['closePopover']; +}) { + return ( + + ( + + {action.label} + + ))} + /> + + ); +} diff --git a/src/plugins/discover/public/components/discover_grid_flyout/use_flyout_actions.tsx b/src/plugins/discover/public/components/discover_grid_flyout/use_flyout_actions.tsx index fb364995b1c215..e0df28e468003d 100644 --- a/src/plugins/discover/public/components/discover_grid_flyout/use_flyout_actions.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/use_flyout_actions.tsx @@ -6,35 +6,18 @@ * Side Public License, v 1. */ -import React from 'react'; -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiHideFor, - EuiIconTip, - EuiText, -} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FlyoutCustomization } from '../../customizations'; +import { FlyoutActionItem, FlyoutCustomization } from '../../customizations'; import { UseNavigationProps, useNavigationProps } from '../../hooks/use_navigation_props'; interface UseFlyoutActionsParams extends UseNavigationProps { actions?: FlyoutCustomization['actions']; } -interface FlyoutActionProps { - onClick: React.MouseEventHandler; - href: string; -} - -const staticViewDocumentItem = { - id: 'viewDocument', - enabled: true, - Content: () => , -}; - -export const useFlyoutActions = ({ actions, ...props }: UseFlyoutActionsParams) => { +export const useFlyoutActions = ({ + actions, + ...props +}: UseFlyoutActionsParams): { flyoutActions: FlyoutActionItem[] } => { const { dataView } = props; const { singleDocHref, contextViewHref, onOpenSingleDoc, onOpenContextView } = useNavigationProps(props); @@ -45,95 +28,35 @@ export const useFlyoutActions = ({ actions, ...props }: UseFlyoutActionsParams) } = actions?.defaultActions ?? {}; const customActions = [...(actions?.getActionItems?.() ?? [])]; - const flyoutActions = [ + const flyoutActions: FlyoutActionItem[] = [ { id: 'singleDocument', enabled: !viewSingleDocument.disabled, - Content: () => , + dataTestSubj: 'docTableRowAction', + iconType: 'document', + href: singleDocHref, + onClick: onOpenSingleDoc, + label: i18n.translate('discover.grid.tableRow.viewSingleDocumentLinkLabel', { + defaultMessage: 'View single document', + }), }, { id: 'surroundingDocument', enabled: Boolean(!viewSurroundingDocument.disabled && dataView.isTimeBased() && dataView.id), - Content: () => , + dataTestSubj: 'docTableRowAction', + iconType: 'documents', + href: contextViewHref, + onClick: onOpenContextView, + label: i18n.translate('discover.grid.tableRow.viewSurroundingDocumentsLinkLabel', { + defaultMessage: 'View surrounding documents', + }), + helpText: i18n.translate('discover.grid.tableRow.viewSurroundingDocumentsHover', { + defaultMessage: + 'Inspect documents that occurred before and after this document. Only pinned filters remain active in the Surrounding documents view.', + }), }, ...customActions, ]; - const hasEnabledActions = flyoutActions.some((action) => action.enabled); - - if (hasEnabledActions) { - flyoutActions.unshift(staticViewDocumentItem); - } - - return { flyoutActions, hasEnabledActions }; -}; - -const ViewDocument = () => { - return ( - - - - - {i18n.translate('discover.grid.tableRow.viewText', { - defaultMessage: 'View:', - })} - - - - - ); -}; - -const SingleDocument = (props: FlyoutActionProps) => { - return ( - - - {i18n.translate('discover.grid.tableRow.viewSingleDocumentLinkTextSimple', { - defaultMessage: 'Single document', - })} - - - ); -}; - -const SurroundingDocuments = (props: FlyoutActionProps) => { - return ( - - - - {i18n.translate('discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple', { - defaultMessage: 'Surrounding documents', - })} - - - - - - - ); + return { flyoutActions: flyoutActions.filter((action) => action.enabled) }; }; diff --git a/src/plugins/discover/public/customizations/customization_types/flyout_customization.ts b/src/plugins/discover/public/customizations/customization_types/flyout_customization.ts index a57a538f216423..794711ba17b178 100644 --- a/src/plugins/discover/public/customizations/customization_types/flyout_customization.ts +++ b/src/plugins/discover/public/customizations/customization_types/flyout_customization.ts @@ -5,10 +5,10 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { EuiFlyoutProps } from '@elastic/eui'; +import { EuiFlyoutProps, IconType } from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/types'; -import React, { type ComponentType } from 'react'; +import React, { type ComponentType, MouseEventHandler } from 'react'; export interface FlyoutDefaultActionItem { disabled?: boolean; @@ -21,8 +21,13 @@ export interface FlyoutDefaultActions { export interface FlyoutActionItem { id: string; - Content: React.ElementType; enabled: boolean; + label: string; + helpText?: string; + iconType: IconType; + onClick: (() => void) | MouseEventHandler; + href?: string; + dataTestSubj?: string; } export interface FlyoutContentProps { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 7d00d772784e68..0993a567fe06c0 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2360,12 +2360,8 @@ "discover.grid.flyout.documentNavigation": "Navigation dans le document", "discover.grid.flyout.toastColumnAdded": "La colonne \"{columnName}\" a été ajoutée.", "discover.grid.flyout.toastColumnRemoved": "La colonne \"{columnName}\" a été supprimée.", - "discover.grid.tableRow.detailHeading": "Document développé", "discover.grid.tableRow.textBasedDetailHeading": "Ligne développée", - "discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "Document unique", "discover.grid.tableRow.viewSurroundingDocumentsHover": "Inspectez des documents qui ont été créés avant et après ce document. Seuls les filtres épinglés restent actifs dans la vue Documents relatifs.", - "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "Documents relatifs", - "discover.grid.tableRow.viewText": "Afficher :", "discover.helpMenu.appName": "Découverte", "discover.inspectorRequestDataTitleDocuments": "Documents", "discover.inspectorRequestDataTitleMoreDocuments": "Plus de documents", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index aeb90ddb6e8abd..7de467b1da61c3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2374,12 +2374,8 @@ "discover.grid.flyout.documentNavigation": "ドキュメントナビゲーション", "discover.grid.flyout.toastColumnAdded": "列'{columnName}'が追加されました", "discover.grid.flyout.toastColumnRemoved": "列'{columnName}'が削除されました", - "discover.grid.tableRow.detailHeading": "拡張ドキュメント", "discover.grid.tableRow.textBasedDetailHeading": "展開された行", - "discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "1つのドキュメント", "discover.grid.tableRow.viewSurroundingDocumentsHover": "このドキュメントの前後に出現したドキュメントを検査します。周りのドキュメントビューでは、固定されたフィルターのみがアクティブのままです。", - "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "周りのドキュメント", - "discover.grid.tableRow.viewText": "表示:", "discover.helpMenu.appName": "Discover", "discover.inspectorRequestDataTitleDocuments": "ドキュメント", "discover.inspectorRequestDataTitleMoreDocuments": "その他のドキュメント", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 965af95be52733..97a9fec267ac74 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2374,12 +2374,8 @@ "discover.grid.flyout.documentNavigation": "文档导航", "discover.grid.flyout.toastColumnAdded": "已添加列“{columnName}”", "discover.grid.flyout.toastColumnRemoved": "已移除列“{columnName}”", - "discover.grid.tableRow.detailHeading": "已展开文档", "discover.grid.tableRow.textBasedDetailHeading": "已展开行", - "discover.grid.tableRow.viewSingleDocumentLinkTextSimple": "单个文档", "discover.grid.tableRow.viewSurroundingDocumentsHover": "检查在此文档之前和之后出现的文档。在周围文档视图中,仅已固定筛选仍处于活动状态。", - "discover.grid.tableRow.viewSurroundingDocumentsLinkTextSimple": "周围文档", - "discover.grid.tableRow.viewText": "视图:", "discover.helpMenu.appName": "Discover", "discover.inspectorRequestDataTitleDocuments": "文档", "discover.inspectorRequestDataTitleMoreDocuments": "更多文档",