mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[UnifiedFieldList] Remove redundant server routes. Create new example plugin for unified field list components and migrate tests. (#158377)
- Closes https://github.com/elastic/kibana/issues/147885
- Closes https://github.com/elastic/kibana/issues/157109
## Summary
**Before:**
Unified Field List plugin has internal routes (wrappers for client code)
which exist only to run api functional tests against them:
- `/api/unified_field_list/existing_fields/{dataViewId}`
- `/api/unified_field_list/field_stats`
Client code does not call these routes directly. So there is no reason
in keeping and versioning them.
**After:**
- Internal routes are removed
- A new "Unified Field List Examples" page was created
http://localhost:5601/app/unifiedFieldListExamples
- API functional tests (which used the routes) were converted to
functional tests against this new example page
- Created a new `unifiedFieldList` page object which is used now in
functional tests (methods are extracted from existing `discover` page
object).
**For testing:**
Steps:
1. Run Kibana with examples: `yarn start --run-examples`
2. Install sample data
3. And navigate to Developer Examples > Unified Field List Examples
page.

### Checklist
- [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
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f43096a78f
commit
8d399fe3aa
90 changed files with 2104 additions and 1846 deletions
9
examples/unified_field_list_examples/README.md
Normal file
9
examples/unified_field_list_examples/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# unified_field_list_examples
|
||||
|
||||
Examples of unified field list components.
|
||||
|
||||
To run this example, ensure you have data to search against (for example, the sample datasets) and start kibana with the `--run-examples` flag.
|
||||
|
||||
```bash
|
||||
yarn start --run-examples
|
||||
```
|
10
examples/unified_field_list_examples/common/index.ts
Normal file
10
examples/unified_field_list_examples/common/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 const PLUGIN_ID = 'unifiedFieldListExamples';
|
||||
export const PLUGIN_NAME = 'Unified Field List Examples';
|
24
examples/unified_field_list_examples/kibana.jsonc
Normal file
24
examples/unified_field_list_examples/kibana.jsonc
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/unified-field-list-examples-plugin",
|
||||
"owner": "@elastic/kibana-data-discovery",
|
||||
"description": "Examples for using unified field list.",
|
||||
"plugin": {
|
||||
"id": "unifiedFieldListExamples",
|
||||
"server": false,
|
||||
"browser": true,
|
||||
"requiredPlugins": [
|
||||
"navigation",
|
||||
"developerExamples",
|
||||
"inspector",
|
||||
"kibanaUtils",
|
||||
"unifiedSearch",
|
||||
"unifiedFieldList",
|
||||
"data",
|
||||
"dataViews",
|
||||
"dataViewFieldEditor",
|
||||
"charts",
|
||||
"fieldFormats"
|
||||
]
|
||||
}
|
||||
}
|
37
examples/unified_field_list_examples/public/application.tsx
Normal file
37
examples/unified_field_list_examples/public/application.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 ReactDOM from 'react-dom';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
|
||||
import { AppPluginStartDependencies } from './types';
|
||||
import { UnifiedFieldListExampleApp } from './example_app';
|
||||
|
||||
export const renderApp = (
|
||||
core: CoreStart,
|
||||
deps: AppPluginStartDependencies,
|
||||
{ element }: AppMountParameters
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<I18nProvider>
|
||||
<UnifiedFieldListExampleApp
|
||||
services={{
|
||||
core,
|
||||
uiSettings: core.uiSettings,
|
||||
...deps,
|
||||
}}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
element
|
||||
);
|
||||
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
};
|
161
examples/unified_field_list_examples/public/example_app.tsx
Normal file
161
examples/unified_field_list_examples/public/example_app.tsx
Normal file
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* 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, { useCallback, useEffect, useState } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPage,
|
||||
EuiPageBody,
|
||||
EuiPageSidebar,
|
||||
EuiTitle,
|
||||
EuiEmptyPrompt,
|
||||
EuiLoadingLogo,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { RootDragDropProvider } from '@kbn/dom-drag-drop';
|
||||
import type { DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { PLUGIN_ID, PLUGIN_NAME } from '../common';
|
||||
import { FieldListSidebar, FieldListSidebarProps } from './field_list_sidebar';
|
||||
import { ExampleDropZone } from './example_drop_zone';
|
||||
|
||||
interface UnifiedFieldListExampleAppProps {
|
||||
services: FieldListSidebarProps['services'];
|
||||
}
|
||||
|
||||
export const UnifiedFieldListExampleApp: React.FC<UnifiedFieldListExampleAppProps> = ({
|
||||
services,
|
||||
}) => {
|
||||
const { navigation, data, unifiedSearch } = services;
|
||||
const { IndexPatternSelect } = unifiedSearch.ui;
|
||||
const [dataView, setDataView] = useState<DataView | null>();
|
||||
const [selectedFieldNames, setSelectedFieldNames] = useState<string[]>([]);
|
||||
|
||||
const onAddFieldToWorkplace = useCallback(
|
||||
(field: DataViewField) => {
|
||||
setSelectedFieldNames((names) => [...names, field.name]);
|
||||
},
|
||||
[setSelectedFieldNames]
|
||||
);
|
||||
|
||||
const onRemoveFieldFromWorkspace = useCallback(
|
||||
(field: DataViewField) => {
|
||||
setSelectedFieldNames((names) => names.filter((name) => name !== field.name));
|
||||
},
|
||||
[setSelectedFieldNames]
|
||||
);
|
||||
|
||||
const onDropFieldToWorkplace = useCallback(
|
||||
(fieldName: string) => {
|
||||
setSelectedFieldNames((names) => [...names.filter((name) => name !== fieldName), fieldName]);
|
||||
},
|
||||
[setSelectedFieldNames]
|
||||
);
|
||||
|
||||
// Fetch the default data view using the `data.dataViews` service, as the component is mounted.
|
||||
useEffect(() => {
|
||||
const setDefaultDataView = async () => {
|
||||
try {
|
||||
const defaultDataView = await data.dataViews.getDefault();
|
||||
setDataView(defaultDataView);
|
||||
} catch (e) {
|
||||
setDataView(null);
|
||||
}
|
||||
};
|
||||
|
||||
setDefaultDataView();
|
||||
}, [data]);
|
||||
|
||||
if (typeof dataView === 'undefined') {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
icon={<EuiLoadingLogo logo="logoKibana" size="xl" />}
|
||||
title={<h2>{PLUGIN_NAME}</h2>}
|
||||
body={<p>Loading...</p>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPage grow={true}>
|
||||
<EuiPageBody paddingSize="s">
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<h1>{PLUGIN_NAME}</h1>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<IndexPatternSelect
|
||||
placeholder={i18n.translate('searchSessionExample.selectDataViewPlaceholder', {
|
||||
defaultMessage: 'Select data view',
|
||||
})}
|
||||
indexPatternId={dataView?.id || ''}
|
||||
onChange={async (dataViewId?: string) => {
|
||||
if (dataViewId) {
|
||||
const newDataView = await data.dataViews.get(dataViewId);
|
||||
setDataView(newDataView);
|
||||
} else {
|
||||
setDataView(undefined);
|
||||
}
|
||||
}}
|
||||
isClearable={false}
|
||||
data-test-subj="dataViewSelector"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{dataView ? (
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<navigation.ui.TopNavMenu
|
||||
appName={PLUGIN_ID}
|
||||
showSearchBar={true}
|
||||
useDefaultBehaviors={true}
|
||||
indexPatterns={dataView ? [dataView] : undefined}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={true}>
|
||||
<RootDragDropProvider>
|
||||
<EuiFlexGroup direction="row" alignItems="stretch">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPageSidebar
|
||||
css={css`
|
||||
flex: 1;
|
||||
width: 320px;
|
||||
`}
|
||||
>
|
||||
<FieldListSidebar
|
||||
services={services}
|
||||
dataView={dataView}
|
||||
selectedFieldNames={selectedFieldNames}
|
||||
onAddFieldToWorkplace={onAddFieldToWorkplace}
|
||||
onRemoveFieldFromWorkspace={onRemoveFieldFromWorkspace}
|
||||
/>
|
||||
</EuiPageSidebar>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ExampleDropZone onDropField={onDropFieldToWorkplace} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</RootDragDropProvider>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
) : (
|
||||
<EuiEmptyPrompt
|
||||
iconType="warning"
|
||||
color="warning"
|
||||
title={<h2>Select a data view</h2>}
|
||||
body={<p>Make sure to have at least one data view or install sample data.</p>}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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, { useContext, useMemo } from 'react';
|
||||
import { DragContext, DragDrop, DropOverlayWrapper, DropType } from '@kbn/dom-drag-drop';
|
||||
import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui';
|
||||
|
||||
const DROP_PROPS = {
|
||||
value: {
|
||||
id: 'exampleDropZone',
|
||||
humanData: {
|
||||
label: 'Drop zone for selecting fields',
|
||||
},
|
||||
},
|
||||
order: [1, 0, 0, 0],
|
||||
types: ['field_add'] as DropType[],
|
||||
};
|
||||
|
||||
export interface ExampleDropZoneProps {
|
||||
onDropField: (fieldName: string) => void;
|
||||
}
|
||||
|
||||
export const ExampleDropZone: React.FC<ExampleDropZoneProps> = ({ onDropField }) => {
|
||||
const dragDropContext = useContext(DragContext);
|
||||
const draggingFieldName = dragDropContext.dragging?.id;
|
||||
|
||||
const onDroppingField = useMemo(() => {
|
||||
if (!draggingFieldName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return () => onDropField(draggingFieldName);
|
||||
}, [onDropField, draggingFieldName]);
|
||||
|
||||
const isDropAllowed = Boolean(onDroppingField);
|
||||
|
||||
return (
|
||||
<DragDrop
|
||||
draggable={false}
|
||||
dropTypes={isDropAllowed ? DROP_PROPS.types : undefined}
|
||||
value={DROP_PROPS.value}
|
||||
order={DROP_PROPS.order}
|
||||
onDrop={onDroppingField}
|
||||
>
|
||||
<DropOverlayWrapper isVisible={isDropAllowed}>
|
||||
<EuiPanel hasShadow={false} paddingSize="l" className="eui-fullHeight">
|
||||
<EuiEmptyPrompt
|
||||
iconType="beaker"
|
||||
title={<h3>Example drop zone</h3>}
|
||||
body={
|
||||
<p>
|
||||
Drop <strong>{draggingFieldName || 'a field'}</strong> here to select it
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</DropOverlayWrapper>
|
||||
</DragDrop>
|
||||
);
|
||||
};
|
225
examples/unified_field_list_examples/public/field_list_item.tsx
Normal file
225
examples/unified_field_list_examples/public/field_list_item.tsx
Normal file
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { DataViewField, DataView } from '@kbn/data-views-plugin/common';
|
||||
import { DragDrop } from '@kbn/dom-drag-drop';
|
||||
import {
|
||||
AddFieldFilterHandler,
|
||||
FieldItemButton,
|
||||
FieldItemButtonProps,
|
||||
FieldPopover,
|
||||
FieldPopoverHeader,
|
||||
FieldsGroupNames,
|
||||
FieldStats,
|
||||
hasQuerySubscriberData,
|
||||
RenderFieldItemParams,
|
||||
useQuerySubscriber,
|
||||
} from '@kbn/unified-field-list-plugin/public';
|
||||
import { generateFilters } from '@kbn/data-plugin/public';
|
||||
import type { CoreStart } from '@kbn/core-lifecycle-browser';
|
||||
import type { AppPluginStartDependencies } from './types';
|
||||
|
||||
export interface FieldListItemProps extends RenderFieldItemParams<DataViewField> {
|
||||
dataView: DataView;
|
||||
services: AppPluginStartDependencies & {
|
||||
core: CoreStart;
|
||||
uiSettings: CoreStart['uiSettings'];
|
||||
};
|
||||
isSelected: boolean;
|
||||
onAddFieldToWorkspace: FieldItemButtonProps<DataViewField>['onAddFieldToWorkspace'];
|
||||
onRemoveFieldFromWorkspace: FieldItemButtonProps<DataViewField>['onRemoveFieldFromWorkspace'];
|
||||
onRefreshFields: () => void;
|
||||
}
|
||||
|
||||
export function FieldListItem({
|
||||
dataView,
|
||||
services,
|
||||
isSelected,
|
||||
field,
|
||||
fieldSearchHighlight,
|
||||
groupIndex,
|
||||
groupName,
|
||||
itemIndex,
|
||||
hideDetails,
|
||||
onRefreshFields,
|
||||
onAddFieldToWorkspace,
|
||||
onRemoveFieldFromWorkspace,
|
||||
}: FieldListItemProps) {
|
||||
const { dataViewFieldEditor, data } = services;
|
||||
const querySubscriberResult = useQuerySubscriber({ data, listenToSearchSessionUpdates: false }); // this example app does not use search sessions
|
||||
const filterManager = data?.query?.filterManager;
|
||||
const [infoIsOpen, setOpen] = useState(false);
|
||||
|
||||
const togglePopover = useCallback(() => {
|
||||
setOpen((value) => !value);
|
||||
}, [setOpen]);
|
||||
|
||||
const closePopover = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
|
||||
const closeFieldEditor = useRef<() => void | undefined>();
|
||||
const setFieldEditorRef = useCallback((ref: () => void | undefined) => {
|
||||
closeFieldEditor.current = ref;
|
||||
}, []);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
id: field.name,
|
||||
humanData: { label: field.displayName || field.name },
|
||||
}),
|
||||
[field]
|
||||
);
|
||||
|
||||
const order = useMemo(() => [0, groupIndex, itemIndex], [groupIndex, itemIndex]);
|
||||
|
||||
const addFilterAndClose: AddFieldFilterHandler | undefined = useMemo(
|
||||
() =>
|
||||
filterManager && dataView
|
||||
? (clickedField, values, operation) => {
|
||||
closePopover();
|
||||
const newFilters = generateFilters(
|
||||
filterManager,
|
||||
clickedField,
|
||||
values,
|
||||
operation,
|
||||
dataView
|
||||
);
|
||||
filterManager.addFilters(newFilters);
|
||||
}
|
||||
: undefined,
|
||||
[dataView, filterManager, closePopover]
|
||||
);
|
||||
|
||||
const editFieldAndClose = useMemo(
|
||||
() =>
|
||||
dataView
|
||||
? (fieldName?: string) => {
|
||||
const ref = dataViewFieldEditor.openEditor({
|
||||
ctx: {
|
||||
dataView,
|
||||
},
|
||||
fieldName,
|
||||
onSave: async () => {
|
||||
onRefreshFields();
|
||||
},
|
||||
});
|
||||
if (setFieldEditorRef) {
|
||||
setFieldEditorRef(ref);
|
||||
}
|
||||
closePopover();
|
||||
}
|
||||
: undefined,
|
||||
[dataViewFieldEditor, dataView, setFieldEditorRef, closePopover, onRefreshFields]
|
||||
);
|
||||
|
||||
const removeFieldAndClose = useMemo(
|
||||
() =>
|
||||
dataView
|
||||
? async (fieldName: string) => {
|
||||
const ref = dataViewFieldEditor.openDeleteModal({
|
||||
ctx: {
|
||||
dataView,
|
||||
},
|
||||
fieldName,
|
||||
onDelete: async () => {
|
||||
onRefreshFields();
|
||||
},
|
||||
});
|
||||
if (setFieldEditorRef) {
|
||||
setFieldEditorRef(ref);
|
||||
}
|
||||
closePopover();
|
||||
}
|
||||
: undefined,
|
||||
[dataView, setFieldEditorRef, closePopover, dataViewFieldEditor, onRefreshFields]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const cleanup = () => {
|
||||
if (closeFieldEditor?.current) {
|
||||
closeFieldEditor?.current();
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
// Make sure to close the editor when unmounting
|
||||
cleanup();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<li>
|
||||
<FieldPopover
|
||||
isOpen={infoIsOpen}
|
||||
closePopover={closePopover}
|
||||
button={
|
||||
<DragDrop
|
||||
draggable
|
||||
order={order}
|
||||
value={value}
|
||||
dataTestSubj={`fieldListPanelField-${field.name}`}
|
||||
onDragStart={closePopover}
|
||||
>
|
||||
<FieldItemButton
|
||||
field={field}
|
||||
fieldSearchHighlight={fieldSearchHighlight}
|
||||
size="xs"
|
||||
isActive={infoIsOpen}
|
||||
isEmpty={groupName === FieldsGroupNames.EmptyFields}
|
||||
isSelected={isSelected}
|
||||
onClick={togglePopover}
|
||||
onAddFieldToWorkspace={onAddFieldToWorkspace}
|
||||
onRemoveFieldFromWorkspace={onRemoveFieldFromWorkspace}
|
||||
/>
|
||||
</DragDrop>
|
||||
}
|
||||
renderHeader={() => {
|
||||
return (
|
||||
<FieldPopoverHeader
|
||||
field={field}
|
||||
closePopover={closePopover}
|
||||
onAddFieldToWorkspace={!isSelected ? onAddFieldToWorkspace : undefined}
|
||||
onAddFilter={addFilterAndClose}
|
||||
onEditField={editFieldAndClose}
|
||||
onDeleteField={removeFieldAndClose}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
renderContent={
|
||||
!hideDetails
|
||||
? () => (
|
||||
<>
|
||||
{hasQuerySubscriberData(querySubscriberResult) && (
|
||||
<FieldStats
|
||||
services={services}
|
||||
query={querySubscriberResult.query}
|
||||
filters={querySubscriberResult.filters}
|
||||
fromDate={querySubscriberResult.fromDate}
|
||||
toDate={querySubscriberResult.toDate}
|
||||
dataViewOrDataViewId={dataView}
|
||||
onAddFilter={addFilterAndClose}
|
||||
field={field}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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, { useCallback, useContext, useMemo } from 'react';
|
||||
import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { ChildDragDropProvider, DragContext } from '@kbn/dom-drag-drop';
|
||||
import {
|
||||
FieldList,
|
||||
FieldListFilters,
|
||||
FieldListGrouped,
|
||||
FieldListGroupedProps,
|
||||
FieldsGroupNames,
|
||||
useExistingFieldsFetcher,
|
||||
useGroupedFields,
|
||||
useQuerySubscriber,
|
||||
} from '@kbn/unified-field-list-plugin/public';
|
||||
import { FieldListItem, FieldListItemProps } from './field_list_item';
|
||||
|
||||
export interface FieldListSidebarProps {
|
||||
dataView: DataView;
|
||||
selectedFieldNames: string[];
|
||||
services: FieldListItemProps['services'];
|
||||
onAddFieldToWorkplace: FieldListItemProps['onAddFieldToWorkspace'];
|
||||
onRemoveFieldFromWorkspace: FieldListItemProps['onRemoveFieldFromWorkspace'];
|
||||
}
|
||||
|
||||
export const FieldListSidebar: React.FC<FieldListSidebarProps> = ({
|
||||
dataView,
|
||||
selectedFieldNames,
|
||||
services,
|
||||
onAddFieldToWorkplace,
|
||||
onRemoveFieldFromWorkspace,
|
||||
}) => {
|
||||
const dragDropContext = useContext(DragContext);
|
||||
const allFields = dataView.fields;
|
||||
const activeDataViews = useMemo(() => [dataView], [dataView]);
|
||||
const querySubscriberResult = useQuerySubscriber({
|
||||
data: services.data,
|
||||
listenToSearchSessionUpdates: false, // this example app does not use search sessions
|
||||
});
|
||||
|
||||
const onSelectedFieldFilter = useCallback(
|
||||
(field: DataViewField) => {
|
||||
return selectedFieldNames.includes(field.name);
|
||||
},
|
||||
[selectedFieldNames]
|
||||
);
|
||||
|
||||
const { refetchFieldsExistenceInfo, isProcessing } = useExistingFieldsFetcher({
|
||||
dataViews: activeDataViews, // if you need field existence info for more than one data view, you can specify it here
|
||||
query: querySubscriberResult.query,
|
||||
filters: querySubscriberResult.filters,
|
||||
fromDate: querySubscriberResult.fromDate,
|
||||
toDate: querySubscriberResult.toDate,
|
||||
services,
|
||||
});
|
||||
|
||||
const { fieldListFiltersProps, fieldListGroupedProps } = useGroupedFields({
|
||||
dataViewId: dataView.id ?? null,
|
||||
allFields,
|
||||
services,
|
||||
isAffectedByGlobalFilter: Boolean(querySubscriberResult.filters?.length),
|
||||
onSupportedFieldFilter,
|
||||
onSelectedFieldFilter,
|
||||
});
|
||||
|
||||
const onRefreshFields = useCallback(() => {
|
||||
refetchFieldsExistenceInfo();
|
||||
}, [refetchFieldsExistenceInfo]);
|
||||
|
||||
const renderFieldItem: FieldListGroupedProps<DataViewField>['renderFieldItem'] = useCallback(
|
||||
(params) => (
|
||||
<FieldListItem
|
||||
dataView={dataView}
|
||||
services={services}
|
||||
isSelected={
|
||||
params.groupName === FieldsGroupNames.SelectedFields ||
|
||||
selectedFieldNames.includes(params.field.name)
|
||||
}
|
||||
onRefreshFields={onRefreshFields}
|
||||
onAddFieldToWorkspace={onAddFieldToWorkplace}
|
||||
onRemoveFieldFromWorkspace={onRemoveFieldFromWorkspace}
|
||||
{...params}
|
||||
/>
|
||||
),
|
||||
[
|
||||
dataView,
|
||||
services,
|
||||
onRefreshFields,
|
||||
selectedFieldNames,
|
||||
onAddFieldToWorkplace,
|
||||
onRemoveFieldFromWorkspace,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<ChildDragDropProvider {...dragDropContext}>
|
||||
<FieldList
|
||||
isProcessing={isProcessing}
|
||||
prepend={<FieldListFilters {...fieldListFiltersProps} />}
|
||||
>
|
||||
<FieldListGrouped
|
||||
{...fieldListGroupedProps}
|
||||
renderFieldItem={renderFieldItem}
|
||||
localStorageKeyPrefix="examples"
|
||||
/>
|
||||
</FieldList>
|
||||
</ChildDragDropProvider>
|
||||
);
|
||||
};
|
||||
|
||||
function onSupportedFieldFilter(field: DataViewField): boolean {
|
||||
return field.name !== '_source';
|
||||
}
|
19
examples/unified_field_list_examples/public/index.ts
Normal file
19
examples/unified_field_list_examples/public/index.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { UnifiedFieldListExamplesPlugin } from './plugin';
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
export function plugin() {
|
||||
return new UnifiedFieldListExamplesPlugin();
|
||||
}
|
||||
export type {
|
||||
UnifiedFieldListExamplesPluginSetup,
|
||||
UnifiedFieldListExamplesPluginStart,
|
||||
} from './types';
|
77
examples/unified_field_list_examples/public/plugin.ts
Normal file
77
examples/unified_field_list_examples/public/plugin.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 {
|
||||
AppMountParameters,
|
||||
AppNavLinkStatus,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
} from '@kbn/core/public';
|
||||
import {
|
||||
AppPluginSetupDependencies,
|
||||
AppPluginStartDependencies,
|
||||
UnifiedFieldListExamplesPluginSetup,
|
||||
UnifiedFieldListExamplesPluginStart,
|
||||
} from './types';
|
||||
import { PLUGIN_ID, PLUGIN_NAME } from '../common';
|
||||
import image from './unified_field_list.png';
|
||||
|
||||
export class UnifiedFieldListExamplesPlugin
|
||||
implements
|
||||
Plugin<
|
||||
UnifiedFieldListExamplesPluginSetup,
|
||||
UnifiedFieldListExamplesPluginStart,
|
||||
AppPluginSetupDependencies,
|
||||
AppPluginStartDependencies
|
||||
>
|
||||
{
|
||||
public setup(
|
||||
core: CoreSetup<AppPluginStartDependencies>,
|
||||
{ developerExamples }: AppPluginSetupDependencies
|
||||
): UnifiedFieldListExamplesPluginSetup {
|
||||
// Register an application into the side navigation menu
|
||||
core.application.register({
|
||||
id: PLUGIN_ID,
|
||||
title: PLUGIN_NAME,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
mount: async (params: AppMountParameters) => {
|
||||
// Load application bundle
|
||||
const { renderApp } = await import('./application');
|
||||
// Get start services as specified in kibana.json
|
||||
const [coreStart, depsStart] = await core.getStartServices();
|
||||
// Render the application
|
||||
return renderApp(coreStart, depsStart, params);
|
||||
},
|
||||
});
|
||||
|
||||
developerExamples.register({
|
||||
appId: PLUGIN_ID,
|
||||
title: PLUGIN_NAME,
|
||||
description: `Examples of unified field list functionality.`,
|
||||
image,
|
||||
links: [
|
||||
{
|
||||
label: 'README',
|
||||
href: 'https://github.com/elastic/kibana/tree/main/src/plugins/unified_field_list/README.md',
|
||||
iconType: 'logoGithub',
|
||||
target: '_blank',
|
||||
size: 's',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart): UnifiedFieldListExamplesPluginStart {
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
35
examples/unified_field_list_examples/public/types.ts
Normal file
35
examples/unified_field_list_examples/public/types.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||
import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
|
||||
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
|
||||
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import type { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface UnifiedFieldListExamplesPluginSetup {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface UnifiedFieldListExamplesPluginStart {}
|
||||
|
||||
export interface AppPluginSetupDependencies {
|
||||
developerExamples: DeveloperExamplesSetup;
|
||||
}
|
||||
|
||||
export interface AppPluginStartDependencies {
|
||||
navigation: NavigationPublicPluginStart;
|
||||
data: DataPublicPluginStart;
|
||||
dataViews: DataViewsPublicPluginStart;
|
||||
dataViewFieldEditor: DataViewFieldEditorStart;
|
||||
unifiedSearch: UnifiedSearchPublicPluginStart;
|
||||
charts: ChartsPluginStart;
|
||||
fieldFormats: FieldFormatsStart;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
32
examples/unified_field_list_examples/tsconfig.json
Normal file
32
examples/unified_field_list_examples/tsconfig.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"common/**/*.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"../../typings/**/*",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/data-plugin",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/navigation-plugin",
|
||||
"@kbn/developer-examples-plugin",
|
||||
"@kbn/unified-search-plugin",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/i18n",
|
||||
"@kbn/dom-drag-drop",
|
||||
"@kbn/unified-field-list-plugin",
|
||||
"@kbn/core-lifecycle-browser",
|
||||
"@kbn/charts-plugin",
|
||||
"@kbn/field-formats-plugin",
|
||||
"@kbn/data-view-field-editor-plugin",
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue