kibana/packages/kbn-unified-field-list
Davis McPhee 3e1865513d
[Discover] Add resize support to the Discover field list sidebar (#167066)
## Summary

This PR adds resize support to the Discover field list sidebar, which is
persisted to a user's local storage similar to the resizable chart
height.

Additionally it migrates the resizable layout code from Unified
Histogram to a new package called `kbn-resizable-layout` so it can be
shared between Discover and Unified Histogram, as well as act as a new
platform component that other teams can consume to create their own
resizable layouts.


![resize](71b9a0ae-1795-43c8-acb0-e75fe46e2a8a)

Resolves #9531.

### Checklist

- [ ] ~Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)~
- [ ]
~[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials~
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] ~Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))~
- [ ] ~If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2023-09-27 21:52:25 -03:00
..
__mocks__ [UnifiedFieldList][Discover] Create a high level unified field list building block (#160397) 2023-07-10 12:18:40 +02:00
src [Discover] Add resize support to the Discover field list sidebar (#167066) 2023-09-27 21:52:25 -03:00
index.ts [UnifiedDocViewer] Move Discover doc viewer into plugin/package (#162847) 2023-08-31 11:46:59 -07:00
jest.config.js [UnifiedFieldList] Convert from a plugin into a package (#158718) 2023-06-23 14:28:12 +02:00
kibana.jsonc [UnifiedFieldList] Convert from a plugin into a package (#158718) 2023-06-23 14:28:12 +02:00
package.json [UnifiedFieldList] Convert from a plugin into a package (#158718) 2023-06-23 14:28:12 +02:00
README.md [UnifiedFieldList][Discover] Create a high level unified field list building block (#160397) 2023-07-10 12:18:40 +02:00
tsconfig.json [Discover] Redesign for the grid, panels and sidebar v1 (#165866) 2023-09-12 08:51:34 +02:00

@kbn/unified-field-list

This Kibana package contains components and services for field list UI (as in fields sidebar on Discover and Lens pages).

UnifiedFieldListSidebarContainer - building block

An example of its usage can be found in Kibana example plugin examples/unified_field_list_examples.

Configure the field list:

const getCreationOptions: UnifiedFieldListSidebarContainerProps['getCreationOptions'] = () => {
  return {
    originatingApp: PLUGIN_ID,
    localStorageKeyPrefix: 'examples',
    timeRangeUpdatesType: 'timefilter',
    disablePopularFields: true,
    ... // more customization option are available
  };
};

Define a ref for accessing API if necessary:

const unifiedFieldListContainerRef = useRef<UnifiedFieldListSidebarContainerApi>(null);

where unifiedFieldListContainerRef.current provides the following API:

refetchFieldsExistenceInfo: ExistingFieldsFetcher['refetchFieldsExistenceInfo'];
closeFieldListFlyout: () => void;
// no user permission or missing dataViewFieldEditor service will result in `undefined` API methods
createField: undefined | (() => void);
editField: undefined | ((fieldName: string) => void);
deleteField: undefined | ((fieldName: string) => void);

Include the building block into your application:

<UnifiedFieldListSidebarContainer
  ref={unifiedFieldListContainerRef}
  // `responsive` is to show the list for desktop view and a button which triggers a flyout with the list for mobile view
  variant="responsive" // can be also `list-always` and `button-and-flyout-always`
  getCreationOptions={getCreationOptions}
  services={services}
  dataView={dataView}
  allFields={dataView.fields}
  workspaceSelectedFieldNames={selectedFieldNames}
  onAddFieldToWorkspace={onAddFieldToWorkspace}
  onRemoveFieldFromWorkspace={onRemoveFieldFromWorkspace}
  onAddFilter={onAddFilter}
  onFieldEdited={onFieldEdited}
/>

Field Stats and Field Popover Components - can be also used as a building block

  • <FieldStats .../> - loads and renders stats (Top values, Distribution) for a data view field.

  • <FieldVisualizeButton .../> - renders a button to open this field in Lens.

  • <FieldPopover .../> - a popover container component for a field.

  • <FieldPopoverHeader .../> - this header component included a field name and common actions.

  • <FieldPopoverVisualize .../> - renders Visualize action in the popover footer.

These components can be combined and customized as the following:

<FieldPopover 
  isOpen={isOpen}
  closePopover={closePopover}
  button={<your trigger>}
  renderHeader={() => 
    <FieldPopoverHeader 
      field={field}
      closePopover={closePopover}
      onAddFieldToWorkspace={onAddFieldToWorkspace}
      onAddFilter={onAddFilter}
      onEditField={onEditField}
      onDeleteField={onDeleteField}
      ...
    />
  }
  renderContent={() => 
    <>
      <FieldStats 
        field={field}
        dataViewOrDataViewId={dataView}
        onAddFilter={onAddFilter}
        ...
      />
      <FieldPopoverVisualize
        field={field}
        datatView={dataView}
        originatingApp={'<your app name>'}
        ...
      />
    </>
  }
  ...
/>

Field List subcomponents (for low level customization, otherwise consider using UnifiedFieldListSidebarContainer)

  • <FieldList .../> - a top-level component to render field filters and field list sections.

  • <FieldListFilters .../> - renders a field search input and field filters by type. Please use useGroupedFields hook for it. For a more direct control, see useFieldFilters hook.

  • <FieldListGrouped .../> - renders a fields list which is split in sections (Special, Selected, Popular, Available, Empty, Meta fields). It accepts already grouped fields, please use useGroupedFields hook for it.

  • <FieldIcon type={getFieldIconType(field)} /> - renders a field icon.

const { isProcessing } = useExistingFieldsFetcher({ // this hook fetches fields info to understand which fields are empty.
  dataViews: [currentDataView],
  ...
});
  
const { fieldListFiltersProps, fieldListGroupedProps } = useGroupedFields({
  dataViewId: currentDataViewId, // pass `null` here for text-based queries to skip fields existence check
  allFields, // pass `null` to show loading indicators
  ...
});

// and now we can render a field list
<FieldList
  isProcessing={isProcessing}
  prepend={
    <FieldListFilters
      {...fieldListFiltersProps}
    />
  }
>
  <FieldListGrouped
    {...fieldListGroupedProps}
    renderFieldItem={renderFieldItem}
  />
</FieldList>

Utils

  • getFieldIconProps(field) - gets icon's props to use with <FieldIcon {...getFieldIconProps(field)} /> component

  • getFieldIconType(field) - gets icon's type for the field

  • getFieldTypeName(field) - gets a field type label to show to the user

  • getFieldTypeDescription(field) - gets a field type description to show to the user as help info

Public Services

  • loadStats(...) - returns the loaded field stats (can also work with Ad-hoc data views)

  • loadFieldExisting(...) - returns the loaded existing fields (can also work with Ad-hoc data views)

Hooks

  • useGroupedFields(...) - this hook groups fields list into sections of Selected, Special, Available, Empty, Meta fields.

  • useFieldFilters(...) - manages state of FieldListFilters component. It is included into useGroupedFields.

  • useQuerySubscriber(...) - memorizes current query, filters and absolute date range which are set via UnifiedSearch.

  • useExistingFieldsFetcher(...) - this hook is responsible for fetching fields existence info for specified data views. It can be used higher in components tree than useExistingFieldsReader hook.

  • useExistingFieldsReader(...) - you can call this hook to read fields existence info which was fetched by useExistingFieldsFetcher hook. Using multiple "reader" hooks from different children components is supported. So you would need only one "fetcher" and as many "reader" hooks as necessary. It is included into useGroupedFields.

An example of using hooks for fetching and reading info whether a field is empty or not:

// `useQuerySubscriber` hook simplifies working with current query state which is required for `useExistingFieldsFetcher`
const querySubscriberResult = useQuerySubscriber(...);
// define a fetcher in any of your components
const { refetchFieldsExistenceInfo, isProcessing } = useExistingFieldsFetcher({
  dataViews,
  query: querySubscriberResult.query,
  filters: querySubscriberResult.filters,
  fromDate: querySubscriberResult.fromDate,
  toDate: querySubscriberResult.toDate,
  ...
});

// define a reader in any of your components on the same page to check whether a field contains data
const { hasFieldData } = useExistingFieldsReader();
const hasData = hasFieldData(currentDataViewId, fieldName) // returns a boolean

Development

See the kibana contributing guide for instructions setting up your development environment.