mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Discover] Redesign for the grid, panels and sidebar v1 (#165866)
## Summary
### Part 1
- Resolves https://github.com/elastic/kibana/issues/164287
- Closes https://github.com/elastic/kibana/issues/146339
- Previously separate PR https://github.com/elastic/kibana/pull/164187
Changes:
- ~~swaps checkbox and row selection~~
- removes vertical borders
- adds rows highlight
- increases cell padding
- adds row stripes
- updates header background
- removes grey background from field name and makes it bolder (part of
https://github.com/elastic/kibana/issues/164634)
- updates Surrounding Documents side paddings
### Part 2
- Resolves https://github.com/elastic/kibana/issues/164661
- Previously separate PR https://github.com/elastic/kibana/pull/165687
Changes:
- removes background from panels, tabs and sidebar
- updates "Add a field" button style
- removes shadow from field list items
- makes field search compact
### Part 3
- Resolves https://github.com/elastic/kibana/issues/164662
Changes:
- wraps "Add a field" button in its own container with a top border
- ~~adds a drag handle to sidebar items~~
- ~~adds new Show/Hide buttons to toggle sidebar~~ moves sidebar toggle
button from discover plugin to unified field list
- reduces spaces between sidebar items from 4px to 2px
- reduces padding on Single Document page
- removes border above grid tabs
<img width="600" alt="Screenshot 2023-09-07 at 14 39 48"
src="976db247
-fd70-4c9b-8634-552ece45b522">
Please note that "auto" row height is in a separate PR which is also
ready for review https://github.com/elastic/kibana/pull/164218
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Davis McPhee <davismcphee@hotmail.com>
Co-authored-by: Davis McPhee <davis.mcphee@elastic.co>
This commit is contained in:
parent
6eea595153
commit
6cb937a37a
49 changed files with 807 additions and 280 deletions
|
@ -33,6 +33,8 @@ const getCreationOptions: UnifiedFieldListSidebarContainerProps['getCreationOpti
|
|||
originatingApp: PLUGIN_ID,
|
||||
localStorageKeyPrefix: 'examples',
|
||||
timeRangeUpdatesType: 'timefilter',
|
||||
compressed: true,
|
||||
showSidebarToggleButton: true,
|
||||
disablePopularFields: true,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -119,6 +119,28 @@ describe('DragDrop', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('dragstart sets dragClassName as expected', async () => {
|
||||
const dndDispatch = jest.fn();
|
||||
const component = mount(
|
||||
<ChildDragDropProvider value={[{ ...defaultContextState, dragging: undefined }, dndDispatch]}>
|
||||
<DragDrop value={value} draggable={true} order={[2, 0, 1, 0]} dragClassName="dragTest">
|
||||
<button>Hi!</button>
|
||||
</DragDrop>
|
||||
</ChildDragDropProvider>
|
||||
);
|
||||
const dragDrop = component.find('[data-test-subj="testDragDrop"]').at(0);
|
||||
|
||||
expect(dragDrop.getDOMNode().querySelector('.dragTest')).toBeNull();
|
||||
dragDrop.simulate('dragstart', { dataTransfer });
|
||||
expect(dragDrop.getDOMNode().querySelector('.dragTest')).toBeDefined();
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(dragDrop.getDOMNode().querySelector('.dragTest')).toBeNull();
|
||||
});
|
||||
|
||||
test('drop resets all the things', async () => {
|
||||
const preventDefault = jest.fn();
|
||||
const stopPropagation = jest.fn();
|
||||
|
|
|
@ -42,6 +42,10 @@ interface BaseProps {
|
|||
* The CSS class(es) for the root element.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* CSS class to apply when the item is being dragged
|
||||
*/
|
||||
dragClassName?: string;
|
||||
|
||||
/**
|
||||
* The event handler that fires when an item
|
||||
|
@ -212,6 +216,7 @@ const removeSelection = () => {
|
|||
const DragInner = memo(function DragInner({
|
||||
dataTestSubj,
|
||||
className,
|
||||
dragClassName,
|
||||
value,
|
||||
children,
|
||||
dndDispatch,
|
||||
|
@ -305,6 +310,18 @@ const DragInner = memo(function DragInner({
|
|||
// so we know we have DraggableProps if we reach this code.
|
||||
if (e && 'dataTransfer' in e) {
|
||||
e.dataTransfer.setData('text', value.humanData.label);
|
||||
|
||||
// Apply an optional class to the element being dragged so the ghost
|
||||
// can be styled. We must add it to the actual element for a single
|
||||
// frame before removing it so the ghost picks up the styling.
|
||||
const current = e.currentTarget;
|
||||
|
||||
if (dragClassName && !current.classList.contains(dragClassName)) {
|
||||
current.classList.add(dragClassName);
|
||||
requestAnimationFrame(() => {
|
||||
current.classList.remove(dragClassName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Chrome causes issues if you try to render from within a
|
||||
|
|
|
@ -3,32 +3,6 @@
|
|||
max-width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: $euiBorderRadius;
|
||||
|
||||
.euiDataGrid__controls {
|
||||
border: none;
|
||||
border-bottom: $euiBorderThin;
|
||||
}
|
||||
|
||||
.euiDataGridRowCell.euiDataGridRowCell--firstColumn {
|
||||
border-left: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.euiDataGridRowCell.euiDataGridRowCell--lastColumn {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.unifiedDataTable__table .euiDataGridRowCell:first-of-type,
|
||||
.unifiedDataTable__table .euiDataGrid--headerShade.euiDataGrid--bordersAll .euiDataGridHeaderCell:first-of-type {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.euiDataGridRowCell:last-of-type,
|
||||
.euiDataGridHeaderCell:last-of-type {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
.unifiedDataTable__cellValue {
|
||||
|
@ -57,6 +31,29 @@
|
|||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
height: 100%;
|
||||
|
||||
.euiDataGrid__content {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.euiDataGrid__controls {
|
||||
border-top: $euiBorderThin;
|
||||
}
|
||||
|
||||
.euiDataGrid--headerUnderline .euiDataGridHeaderCell {
|
||||
border-bottom: $euiBorderThin;
|
||||
}
|
||||
|
||||
.euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id='openDetails'],
|
||||
.euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id='select'] {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.euiDataGrid--rowHoverHighlight .euiDataGridRow:hover,
|
||||
.euiDataGrid--rowHoverHighlight .euiDataGridRow:hover .euiDataGridRowCell__contentByHeight + .euiDataGridRowCell__expandActions {
|
||||
background-color: tintOrShade($euiColorLightShade, 50%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.unifiedDataTable__table {
|
||||
|
@ -65,14 +62,6 @@
|
|||
min-height: 0;
|
||||
}
|
||||
|
||||
.unifiedDataTable__footer {
|
||||
flex-shrink: 0;
|
||||
background-color: $euiColorLightShade;
|
||||
padding: $euiSize / 2 $euiSize;
|
||||
margin-top: $euiSize / 4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.unifiedDataTable__flyoutHeader {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
@ -118,7 +107,35 @@
|
|||
@include euiTextTruncate;
|
||||
}
|
||||
|
||||
.unifiedDataTable__rowControl {
|
||||
// fine-tuning the vertical alignment with the text for any row height setting
|
||||
margin-top: -3px;
|
||||
.euiDataGridRowCell__truncate & { // "Single line" row height setting
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.unifiedDataTable__descriptionList {
|
||||
// force the content truncation when "Single line" row height setting is active
|
||||
.euiDataGridRowCell__truncate & {
|
||||
-webkit-line-clamp: 1;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.unifiedDataTable__descriptionListTitle {
|
||||
margin-inline: 0 0;
|
||||
padding-inline: 0;
|
||||
background: transparent;
|
||||
font-weight: $euiFontWeightBold;
|
||||
}
|
||||
|
||||
.unifiedDataTable__descriptionListDescription {
|
||||
margin-inline: $euiSizeS $euiSizeS;
|
||||
padding-inline: 0;
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ describe('Data table columns', function () {
|
|||
</div>,
|
||||
"displayAsText": "timestamp",
|
||||
"id": "timestamp",
|
||||
"initialWidth": 210,
|
||||
"initialWidth": 212,
|
||||
"isSortable": true,
|
||||
"schema": "datetime",
|
||||
"visibleCellActions": undefined,
|
||||
|
@ -406,7 +406,7 @@ describe('Data table columns', function () {
|
|||
</div>,
|
||||
"displayAsText": "timestamp",
|
||||
"id": "timestamp",
|
||||
"initialWidth": 210,
|
||||
"initialWidth": 212,
|
||||
"isSortable": true,
|
||||
"schema": "datetime",
|
||||
"visibleCellActions": undefined,
|
||||
|
|
|
@ -30,7 +30,7 @@ import { buildEditFieldButton } from './build_edit_field_button';
|
|||
|
||||
const openDetails = {
|
||||
id: 'openDetails',
|
||||
width: 24,
|
||||
width: 26,
|
||||
headerCellRender: () => (
|
||||
<EuiScreenReaderOnly>
|
||||
<span>
|
||||
|
|
|
@ -15,14 +15,19 @@ import {
|
|||
EuiCopy,
|
||||
EuiDataGridCellValueElementProps,
|
||||
EuiPopover,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { euiDarkVars as themeDark, euiLightVars as themeLight } from '@kbn/ui-theme';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||
import { UnifiedDataTableContext } from '../table_context';
|
||||
|
||||
export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { selectedDocs, expanded, rows, isDarkMode, setSelectedDocs } =
|
||||
useContext(UnifiedDataTableContext);
|
||||
const doc = useMemo(() => rows[rowIndex], [rows, rowIndex]);
|
||||
|
@ -46,20 +51,33 @@ export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle
|
|||
}, [expanded, doc, setCellProps, isDarkMode]);
|
||||
|
||||
return (
|
||||
<EuiCheckbox
|
||||
id={doc.id}
|
||||
aria-label={toggleDocumentSelectionLabel}
|
||||
checked={checked}
|
||||
data-test-subj={`dscGridSelectDoc-${doc.id}`}
|
||||
onChange={() => {
|
||||
if (checked) {
|
||||
const newSelection = selectedDocs.filter((docId) => docId !== doc.id);
|
||||
setSelectedDocs(newSelection);
|
||||
} else {
|
||||
setSelectedDocs([...selectedDocs, doc.id]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
className="unifiedDataTable__rowControl"
|
||||
css={css`
|
||||
padding-block: ${euiTheme.size.xs}; // to have the same height as "openDetails" control
|
||||
padding-left: ${euiTheme.size.xs}; // space between controls
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCheckbox
|
||||
id={doc.id}
|
||||
aria-label={toggleDocumentSelectionLabel}
|
||||
checked={checked}
|
||||
data-test-subj={`dscGridSelectDoc-${doc.id}`}
|
||||
onChange={() => {
|
||||
if (checked) {
|
||||
const newSelection = selectedDocs.filter((docId) => docId !== doc.id);
|
||||
setSelectedDocs(newSelection);
|
||||
} else {
|
||||
setSelectedDocs([...selectedDocs, doc.id]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -62,23 +62,25 @@ export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle
|
|||
}
|
||||
|
||||
return (
|
||||
<EuiToolTip content={buttonLabel} delay="long" ref={toolTipRef}>
|
||||
<EuiButtonIcon
|
||||
id={rowIndex === 0 ? tourStep : undefined}
|
||||
size="xs"
|
||||
iconSize="s"
|
||||
aria-label={buttonLabel}
|
||||
data-test-subj={testSubj}
|
||||
onClick={() => {
|
||||
const nextHit = isCurrentRowExpanded ? undefined : current;
|
||||
toolTipRef.current?.hideToolTip();
|
||||
setPressed(Boolean(nextHit));
|
||||
setExpanded?.(nextHit);
|
||||
}}
|
||||
color={isCurrentRowExpanded ? 'primary' : 'text'}
|
||||
iconType={isCurrentRowExpanded ? 'minimize' : 'expand'}
|
||||
isSelected={isCurrentRowExpanded}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
<div className="unifiedDataTable__rowControl">
|
||||
<EuiToolTip content={buttonLabel} delay="long" ref={toolTipRef}>
|
||||
<EuiButtonIcon
|
||||
id={rowIndex === 0 ? tourStep : undefined}
|
||||
size="xs"
|
||||
iconSize="s"
|
||||
aria-label={buttonLabel}
|
||||
data-test-subj={testSubj}
|
||||
onClick={() => {
|
||||
const nextHit = isCurrentRowExpanded ? undefined : current;
|
||||
toolTipRef.current?.hideToolTip();
|
||||
setPressed(Boolean(nextHit));
|
||||
setExpanded?.(nextHit);
|
||||
}}
|
||||
color={isCurrentRowExpanded ? 'primary' : 'text'}
|
||||
iconType={isCurrentRowExpanded ? 'minimize' : 'expand'}
|
||||
isSelected={isCurrentRowExpanded}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,15 +13,17 @@ export const MAX_LOADED_GRID_ROWS = 10000;
|
|||
export const ROWS_PER_PAGE_OPTIONS = [10, 25, 50, DEFAULT_ROWS_PER_PAGE, 250, 500];
|
||||
|
||||
export const defaultMonacoEditorWidth = 370;
|
||||
export const defaultTimeColumnWidth = 210;
|
||||
export const defaultTimeColumnWidth = 212;
|
||||
export const kibanaJSON = 'kibana-json';
|
||||
|
||||
export const GRID_STYLE = {
|
||||
border: 'all',
|
||||
export const GRID_STYLE: EuiDataGridStyle = {
|
||||
border: 'horizontal',
|
||||
fontSize: 's',
|
||||
cellPadding: 's',
|
||||
rowHover: 'none',
|
||||
} as EuiDataGridStyle;
|
||||
cellPadding: 'l',
|
||||
rowHover: 'highlight',
|
||||
header: 'underline',
|
||||
stripes: true,
|
||||
};
|
||||
|
||||
export const toolbarVisibility = {
|
||||
showColumnSelector: {
|
||||
|
|
|
@ -212,7 +212,9 @@ describe('Unified data table cell rendering', function () {
|
|||
compressed={true}
|
||||
type="inline"
|
||||
>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
extension
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -223,7 +225,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
bytesDisplayName
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -234,7 +238,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
_index
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -245,7 +251,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
_score
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -363,7 +371,9 @@ describe('Unified data table cell rendering', function () {
|
|||
compressed={true}
|
||||
type="inline"
|
||||
>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
extension
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -376,7 +386,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
bytesDisplayName
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -389,7 +401,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
_index
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -400,7 +414,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
_score
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -443,7 +459,9 @@ describe('Unified data table cell rendering', function () {
|
|||
compressed={true}
|
||||
type="inline"
|
||||
>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
extension
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -456,7 +474,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
bytesDisplayName
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -469,7 +489,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
_index
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -480,7 +502,9 @@ describe('Unified data table cell rendering', function () {
|
|||
}
|
||||
}
|
||||
/>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
_score
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -603,7 +627,9 @@ describe('Unified data table cell rendering', function () {
|
|||
compressed={true}
|
||||
type="inline"
|
||||
>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
object.value
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
@ -646,7 +672,9 @@ describe('Unified data table cell rendering', function () {
|
|||
compressed={true}
|
||||
type="inline"
|
||||
>
|
||||
<EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle
|
||||
className="unifiedDataTable__descriptionListTitle"
|
||||
>
|
||||
object.value
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
|
|
|
@ -137,7 +137,9 @@ export const getRenderCellValueFn =
|
|||
>
|
||||
{pairs.map(([key, value]) => (
|
||||
<Fragment key={key}>
|
||||
<EuiDescriptionListTitle>{key}</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListTitle className="unifiedDataTable__descriptionListTitle">
|
||||
{key}
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription
|
||||
className="unifiedDataTable__descriptionListDescription"
|
||||
dangerouslySetInnerHTML={{ __html: value }}
|
||||
|
|
|
@ -191,7 +191,7 @@ exports[`UnifiedFieldList <FieldItemButton /> renders properly with a drag handl
|
|||
"aria-label": "Preview bytes: number",
|
||||
}
|
||||
}
|
||||
className="unifiedFieldListItemButton unifiedFieldListItemButton--number unifiedFieldListItemButton--exists custom"
|
||||
className="unifiedFieldListItemButton unifiedFieldListItemButton--number unifiedFieldListItemButton--exists unifiedFieldListItemButton--withDragHandle custom"
|
||||
dataTestSubj="test-subj"
|
||||
dragHandle={
|
||||
<span>
|
||||
|
|
|
@ -105,6 +105,7 @@ export function FieldItemButton<T extends FieldListItem = DataViewField>({
|
|||
[`unifiedFieldListItemButton--${type}`]: type,
|
||||
[`unifiedFieldListItemButton--exists`]: !isEmpty,
|
||||
[`unifiedFieldListItemButton--missing`]: isEmpty,
|
||||
[`unifiedFieldListItemButton--withDragHandle`]: Boolean(otherProps.dragHandle),
|
||||
},
|
||||
className
|
||||
);
|
||||
|
|
|
@ -23,6 +23,7 @@ export interface FieldListFiltersProps<T extends FieldListItem> {
|
|||
getCustomFieldType?: FieldTypeFilterProps<T>['getCustomFieldType'];
|
||||
onSupportedFieldFilter?: FieldTypeFilterProps<T>['onSupportedFieldFilter'];
|
||||
onChangeFieldTypes: FieldTypeFilterProps<T>['onChange'];
|
||||
compressed?: FieldNameSearchProps['compressed'];
|
||||
nameFilter: FieldNameSearchProps['nameFilter'];
|
||||
screenReaderDescriptionId?: FieldNameSearchProps['screenReaderDescriptionId'];
|
||||
onChangeNameFilter: FieldNameSearchProps['onChange'];
|
||||
|
@ -38,6 +39,7 @@ export interface FieldListFiltersProps<T extends FieldListItem> {
|
|||
* @param getCustomFieldType
|
||||
* @param onSupportedFieldFilter
|
||||
* @param onChangeFieldTypes
|
||||
* @param compressed
|
||||
* @param nameFilter
|
||||
* @param screenReaderDescriptionId
|
||||
* @param onChangeNameFilter
|
||||
|
@ -52,6 +54,7 @@ function InnerFieldListFilters<T extends FieldListItem = DataViewField>({
|
|||
getCustomFieldType,
|
||||
onSupportedFieldFilter,
|
||||
onChangeFieldTypes,
|
||||
compressed,
|
||||
nameFilter,
|
||||
screenReaderDescriptionId,
|
||||
onChangeNameFilter,
|
||||
|
@ -72,6 +75,7 @@ function InnerFieldListFilters<T extends FieldListItem = DataViewField>({
|
|||
/>
|
||||
) : undefined
|
||||
}
|
||||
compressed={compressed}
|
||||
nameFilter={nameFilter}
|
||||
screenReaderDescriptionId={screenReaderDescriptionId}
|
||||
onChange={onChangeNameFilter}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { EuiFieldSearch, type EuiFieldSearchProps } from '@elastic/eui';
|
|||
export interface FieldNameSearchProps {
|
||||
'data-test-subj': string;
|
||||
append?: EuiFieldSearchProps['append'];
|
||||
compressed?: EuiFieldSearchProps['compressed'];
|
||||
nameFilter: string;
|
||||
screenReaderDescriptionId?: string;
|
||||
onChange: (nameFilter: string) => unknown;
|
||||
|
@ -25,6 +26,7 @@ export interface FieldNameSearchProps {
|
|||
* Search input for fields list
|
||||
* @param dataTestSubject
|
||||
* @param append
|
||||
* @param compressed
|
||||
* @param nameFilter
|
||||
* @param screenReaderDescriptionId
|
||||
* @param onChange
|
||||
|
@ -33,6 +35,7 @@ export interface FieldNameSearchProps {
|
|||
export const FieldNameSearch: React.FC<FieldNameSearchProps> = ({
|
||||
'data-test-subj': dataTestSubject,
|
||||
append,
|
||||
compressed,
|
||||
nameFilter,
|
||||
screenReaderDescriptionId,
|
||||
onChange,
|
||||
|
@ -52,6 +55,7 @@ export const FieldNameSearch: React.FC<FieldNameSearchProps> = ({
|
|||
placeholder={searchPlaceholder}
|
||||
value={nameFilter}
|
||||
append={append}
|
||||
compressed={compressed}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -82,6 +82,7 @@ async function getComponent({
|
|||
isEmpty: false,
|
||||
groupIndex: 1,
|
||||
itemIndex: 0,
|
||||
size: 'xs',
|
||||
workspaceSelectedFieldNames: [],
|
||||
};
|
||||
const comp = await mountWithIntl(<UnifiedFieldListItem {...props} />);
|
||||
|
|
|
@ -33,6 +33,7 @@ import type {
|
|||
interface GetCommonFieldItemButtonPropsParams {
|
||||
stateService: UnifiedFieldListSidebarContainerStateService;
|
||||
field: DataViewField;
|
||||
size: FieldItemButtonProps<DataViewField>['size'];
|
||||
isSelected: boolean;
|
||||
toggleDisplay: (field: DataViewField, isSelected?: boolean) => void;
|
||||
}
|
||||
|
@ -40,10 +41,12 @@ interface GetCommonFieldItemButtonPropsParams {
|
|||
function getCommonFieldItemButtonProps({
|
||||
stateService,
|
||||
field,
|
||||
size,
|
||||
isSelected,
|
||||
toggleDisplay,
|
||||
}: GetCommonFieldItemButtonPropsParams): {
|
||||
field: FieldItemButtonProps<DataViewField>['field'];
|
||||
size: FieldItemButtonProps<DataViewField>['size'];
|
||||
isSelected: FieldItemButtonProps<DataViewField>['isSelected'];
|
||||
buttonAddFieldToWorkspaceProps?: FieldItemButtonProps<DataViewField>['buttonAddFieldToWorkspaceProps'];
|
||||
buttonRemoveFieldFromWorkspaceProps?: FieldItemButtonProps<DataViewField>['buttonRemoveFieldFromWorkspaceProps'];
|
||||
|
@ -54,6 +57,7 @@ function getCommonFieldItemButtonProps({
|
|||
field.name === '_source' ? undefined : (f: DataViewField) => toggleDisplay(f, isSelected);
|
||||
return {
|
||||
field,
|
||||
size,
|
||||
isSelected,
|
||||
buttonAddFieldToWorkspaceProps: stateService.creationOptions.buttonAddFieldToWorkspaceProps,
|
||||
buttonRemoveFieldFromWorkspaceProps:
|
||||
|
@ -68,10 +72,11 @@ interface MultiFieldsProps {
|
|||
multiFields: NonNullable<UnifiedFieldListItemProps['multiFields']>;
|
||||
toggleDisplay: (field: DataViewField) => void;
|
||||
alwaysShowActionButton: boolean;
|
||||
size: FieldItemButtonProps<DataViewField>['size'];
|
||||
}
|
||||
|
||||
const MultiFields: React.FC<MultiFieldsProps> = memo(
|
||||
({ stateService, multiFields, toggleDisplay, alwaysShowActionButton }) => (
|
||||
({ stateService, multiFields, toggleDisplay, alwaysShowActionButton, size }) => (
|
||||
<React.Fragment>
|
||||
<EuiTitle size="xxxs">
|
||||
<h5>
|
||||
|
@ -84,7 +89,6 @@ const MultiFields: React.FC<MultiFieldsProps> = memo(
|
|||
{multiFields.map((entry) => (
|
||||
<FieldItemButton
|
||||
key={entry.field.name}
|
||||
size="xs"
|
||||
flush="both"
|
||||
isEmpty={false}
|
||||
isActive={false}
|
||||
|
@ -95,6 +99,7 @@ const MultiFields: React.FC<MultiFieldsProps> = memo(
|
|||
field: entry.field,
|
||||
isSelected: entry.isSelected,
|
||||
toggleDisplay,
|
||||
size,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
|
@ -187,6 +192,10 @@ export interface UnifiedFieldListItemProps {
|
|||
* Item index in the field list
|
||||
*/
|
||||
itemIndex: number;
|
||||
/**
|
||||
* Item size
|
||||
*/
|
||||
size: FieldItemButtonProps<DataViewField>['size'];
|
||||
}
|
||||
|
||||
function UnifiedFieldListItemComponent({
|
||||
|
@ -209,6 +218,7 @@ function UnifiedFieldListItemComponent({
|
|||
workspaceSelectedFieldNames,
|
||||
groupIndex,
|
||||
itemIndex,
|
||||
size,
|
||||
}: UnifiedFieldListItemProps) {
|
||||
const [infoIsOpen, setOpen] = useState(false);
|
||||
|
||||
|
@ -284,6 +294,7 @@ function UnifiedFieldListItemComponent({
|
|||
multiFields={multiFields}
|
||||
alwaysShowActionButton={alwaysShowActionButton}
|
||||
toggleDisplay={toggleDisplay}
|
||||
size={size}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -315,6 +326,8 @@ function UnifiedFieldListItemComponent({
|
|||
[field, itemIndex]
|
||||
);
|
||||
const order = useMemo(() => [0, groupIndex, itemIndex], [groupIndex, itemIndex]);
|
||||
const isDragDisabled =
|
||||
alwaysShowActionButton || stateService.creationOptions.disableFieldListItemDragAndDrop;
|
||||
|
||||
return (
|
||||
<FieldPopover
|
||||
|
@ -322,26 +335,30 @@ function UnifiedFieldListItemComponent({
|
|||
button={
|
||||
<DragDrop
|
||||
draggable
|
||||
dragClassName="unifiedFieldListItemButton__dragging"
|
||||
order={order}
|
||||
value={value}
|
||||
onDragStart={closePopover}
|
||||
isDisabled={
|
||||
alwaysShowActionButton || stateService.creationOptions.disableFieldListItemDragAndDrop
|
||||
}
|
||||
isDisabled={isDragDisabled}
|
||||
dataTestSubj={`${
|
||||
stateService.creationOptions.dataTestSubj?.fieldListItemDndDataTestSubjPrefix ??
|
||||
'unifiedFieldListItemDnD'
|
||||
}-${field.name}`}
|
||||
>
|
||||
<FieldItemButton
|
||||
size="xs"
|
||||
fieldSearchHighlight={highlight}
|
||||
isEmpty={isEmpty}
|
||||
isActive={infoIsOpen}
|
||||
flush={alwaysShowActionButton ? 'both' : undefined}
|
||||
shouldAlwaysShowAction={alwaysShowActionButton}
|
||||
onClick={field.type !== '_source' ? togglePopover : undefined}
|
||||
{...getCommonFieldItemButtonProps({ stateService, field, isSelected, toggleDisplay })}
|
||||
{...getCommonFieldItemButtonProps({
|
||||
stateService,
|
||||
field,
|
||||
isSelected,
|
||||
toggleDisplay,
|
||||
size,
|
||||
})}
|
||||
/>
|
||||
</DragDrop>
|
||||
}
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
width: $euiSize * 19;
|
||||
height: 100%;
|
||||
|
||||
&--collapsed {
|
||||
width: auto;
|
||||
padding: $euiSizeS $euiSizeS 0;
|
||||
}
|
||||
|
||||
@include euiBreakpoint('xs', 's') {
|
||||
width: 100%;
|
||||
padding: $euiSize;
|
||||
|
@ -14,7 +19,7 @@
|
|||
}
|
||||
|
||||
.unifiedFieldListSidebar__list {
|
||||
padding: $euiSizeS 0 $euiSizeS $euiSizeS;
|
||||
padding: $euiSizeS $euiSizeS 0;
|
||||
|
||||
@include euiBreakpoint('xs', 's') {
|
||||
padding: $euiSizeS 0 0 0;
|
||||
|
@ -38,3 +43,18 @@
|
|||
.unifiedFieldListSidebar__flyoutHeader {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.unifiedFieldListSidebar .unifiedFieldListItemButton {
|
||||
&.kbnFieldButton {
|
||||
margin-bottom: $euiSizeXS / 2;
|
||||
}
|
||||
|
||||
&.domDragDrop-isDraggable {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:not(.unifiedFieldListItemButton__dragging) {
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,16 +9,29 @@
|
|||
import './field_list_sidebar.scss';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPageSidebar } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonProps,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHideFor,
|
||||
EuiPageSidebar,
|
||||
EuiPageSidebarProps,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { ToolbarButton } from '@kbn/shared-ux-button-toolbar';
|
||||
import { type DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { getDataViewFieldSubtypeMulti } from '@kbn/es-query/src/utils';
|
||||
import { FIELDS_LIMIT_SETTING, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils';
|
||||
import { FieldList } from '../../components/field_list';
|
||||
import { FieldListFilters } from '../../components/field_list_filters';
|
||||
import { FieldListGrouped, type FieldListGroupedProps } from '../../components/field_list_grouped';
|
||||
import { FieldsGroupNames } from '../../types';
|
||||
import { FieldsGroupNames, type ButtonAddFieldVariant } from '../../types';
|
||||
import { GroupedFieldsParams, useGroupedFields } from '../../hooks/use_grouped_fields';
|
||||
import { UnifiedFieldListItem, type UnifiedFieldListItemProps } from '../unified_field_list_item';
|
||||
import { SidebarToggleButton, type SidebarToggleButtonProps } from './sidebar_toggle_button';
|
||||
import {
|
||||
getSelectedFields,
|
||||
shouldShowField,
|
||||
|
@ -46,6 +59,11 @@ export type UnifiedFieldListSidebarCustomizableProps = Pick<
|
|||
*/
|
||||
showFieldList?: boolean;
|
||||
|
||||
/**
|
||||
* Compressed view
|
||||
*/
|
||||
compressed?: boolean;
|
||||
|
||||
/**
|
||||
* Custom logic for determining which field is selected
|
||||
*/
|
||||
|
@ -83,6 +101,22 @@ interface UnifiedFieldListSidebarInternalProps {
|
|||
*/
|
||||
alwaysShowActionButton?: UnifiedFieldListItemProps['alwaysShowActionButton'];
|
||||
|
||||
/**
|
||||
* What button style type to use
|
||||
*/
|
||||
buttonAddFieldVariant: ButtonAddFieldVariant;
|
||||
|
||||
/**
|
||||
* In case if sidebar is collapsible by default
|
||||
* Pass `undefined` to hide the collapse/expand buttons from the sidebar
|
||||
*/
|
||||
isSidebarCollapsed?: boolean;
|
||||
|
||||
/**
|
||||
* A handler to toggle the sidebar
|
||||
*/
|
||||
onToggleSidebar?: SidebarToggleButtonProps['onChange'];
|
||||
|
||||
/**
|
||||
* Trigger a field editing
|
||||
*/
|
||||
|
@ -104,10 +138,13 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
|
|||
workspaceSelectedFieldNames,
|
||||
isProcessing,
|
||||
alwaysShowActionButton,
|
||||
buttonAddFieldVariant,
|
||||
isSidebarCollapsed,
|
||||
allFields,
|
||||
dataView,
|
||||
trackUiMetric,
|
||||
showFieldList = true,
|
||||
compressed = true,
|
||||
isAffectedByGlobalFilter,
|
||||
prepend,
|
||||
onAddFieldToWorkspace,
|
||||
|
@ -116,6 +153,7 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
|
|||
onSelectedFieldFilter,
|
||||
onEditField,
|
||||
onDeleteField,
|
||||
onToggleSidebar,
|
||||
}) => {
|
||||
const { dataViews, core } = services;
|
||||
const useNewFieldsApi = useMemo(
|
||||
|
@ -210,6 +248,7 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
|
|||
services={services}
|
||||
alwaysShowActionButton={alwaysShowActionButton}
|
||||
field={field}
|
||||
size={compressed ? 'xs' : 's'}
|
||||
highlight={fieldSearchHighlight}
|
||||
dataView={dataView!}
|
||||
onAddFieldToWorkspace={onAddFieldToWorkspace}
|
||||
|
@ -235,6 +274,7 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
|
|||
searchMode,
|
||||
services,
|
||||
alwaysShowActionButton,
|
||||
compressed,
|
||||
dataView,
|
||||
onAddFieldToWorkspace,
|
||||
onRemoveFieldFromWorkspace,
|
||||
|
@ -248,40 +288,94 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
|
|||
]
|
||||
);
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
if (!dataView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sidebarToggleButton =
|
||||
typeof isSidebarCollapsed === 'boolean' && onToggleSidebar ? (
|
||||
<SidebarToggleButton
|
||||
buttonSize={compressed ? 's' : 'm'}
|
||||
isSidebarCollapsed={isSidebarCollapsed}
|
||||
onChange={onToggleSidebar}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
const pageSidebarProps: Partial<EuiPageSidebarProps> = {
|
||||
className: classnames('unifiedFieldListSidebar', {
|
||||
'unifiedFieldListSidebar--collapsed': isSidebarCollapsed,
|
||||
}),
|
||||
'aria-label': i18n.translate(
|
||||
'unifiedFieldList.fieldListSidebar.indexAndFieldsSectionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Index and fields',
|
||||
}
|
||||
),
|
||||
id:
|
||||
stateService.creationOptions.dataTestSubj?.fieldListSidebarDataTestSubj ??
|
||||
'unifiedFieldListSidebarId',
|
||||
'data-test-subj':
|
||||
stateService.creationOptions.dataTestSubj?.fieldListSidebarDataTestSubj ??
|
||||
'unifiedFieldListSidebarId',
|
||||
};
|
||||
|
||||
if (isSidebarCollapsed && sidebarToggleButton) {
|
||||
return (
|
||||
<EuiHideFor sizes={['xs', 's']}>
|
||||
<div {...pageSidebarProps}>{sidebarToggleButton}</div>
|
||||
</EuiHideFor>
|
||||
);
|
||||
}
|
||||
|
||||
const hasButtonAddFieldToolbarStyle = buttonAddFieldVariant === 'toolbar';
|
||||
const buttonAddFieldCommonProps: Partial<EuiButtonProps> = {
|
||||
size: 's',
|
||||
iconType: 'indexOpen',
|
||||
'data-test-subj':
|
||||
stateService.creationOptions.dataTestSubj?.fieldListAddFieldButtonTestSubj ??
|
||||
'unifiedFieldListAddField',
|
||||
};
|
||||
const buttonAddFieldLabel = i18n.translate(
|
||||
'unifiedFieldList.fieldListSidebar.addFieldButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Add a field',
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPageSidebar
|
||||
className="unifiedFieldListSidebar"
|
||||
aria-label={i18n.translate(
|
||||
'unifiedFieldList.fieldListSidebar.indexAndFieldsSectionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Index and fields',
|
||||
}
|
||||
)}
|
||||
id={
|
||||
stateService.creationOptions.dataTestSubj?.fieldListSidebarDataTestSubj ??
|
||||
'unifiedFieldListSidebarId'
|
||||
}
|
||||
data-test-subj={
|
||||
stateService.creationOptions.dataTestSubj?.fieldListSidebarDataTestSubj ??
|
||||
'unifiedFieldListSidebarId'
|
||||
}
|
||||
>
|
||||
<EuiPageSidebar {...pageSidebarProps}>
|
||||
<EuiFlexGroup
|
||||
className="unifiedFieldListSidebar__group"
|
||||
direction="column"
|
||||
alignItems="stretch"
|
||||
gutterSize="s"
|
||||
gutterSize="none"
|
||||
responsive={false}
|
||||
>
|
||||
{Boolean(prepend) && <EuiFlexItem grow={false}>{prepend}</EuiFlexItem>}
|
||||
{Boolean(prepend) && (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
margin-bottom: ${euiTheme.size.s};
|
||||
`}
|
||||
>
|
||||
{prepend}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<FieldList
|
||||
isProcessing={isProcessing}
|
||||
prepend={<FieldListFilters {...fieldListFiltersProps} />}
|
||||
prepend={
|
||||
<EuiFlexGroup direction="row" gutterSize="s" responsive={false}>
|
||||
{sidebarToggleButton && (
|
||||
<EuiFlexItem grow={false}>{sidebarToggleButton}</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<FieldListFilters {...fieldListFiltersProps} compressed={compressed} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
className="unifiedFieldListSidebar__list"
|
||||
>
|
||||
{showFieldList ? (
|
||||
|
@ -293,25 +387,33 @@ export const UnifiedFieldListSidebarComponent: React.FC<UnifiedFieldListSidebarP
|
|||
) : (
|
||||
<EuiFlexItem grow />
|
||||
)}
|
||||
{!!onEditField && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
iconType="indexOpen"
|
||||
data-test-subj={
|
||||
stateService.creationOptions.dataTestSubj?.fieldListAddFieldButtonTestSubj ??
|
||||
'unifiedFieldListAddField'
|
||||
}
|
||||
onClick={() => onEditField()}
|
||||
size="s"
|
||||
>
|
||||
{i18n.translate('unifiedFieldList.fieldListSidebar.addFieldButtonLabel', {
|
||||
defaultMessage: 'Add a field',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</FieldList>
|
||||
</EuiFlexItem>
|
||||
{!!onEditField && (
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={
|
||||
hasButtonAddFieldToolbarStyle
|
||||
? css`
|
||||
padding: ${euiTheme.size.s};
|
||||
border-top: ${euiTheme.border.thin};
|
||||
`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{hasButtonAddFieldToolbarStyle ? (
|
||||
<ToolbarButton
|
||||
{...buttonAddFieldCommonProps}
|
||||
label={buttonAddFieldLabel}
|
||||
onClick={() => onEditField()}
|
||||
/>
|
||||
) : (
|
||||
<EuiButton {...buttonAddFieldCommonProps} onClick={() => onEditField()}>
|
||||
{buttonAddFieldLabel}
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPageSidebar>
|
||||
);
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
type ExistingFieldsFetcher,
|
||||
} from '../../hooks/use_existing_fields';
|
||||
import { useQuerySubscriber } from '../../hooks/use_query_subscriber';
|
||||
import { useSidebarToggle } from '../../hooks/use_sidebar_toggle';
|
||||
import {
|
||||
UnifiedFieldListSidebar,
|
||||
type UnifiedFieldListSidebarCustomizableProps,
|
||||
|
@ -72,11 +73,6 @@ export type UnifiedFieldListSidebarContainerProps = Omit<
|
|||
*/
|
||||
getCreationOptions: () => UnifiedFieldListSidebarContainerCreationOptions;
|
||||
|
||||
/**
|
||||
* In case if you have a sidebar toggle button
|
||||
*/
|
||||
isSidebarCollapsed?: boolean;
|
||||
|
||||
/**
|
||||
* Custom content to render at the top of field list in the flyout (for example a data view picker)
|
||||
*/
|
||||
|
@ -115,7 +111,6 @@ const UnifiedFieldListSidebarContainer = forwardRef<
|
|||
services,
|
||||
dataView,
|
||||
workspaceSelectedFieldNames,
|
||||
isSidebarCollapsed, // TODO later: pull the logic of collapsing the sidebar to this component
|
||||
prependInFlyout,
|
||||
variant = 'responsive',
|
||||
onFieldEdited,
|
||||
|
@ -125,6 +120,7 @@ const UnifiedFieldListSidebarContainer = forwardRef<
|
|||
);
|
||||
const { data, dataViewFieldEditor } = services;
|
||||
const [isFieldListFlyoutVisible, setIsFieldListFlyoutVisible] = useState<boolean>(false);
|
||||
const { isSidebarCollapsed, onToggleSidebar } = useSidebarToggle({ stateService });
|
||||
|
||||
const canEditDataView =
|
||||
Boolean(dataViewFieldEditor?.userPermissions.editIndexPattern()) ||
|
||||
|
@ -250,8 +246,15 @@ const UnifiedFieldListSidebarContainer = forwardRef<
|
|||
isAffectedByGlobalFilter,
|
||||
onEditField: editField,
|
||||
onDeleteField: deleteField,
|
||||
compressed: stateService.creationOptions.compressed ?? false,
|
||||
buttonAddFieldVariant: stateService.creationOptions.buttonAddFieldVariant ?? 'primary',
|
||||
};
|
||||
|
||||
if (stateService.creationOptions.showSidebarToggleButton) {
|
||||
commonSidebarProps.isSidebarCollapsed = isSidebarCollapsed;
|
||||
commonSidebarProps.onToggleSidebar = onToggleSidebar;
|
||||
}
|
||||
|
||||
const buttonPropsToTriggerFlyout = stateService.creationOptions.buttonPropsToTriggerFlyout;
|
||||
|
||||
const renderListVariant = () => {
|
||||
|
@ -319,6 +322,8 @@ const UnifiedFieldListSidebarContainer = forwardRef<
|
|||
<UnifiedFieldListSidebar
|
||||
{...commonSidebarProps}
|
||||
alwaysShowActionButton={true}
|
||||
buttonAddFieldVariant="primary" // always for the flyout
|
||||
isSidebarCollapsed={undefined}
|
||||
prepend={prependInFlyout?.()}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
|
@ -333,12 +338,12 @@ const UnifiedFieldListSidebarContainer = forwardRef<
|
|||
}
|
||||
|
||||
if (variant === 'list-always') {
|
||||
return (!isSidebarCollapsed && renderListVariant()) || null;
|
||||
return renderListVariant();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isSidebarCollapsed && <EuiHideFor sizes={['xs', 's']}>{renderListVariant()}</EuiHideFor>}
|
||||
<EuiHideFor sizes={['xs', 's']}>{renderListVariant()}</EuiHideFor>
|
||||
<EuiShowFor sizes={['xs', 's']}>{renderButtonVariant()}</EuiShowFor>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { SidebarToggleButton, type SidebarToggleButtonProps } from './sidebar_toggle_button';
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IconButtonGroup, type IconButtonGroupProps } from '@kbn/shared-ux-button-toolbar';
|
||||
|
||||
/**
|
||||
* Toggle button props
|
||||
*/
|
||||
export interface SidebarToggleButtonProps {
|
||||
'data-test-subj'?: string;
|
||||
isSidebarCollapsed: boolean;
|
||||
buttonSize: IconButtonGroupProps['buttonSize'];
|
||||
onChange: (isSidebarCollapsed: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A toggle button for the fields sidebar
|
||||
* @param data-test-subj
|
||||
* @param isSidebarCollapsed
|
||||
* @param onChange
|
||||
* @constructor
|
||||
*/
|
||||
export const SidebarToggleButton: React.FC<SidebarToggleButtonProps> = ({
|
||||
'data-test-subj': dataTestSubj = 'unifiedFieldListSidebar__toggle',
|
||||
isSidebarCollapsed,
|
||||
buttonSize,
|
||||
onChange,
|
||||
}) => {
|
||||
// TODO: replace with new Eui icons once available
|
||||
return (
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<IconButtonGroup
|
||||
legend={i18n.translate('unifiedFieldList.fieldListSidebar.toggleSidebarLegend', {
|
||||
defaultMessage: 'Toggle sidebar',
|
||||
})}
|
||||
buttonSize={buttonSize}
|
||||
buttons={[
|
||||
...(isSidebarCollapsed
|
||||
? [
|
||||
{
|
||||
label: i18n.translate('unifiedFieldList.fieldListSidebar.expandSidebarButton', {
|
||||
defaultMessage: 'Show sidebar',
|
||||
}),
|
||||
iconType: 'menuRight',
|
||||
'data-test-subj': `${dataTestSubj}-expand`,
|
||||
onClick: () => onChange(false),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: i18n.translate('unifiedFieldList.fieldListSidebar.collapseSidebarButton', {
|
||||
defaultMessage: 'Hide sidebar',
|
||||
}),
|
||||
iconType: 'menuLeft',
|
||||
'data-test-subj': `${dataTestSubj}-collapse`,
|
||||
onClick: () => onChange(true),
|
||||
},
|
||||
]),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { useSidebarToggle } from './use_sidebar_toggle';
|
||||
import * as localStorageModule from 'react-use/lib/useLocalStorage';
|
||||
|
||||
jest.spyOn(localStorageModule, 'default');
|
||||
|
||||
describe('UnifiedFieldList useSidebarToggle', () => {
|
||||
const stateService = {
|
||||
creationOptions: {
|
||||
originatingApp: 'test',
|
||||
localStorageKeyPrefix: 'this',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(localStorageModule.default as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it('should toggle correctly', async () => {
|
||||
const storeMock = jest.fn();
|
||||
(localStorageModule.default as jest.Mock).mockImplementation(() => {
|
||||
return [false, storeMock];
|
||||
});
|
||||
|
||||
const { result } = renderHook(useSidebarToggle, {
|
||||
initialProps: {
|
||||
stateService,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.current.isSidebarCollapsed).toBe(false);
|
||||
|
||||
act(() => {
|
||||
result.current.onToggleSidebar(true);
|
||||
});
|
||||
|
||||
expect(result.current.isSidebarCollapsed).toBe(true);
|
||||
expect(storeMock).toHaveBeenCalledWith(true);
|
||||
|
||||
act(() => {
|
||||
result.current.onToggleSidebar(false);
|
||||
});
|
||||
|
||||
expect(result.current.isSidebarCollapsed).toBe(false);
|
||||
expect(storeMock).toHaveBeenLastCalledWith(false);
|
||||
});
|
||||
|
||||
it('should restore collapsed state and expand from it', async () => {
|
||||
const storeMock = jest.fn();
|
||||
(localStorageModule.default as jest.Mock).mockImplementation(() => {
|
||||
return [true, storeMock];
|
||||
});
|
||||
|
||||
const { result } = renderHook(useSidebarToggle, {
|
||||
initialProps: {
|
||||
stateService,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.current.isSidebarCollapsed).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.onToggleSidebar(false);
|
||||
});
|
||||
|
||||
expect(result.current.isSidebarCollapsed).toBe(false);
|
||||
expect(storeMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('should not persist if local storage key is not defined', async () => {
|
||||
const storeMock = jest.fn();
|
||||
(localStorageModule.default as jest.Mock).mockImplementation(() => {
|
||||
return [false, storeMock];
|
||||
});
|
||||
|
||||
const { result } = renderHook(useSidebarToggle, {
|
||||
initialProps: {
|
||||
stateService: {
|
||||
creationOptions: {
|
||||
originatingApp: 'test',
|
||||
localStorageKeyPrefix: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.current.isSidebarCollapsed).toBe(false);
|
||||
|
||||
act(() => {
|
||||
result.current.onToggleSidebar(true);
|
||||
});
|
||||
|
||||
expect(result.current.isSidebarCollapsed).toBe(true);
|
||||
expect(storeMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { useCallback, useState, useMemo } from 'react';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
import type { UnifiedFieldListSidebarContainerStateService } from '../types';
|
||||
|
||||
/**
|
||||
* Hook params
|
||||
*/
|
||||
export interface UseSidebarToggleParams {
|
||||
/**
|
||||
* Service for managing the state
|
||||
*/
|
||||
stateService: UnifiedFieldListSidebarContainerStateService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook result type
|
||||
*/
|
||||
export interface UseSidebarToggleResult {
|
||||
isSidebarCollapsed: boolean;
|
||||
onToggleSidebar: (isSidebarCollapsed: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing sidebar toggle state
|
||||
* @param stateService
|
||||
*/
|
||||
export const useSidebarToggle = ({
|
||||
stateService,
|
||||
}: UseSidebarToggleParams): UseSidebarToggleResult => {
|
||||
const [initialIsSidebarCollapsed, storeIsSidebarCollapsed] = useLocalStorage<boolean>(
|
||||
`${stateService.creationOptions.localStorageKeyPrefix ?? 'unifiedFieldList'}:sidebarClosed`, // as legacy `discover:sidebarClosed` key
|
||||
false
|
||||
);
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState<boolean>(
|
||||
initialIsSidebarCollapsed ?? false
|
||||
);
|
||||
|
||||
const onToggleSidebar = useCallback(
|
||||
(isCollapsed) => {
|
||||
setIsSidebarCollapsed(isCollapsed);
|
||||
if (stateService.creationOptions.localStorageKeyPrefix) {
|
||||
storeIsSidebarCollapsed(isCollapsed);
|
||||
}
|
||||
},
|
||||
[
|
||||
storeIsSidebarCollapsed,
|
||||
setIsSidebarCollapsed,
|
||||
stateService.creationOptions.localStorageKeyPrefix,
|
||||
]
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => ({ isSidebarCollapsed, onToggleSidebar }),
|
||||
[isSidebarCollapsed, onToggleSidebar]
|
||||
);
|
||||
};
|
|
@ -107,6 +107,8 @@ export type OverrideFieldGroupDetails = (
|
|||
|
||||
export type TimeRangeUpdatesType = 'search-session' | 'timefilter';
|
||||
|
||||
export type ButtonAddFieldVariant = 'primary' | 'toolbar';
|
||||
|
||||
export type SearchMode = 'documents' | 'text-based';
|
||||
|
||||
export interface UnifiedFieldListSidebarContainerCreationOptions {
|
||||
|
@ -116,7 +118,12 @@ export interface UnifiedFieldListSidebarContainerCreationOptions {
|
|||
originatingApp: string;
|
||||
|
||||
/**
|
||||
* Your app name: "discover", "lens", etc. If not provided, sections state would not be persisted.
|
||||
* Pass `true` to enable the compressed view
|
||||
*/
|
||||
compressed?: boolean;
|
||||
|
||||
/**
|
||||
* Your app name: "discover", "lens", etc. If not provided, sections and sidebar toggle states would not be persisted.
|
||||
*/
|
||||
localStorageKeyPrefix?: string;
|
||||
|
||||
|
@ -125,6 +132,16 @@ export interface UnifiedFieldListSidebarContainerCreationOptions {
|
|||
*/
|
||||
timeRangeUpdatesType?: TimeRangeUpdatesType;
|
||||
|
||||
/**
|
||||
* Choose how the bottom "Add a field" button should look like. Default `primary`.
|
||||
*/
|
||||
buttonAddFieldVariant?: ButtonAddFieldVariant;
|
||||
|
||||
/**
|
||||
* Pass `true` to make the sidebar collapsible. Additionally, define `localStorageKeyPrefix` to persist toggle state.
|
||||
*/
|
||||
showSidebarToggleButton?: boolean;
|
||||
|
||||
/**
|
||||
* Pass `true` to skip auto fetching of fields existence info
|
||||
*/
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"@kbn/shared-ux-utility",
|
||||
"@kbn/discover-utils",
|
||||
"@kbn/ebt-tools",
|
||||
"@kbn/shared-ux-button-toolbar",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import {
|
|||
SEARCH_FIELDS_FROM_SOURCE,
|
||||
SHOW_MULTIFIELDS,
|
||||
} from '@kbn/discover-utils';
|
||||
import { SIDEBAR_CLOSED_KEY } from '../../application/main/components/layout/discover_layout';
|
||||
import { LocalStorageMock } from '../local_storage_mock';
|
||||
import { DiscoverServices } from '../../build_services';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
|
@ -73,9 +72,7 @@ export const services = {
|
|||
docLinks: { links: { discover: {} } },
|
||||
theme,
|
||||
},
|
||||
storage: new LocalStorageMock({
|
||||
[SIDEBAR_CLOSED_KEY]: false,
|
||||
}) as unknown as Storage,
|
||||
storage: new LocalStorageMock({}) as unknown as Storage,
|
||||
data: {
|
||||
query: {
|
||||
timefilter: {
|
||||
|
|
|
@ -156,7 +156,7 @@ export function ActionBar({
|
|||
</EuiFlexGroup>
|
||||
{!isSuccessor && showWarning && <EuiSpacer size="s" />}
|
||||
{!isSuccessor && showWarning && <ActionBarWarning docCount={docCountAvailable} type={type} />}
|
||||
{!isSuccessor && <EuiSpacer size="s" />}
|
||||
<EuiSpacer size="s" />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,8 +17,4 @@
|
|||
&__cell--highlight {
|
||||
background-color: tintOrShade($euiColorPrimary, 90%, 70%);
|
||||
}
|
||||
|
||||
.euiDataGridRowCell.euiDataGridRowCell--firstColumn {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ import React, { Fragment, memo, useEffect, useRef, useMemo, useCallback } from '
|
|||
import './context_app.scss';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiText, EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiText, EuiPage, EuiPageBody, EuiSpacer, useEuiPaddingSize } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
|
||||
|
@ -215,6 +216,8 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
};
|
||||
};
|
||||
|
||||
const titlePadding = useEuiPaddingSize('m');
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{fetchedState.anchorStatus.value === LoadingStatus.FAILED ? (
|
||||
|
@ -235,12 +238,16 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
<EuiPage className={classNames({ dscDocsPage: !isLegacy })}>
|
||||
<EuiPageBody
|
||||
panelled
|
||||
paddingSize="s"
|
||||
paddingSize="none"
|
||||
className="dscDocsContent"
|
||||
panelProps={{ role: 'main' }}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText data-test-subj="contextDocumentSurroundingHeader">
|
||||
<EuiText
|
||||
data-test-subj="contextDocumentSurroundingHeader"
|
||||
css={css`
|
||||
padding: ${titlePadding} ${titlePadding} 0;
|
||||
`}
|
||||
>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="discover.context.contextOfTitle"
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
import React, { Fragment, useCallback, useMemo, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiHorizontalRule, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { EuiSpacer, EuiText, useEuiPaddingSize } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { SortDirection } from '@kbn/data-plugin/public';
|
||||
import type { SortOrder } from '@kbn/saved-search-plugin/public';
|
||||
|
@ -149,27 +150,28 @@ export function ContextAppContent({
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
{!!interceptedWarnings?.length && (
|
||||
<>
|
||||
<SearchResponseWarnings
|
||||
variant="callout"
|
||||
interceptedWarnings={interceptedWarnings}
|
||||
data-test-subj="dscContextInterceptedWarnings"
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
<ActionBarMemoized
|
||||
type={SurrDocType.PREDECESSORS}
|
||||
defaultStepSize={defaultStepSize}
|
||||
docCount={predecessorCount}
|
||||
docCountAvailable={predecessors.length}
|
||||
onChangeCount={onChangeCount}
|
||||
isLoading={arePredecessorsLoading}
|
||||
isDisabled={isAnchorLoading}
|
||||
/>
|
||||
{loadingFeedback()}
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<WrapperWithPadding>
|
||||
{!!interceptedWarnings?.length && (
|
||||
<>
|
||||
<SearchResponseWarnings
|
||||
variant="callout"
|
||||
interceptedWarnings={interceptedWarnings}
|
||||
data-test-subj="dscContextInterceptedWarnings"
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
<ActionBarMemoized
|
||||
type={SurrDocType.PREDECESSORS}
|
||||
defaultStepSize={defaultStepSize}
|
||||
docCount={predecessorCount}
|
||||
docCountAvailable={predecessors.length}
|
||||
onChangeCount={onChangeCount}
|
||||
isLoading={arePredecessorsLoading}
|
||||
isDisabled={isAnchorLoading}
|
||||
/>
|
||||
{loadingFeedback()}
|
||||
</WrapperWithPadding>
|
||||
{isLegacy && rows && rows.length !== 0 && (
|
||||
<DocTableContextMemoized
|
||||
columns={columns}
|
||||
|
@ -215,16 +217,31 @@ export function ContextAppContent({
|
|||
</CellActionsProvider>
|
||||
</div>
|
||||
)}
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<ActionBarMemoized
|
||||
type={SurrDocType.SUCCESSORS}
|
||||
defaultStepSize={defaultStepSize}
|
||||
docCount={successorCount}
|
||||
docCountAvailable={successors.length}
|
||||
onChangeCount={onChangeCount}
|
||||
isLoading={areSuccessorsLoading}
|
||||
isDisabled={isAnchorLoading}
|
||||
/>
|
||||
<WrapperWithPadding>
|
||||
<ActionBarMemoized
|
||||
type={SurrDocType.SUCCESSORS}
|
||||
defaultStepSize={defaultStepSize}
|
||||
docCount={successorCount}
|
||||
docCountAvailable={successors.length}
|
||||
onChangeCount={onChangeCount}
|
||||
isLoading={areSuccessorsLoading}
|
||||
isDisabled={isAnchorLoading}
|
||||
/>
|
||||
</WrapperWithPadding>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const WrapperWithPadding: React.FC = ({ children }) => {
|
||||
const padding = useEuiPaddingSize('s');
|
||||
|
||||
return (
|
||||
<div
|
||||
css={css`
|
||||
padding: 0 ${padding};
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@ export function Doc(props: DocProps) {
|
|||
values: { id: props.id },
|
||||
})}
|
||||
</h1>
|
||||
<EuiPageBody panelled paddingSize="l" panelProps={{ role: 'main' }}>
|
||||
<EuiPageBody panelled paddingSize="m" panelProps={{ role: 'main' }}>
|
||||
{reqState === ElasticRequestState.NotFoundDataView && (
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
|
|
|
@ -32,18 +32,12 @@ discover-app {
|
|||
}
|
||||
|
||||
.dscPageContent__wrapper {
|
||||
padding: $euiSizeS $euiSizeS $euiSizeS 0;
|
||||
overflow: hidden; // Ensures horizontal scroll of table
|
||||
|
||||
@include euiBreakpoint('xs', 's') {
|
||||
padding: 0 $euiSizeS $euiSizeS;
|
||||
}
|
||||
}
|
||||
|
||||
.dscPageContent {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: $euiBorderThin;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import { EuiPageSidebar } from '@elastic/eui';
|
|||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import type { Query, AggregateQuery } from '@kbn/es-query';
|
||||
import { setHeaderActionMenuMounter } from '../../../../kibana_services';
|
||||
import { DiscoverLayout, SIDEBAR_CLOSED_KEY } from './discover_layout';
|
||||
import { DiscoverLayout } from './discover_layout';
|
||||
import { dataViewMock, esHitsMock } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { savedSearchMock } from '../../../../__mocks__/saved_search';
|
||||
import {
|
||||
|
@ -31,9 +31,7 @@ import {
|
|||
import { createDiscoverServicesMock } from '../../../../__mocks__/services';
|
||||
import { FetchStatus } from '../../../types';
|
||||
import { RequestAdapter } from '@kbn/inspector-plugin/common';
|
||||
import { LocalStorageMock } from '../../../../__mocks__/local_storage_mock';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { DiscoverServices } from '../../../../build_services';
|
||||
import { buildDataTableRecord } from '@kbn/discover-utils';
|
||||
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
||||
import { createSearchSessionMock } from '../../../../__mocks__/search_session';
|
||||
|
@ -41,6 +39,9 @@ import { getSessionServiceMock } from '@kbn/data-plugin/public/search/session/mo
|
|||
import { DiscoverMainProvider } from '../../services/discover_state_provider';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { ErrorCallout } from '../../../../components/common/error_callout';
|
||||
import * as localStorageModule from 'react-use/lib/useLocalStorage';
|
||||
|
||||
jest.spyOn(localStorageModule, 'default');
|
||||
|
||||
setHeaderActionMenuMounter(jest.fn());
|
||||
|
||||
|
@ -57,12 +58,7 @@ async function mountComponent(
|
|||
}) as DataMain$
|
||||
) {
|
||||
const searchSourceMock = createSearchSourceMock({});
|
||||
const services = {
|
||||
...createDiscoverServicesMock(),
|
||||
storage: new LocalStorageMock({
|
||||
[SIDEBAR_CLOSED_KEY]: prevSidebarClosed,
|
||||
}) as unknown as Storage,
|
||||
} as unknown as DiscoverServices;
|
||||
const services = createDiscoverServicesMock();
|
||||
const time = { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
|
||||
services.data.query.timefilter.timefilter.getTime = () => time;
|
||||
(services.data.query.queryString.getDefaultQuery as jest.Mock).mockReturnValue({
|
||||
|
@ -77,6 +73,9 @@ async function mountComponent(
|
|||
(searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation(
|
||||
jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } }))
|
||||
);
|
||||
(localStorageModule.default as jest.Mock).mockImplementation(
|
||||
jest.fn(() => [prevSidebarClosed, jest.fn()])
|
||||
);
|
||||
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||
|
||||
|
|
|
@ -6,17 +6,18 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import './discover_layout.scss';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHideFor,
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
useEuiBackgroundColor,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import classNames from 'classnames';
|
||||
|
@ -52,11 +53,6 @@ import { DiscoverHistogramLayout } from './discover_histogram_layout';
|
|||
import { ErrorCallout } from '../../../../components/common/error_callout';
|
||||
import { addLog } from '../../../../utils/add_log';
|
||||
|
||||
/**
|
||||
* Local storage key for sidebar persistence state
|
||||
*/
|
||||
export const SIDEBAR_CLOSED_KEY = 'discover:sidebarClosed';
|
||||
|
||||
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
||||
const TopNavMemoized = React.memo(DiscoverTopNav);
|
||||
|
||||
|
@ -72,11 +68,12 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
data,
|
||||
uiSettings,
|
||||
filterManager,
|
||||
storage,
|
||||
history,
|
||||
spaces,
|
||||
inspector,
|
||||
} = useDiscoverServices();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const pageBackgroundColor = useEuiBackgroundColor('plain');
|
||||
const globalQueryState = data.query.getState();
|
||||
const { main$ } = stateContainer.dataState.data$;
|
||||
const [query, savedQuery, columns, sort] = useAppStateSelector((state) => [
|
||||
|
@ -109,8 +106,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
return dataView.type !== DataViewType.ROLLUP && dataView.isTimeBased();
|
||||
}, [dataView]);
|
||||
|
||||
const initialSidebarClosed = Boolean(storage.get(SIDEBAR_CLOSED_KEY));
|
||||
const [isSidebarClosed, setIsSidebarClosed] = useState(initialSidebarClosed);
|
||||
const useNewFieldsApi = useMemo(() => !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), [uiSettings]);
|
||||
|
||||
const isPlainRecord = useMemo(() => getRawRecordType(query) === RecordRawType.PLAIN, [query]);
|
||||
|
@ -172,11 +167,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
filterManager.setFilters(disabledFilters);
|
||||
}, [filterManager]);
|
||||
|
||||
const toggleSidebarCollapse = useCallback(() => {
|
||||
storage.set(SIDEBAR_CLOSED_KEY, !isSidebarClosed);
|
||||
setIsSidebarClosed(!isSidebarClosed);
|
||||
}, [isSidebarClosed, storage]);
|
||||
|
||||
const contentCentered = resultState === 'uninitialized' || resultState === 'none';
|
||||
const documentState = useDataState(stateContainer.dataState.data$.documents$);
|
||||
|
||||
|
@ -240,7 +230,13 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
]);
|
||||
|
||||
return (
|
||||
<EuiPage className="dscPage" data-fetch-counter={fetchCounter.current}>
|
||||
<EuiPage
|
||||
className="dscPage"
|
||||
data-fetch-counter={fetchCounter.current}
|
||||
css={css`
|
||||
background-color: ${pageBackgroundColor};
|
||||
`}
|
||||
>
|
||||
<h1
|
||||
id="savedSearchTitle"
|
||||
className="euiScreenReaderOnly"
|
||||
|
@ -274,7 +270,7 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
spaces={spaces}
|
||||
history={history}
|
||||
/>
|
||||
<EuiFlexGroup className="dscPageBody__contents" gutterSize="s">
|
||||
<EuiFlexGroup className="dscPageBody__contents" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SidebarMemoized
|
||||
documents$={stateContainer.dataState.data$.documents$}
|
||||
|
@ -284,7 +280,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
onRemoveField={onRemoveColumn}
|
||||
onChangeDataView={stateContainer.actions.onChangeDataView}
|
||||
selectedDataView={dataView}
|
||||
isClosed={isSidebarClosed}
|
||||
trackUiMetric={trackUiMetric}
|
||||
onFieldEdited={onFieldEdited}
|
||||
onDataViewCreated={stateContainer.actions.onDataViewCreated}
|
||||
|
@ -292,23 +287,12 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiHideFor sizes={['xs', 's']}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButtonIcon
|
||||
iconType={isSidebarClosed ? 'menuRight' : 'menuLeft'}
|
||||
iconSize="m"
|
||||
size="xs"
|
||||
onClick={toggleSidebarCollapse}
|
||||
data-test-subj="collapseSideBarButton"
|
||||
aria-controls="discover-sidebar"
|
||||
aria-expanded={isSidebarClosed ? 'false' : 'true'}
|
||||
aria-label={i18n.translate('discover.toggleSidebarAriaLabel', {
|
||||
defaultMessage: 'Toggle sidebar',
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
border-right: ${euiTheme.border.thin};
|
||||
`}
|
||||
/>
|
||||
</EuiHideFor>
|
||||
<EuiFlexItem className="dscPageContent__wrapper">
|
||||
{resultState === 'none' ? (
|
||||
|
@ -335,7 +319,10 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
role="main"
|
||||
panelRef={resizeRef}
|
||||
paddingSize="none"
|
||||
borderRadius="none"
|
||||
hasShadow={false}
|
||||
hasBorder={false}
|
||||
color="transparent"
|
||||
className={classNames('dscPageContent', {
|
||||
'dscPageContent--centered': contentCentered,
|
||||
})}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { DragDrop, type DropType, DropOverlayWrapper } from '@kbn/dom-drag-drop';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import React, { useCallback } from 'react';
|
||||
|
@ -97,7 +97,6 @@ export const DiscoverMainContent = ({
|
|||
data-test-subj="dscMainContent"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
{!isPlainRecord && (
|
||||
<DocumentViewModeToggle
|
||||
viewMode={viewMode}
|
||||
|
|
|
@ -119,9 +119,9 @@ export const NoResultsSuggestions: React.FC<NoResultsSuggestionProps> = ({
|
|||
return (
|
||||
<EuiEmptyPrompt
|
||||
layout="horizontal"
|
||||
color="plain"
|
||||
color="transparent"
|
||||
icon={<NoResultsIllustration />}
|
||||
hasBorder
|
||||
hasBorder={false}
|
||||
title={
|
||||
<h2 data-test-subj="discoverNoResults">
|
||||
<FormattedMessage
|
||||
|
|
|
@ -748,5 +748,14 @@ describe('discover responsive sidebar', function () {
|
|||
|
||||
expect(comp.find('[data-test-subj="custom-data-view-picker"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow to toggle sidebar', async function () {
|
||||
const comp = await mountComponent(props);
|
||||
expect(findTestSubject(comp, 'fieldList').exists()).toBe(true);
|
||||
findTestSubject(comp, 'unifiedFieldListSidebar__toggle-collapse').simulate('click');
|
||||
expect(findTestSubject(comp, 'fieldList').exists()).toBe(false);
|
||||
findTestSubject(comp, 'unifiedFieldListSidebar__toggle-expand').simulate('click');
|
||||
expect(findTestSubject(comp, 'fieldList').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -42,7 +42,10 @@ const getCreationOptions: UnifiedFieldListSidebarContainerProps['getCreationOpti
|
|||
return {
|
||||
originatingApp: PLUGIN_ID,
|
||||
localStorageKeyPrefix: 'discover',
|
||||
compressed: true,
|
||||
showSidebarToggleButton: true,
|
||||
disableFieldsExistenceAutoFetching: true,
|
||||
buttonAddFieldVariant: 'toolbar',
|
||||
buttonPropsToTriggerFlyout: {
|
||||
contentProps: {
|
||||
id: DISCOVER_TOUR_STEP_ANCHOR_IDS.addFields,
|
||||
|
@ -87,10 +90,6 @@ export interface DiscoverSidebarResponsiveProps {
|
|||
* hits fetched from ES, displayed in the doc table
|
||||
*/
|
||||
documents$: DataDocuments$;
|
||||
/**
|
||||
* Has been toggled closed
|
||||
*/
|
||||
isClosed?: boolean;
|
||||
/**
|
||||
* Callback function when selecting a field
|
||||
*/
|
||||
|
@ -380,7 +379,6 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
|
|||
ref={initializeUnifiedFieldListSidebarContainerApi}
|
||||
variant={fieldListVariant}
|
||||
getCreationOptions={getCreationOptions}
|
||||
isSidebarCollapsed={props.isClosed}
|
||||
services={fieldListSidebarServices}
|
||||
dataView={selectedDataView}
|
||||
trackUiMetric={trackUiMetric}
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiTabs, EuiTab, useEuiPaddingSize } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { EuiTab, EuiTabs, useEuiPaddingSize, useEuiTheme } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { SHOW_FIELD_STATISTICS } from '@kbn/discover-utils';
|
||||
import { VIEW_MODE } from '../../../common/constants';
|
||||
import { useDiscoverServices } from '../../hooks/use_discover_services';
|
||||
|
@ -22,11 +21,12 @@ export const DocumentViewModeToggle = ({
|
|||
viewMode: VIEW_MODE;
|
||||
setDiscoverViewMode: (viewMode: VIEW_MODE) => void;
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { uiSettings } = useDiscoverServices();
|
||||
|
||||
const tabsCss = css`
|
||||
padding: 0 ${useEuiPaddingSize('s')};
|
||||
background-color: ${euiThemeVars.euiPageBackgroundColor};
|
||||
border-bottom: ${viewMode === VIEW_MODE.AGGREGATED_LEVEL ? euiTheme.border.thin : 'none'};
|
||||
`;
|
||||
|
||||
const showViewModeToggle = uiSettings.get(SHOW_FIELD_STATISTICS) ?? false;
|
||||
|
@ -36,7 +36,7 @@ export const DocumentViewModeToggle = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<EuiTabs size="s" css={tabsCss} data-test-subj="dscViewModeToggle">
|
||||
<EuiTabs size="s" css={tabsCss} data-test-subj="dscViewModeToggle" bottomBorder={false}>
|
||||
<EuiTab
|
||||
isSelected={viewMode === VIEW_MODE.DOCUMENT_LEVEL}
|
||||
onClick={() => setDiscoverViewMode(VIEW_MODE.DOCUMENT_LEVEL)}
|
||||
|
|
|
@ -275,7 +275,7 @@ export const UnifiedHistogramLayout = ({
|
|||
chart={chart}
|
||||
breakdown={breakdown}
|
||||
appendHitsCounter={appendHitsCounter}
|
||||
appendHistogram={showFixedPanels ? <EuiSpacer size="s" /> : <EuiSpacer size="l" />}
|
||||
appendHistogram={<EuiSpacer size="s" />}
|
||||
disableAutoFetching={disableAutoFetching}
|
||||
disableTriggers={disableTriggers}
|
||||
disabledActions={disabledActions}
|
||||
|
|
|
@ -6,12 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiResizableContainer,
|
||||
useEuiTheme,
|
||||
useGeneratedHtmlId,
|
||||
useResizeObserver,
|
||||
} from '@elastic/eui';
|
||||
import { EuiResizableContainer, useGeneratedHtmlId, useResizeObserver } from '@elastic/eui';
|
||||
import type { ResizeTrigger } from '@elastic/eui/src/components/resizable_container/types';
|
||||
import { css } from '@emotion/react';
|
||||
import { isEqual, round } from 'lodash';
|
||||
|
@ -162,12 +157,6 @@ export const PanelsResizable = ({
|
|||
disableResizeWithPortalsHack();
|
||||
}, [disableResizeWithPortalsHack, resizeWithPortalsHackIsResizing]);
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const buttonCss = css`
|
||||
margin-top: -${euiTheme.size.base};
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
return (
|
||||
<EuiResizableContainer
|
||||
className={className}
|
||||
|
@ -189,7 +178,7 @@ export const PanelsResizable = ({
|
|||
{topPanel}
|
||||
</EuiResizablePanel>
|
||||
<EuiResizableButton
|
||||
css={[resizeWithPortalsHackButtonCss, buttonCss]}
|
||||
css={resizeWithPortalsHackButtonCss}
|
||||
data-test-subj="unifiedHistogramResizableButton"
|
||||
/>
|
||||
<EuiResizablePanel
|
||||
|
|
|
@ -15,6 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
const PageObjects = getPageObjects(['common', 'timePicker', 'discover']);
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const security = getService('security');
|
||||
const browser = getService('browser');
|
||||
const from = 'Jan 1, 2019 @ 00:00:00.000';
|
||||
const to = 'Jan 1, 2019 @ 23:59:59.999';
|
||||
|
||||
|
@ -25,7 +26,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await kibanaServer.importExport.load(
|
||||
'test/functional/fixtures/kbn_archiver/date_nanos_mixed'
|
||||
);
|
||||
await kibanaServer.uiSettings.replace({ defaultIndex: 'timestamp-*' });
|
||||
await kibanaServer.uiSettings.replace({
|
||||
defaultIndex: 'timestamp-*',
|
||||
hideAnnouncements: true, // should be enough vertical space to render rows
|
||||
});
|
||||
await browser.setWindowSize(1200, 900);
|
||||
await security.testUser.setRoles(['kibana_admin', 'kibana_date_nanos_mixed']);
|
||||
await PageObjects.common.setTime({ from, to });
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
|
|
|
@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
return actualCount <= expectedCount;
|
||||
});
|
||||
const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
|
||||
expect(Math.round(newDurationHours)).to.be(26);
|
||||
expect(Math.round(newDurationHours)).to.be(24); // might fail if histogram's width changes
|
||||
|
||||
await retry.waitFor('doc table containing the documents of the brushed range', async () => {
|
||||
const rowData = await PageObjects.discover.getDocTableField(1);
|
||||
|
|
|
@ -214,16 +214,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('collapse expand', function () {
|
||||
it('should initially be expanded', async function () {
|
||||
await testSubjects.existOrFail('discover-sidebar');
|
||||
await testSubjects.existOrFail('fieldList');
|
||||
});
|
||||
|
||||
it('should collapse when clicked', async function () {
|
||||
await PageObjects.discover.toggleSidebarCollapse();
|
||||
await testSubjects.missingOrFail('discover-sidebar');
|
||||
await testSubjects.existOrFail('discover-sidebar');
|
||||
await testSubjects.missingOrFail('fieldList');
|
||||
});
|
||||
|
||||
it('should expand when clicked', async function () {
|
||||
await PageObjects.discover.toggleSidebarCollapse();
|
||||
await testSubjects.existOrFail('discover-sidebar');
|
||||
await testSubjects.existOrFail('fieldList');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -370,13 +370,14 @@ export class DiscoverPageObject extends FtrService {
|
|||
}
|
||||
|
||||
public async toggleSidebarCollapse() {
|
||||
return await this.testSubjects.click('collapseSideBarButton');
|
||||
return await this.testSubjects.click('unifiedFieldListSidebar__toggle');
|
||||
}
|
||||
|
||||
public async closeSidebar() {
|
||||
await this.retry.tryForTime(2 * 1000, async () => {
|
||||
await this.toggleSidebarCollapse();
|
||||
await this.testSubjects.missingOrFail('discover-sidebar');
|
||||
await this.testSubjects.click('unifiedFieldListSidebar__toggle-collapse');
|
||||
await this.testSubjects.missingOrFail('unifiedFieldListSidebar__toggle-collapse');
|
||||
await this.testSubjects.missingOrFail('fieldList');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2395,7 +2395,6 @@
|
|||
"discover.serverLocatorExtension.titleFromLocatorUnknown": "Recherche inconnue",
|
||||
"discover.singleDocRoute.errorTitle": "Une erreur s'est produite",
|
||||
"discover.skipToBottomButtonLabel": "Atteindre la fin du tableau",
|
||||
"discover.toggleSidebarAriaLabel": "Activer/Désactiver la barre latérale",
|
||||
"discover.topNav.openSearchPanel.manageSearchesButtonLabel": "Gérer les recherches",
|
||||
"discover.topNav.openSearchPanel.noSearchesFoundDescription": "Aucune recherche correspondante trouvée.",
|
||||
"discover.topNav.openSearchPanel.openSearchTitle": "Ouvrir une recherche",
|
||||
|
|
|
@ -2410,7 +2410,6 @@
|
|||
"discover.serverLocatorExtension.titleFromLocatorUnknown": "不明な検索",
|
||||
"discover.singleDocRoute.errorTitle": "エラーが発生しました",
|
||||
"discover.skipToBottomButtonLabel": "テーブルの最後に移動",
|
||||
"discover.toggleSidebarAriaLabel": "サイドバーを切り替える",
|
||||
"discover.topNav.openSearchPanel.manageSearchesButtonLabel": "検索の管理",
|
||||
"discover.topNav.openSearchPanel.noSearchesFoundDescription": "一致する検索が見つかりませんでした。",
|
||||
"discover.topNav.openSearchPanel.openSearchTitle": "検索を開く",
|
||||
|
|
|
@ -2410,7 +2410,6 @@
|
|||
"discover.serverLocatorExtension.titleFromLocatorUnknown": "未知搜索",
|
||||
"discover.singleDocRoute.errorTitle": "发生错误",
|
||||
"discover.skipToBottomButtonLabel": "转到表尾",
|
||||
"discover.toggleSidebarAriaLabel": "切换侧边栏",
|
||||
"discover.topNav.openSearchPanel.manageSearchesButtonLabel": "管理搜索",
|
||||
"discover.topNav.openSearchPanel.noSearchesFoundDescription": "未找到匹配的搜索。",
|
||||
"discover.topNav.openSearchPanel.openSearchTitle": "打开搜索",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue