/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ import React, { useMemo, useCallback, useEffect, useRef, useState } from 'react'; import { EuiComboBox, EuiFlexItem, EuiFormLabel, EuiFlexGrid, EuiFieldNumber, EuiFormErrorText, EuiCollapsibleNavGroup, EuiSpacer, EuiPanel, EuiForm, EuiSwitch, EuiCheckbox, EuiFormRow, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; import { Filter, IndexPattern, IndexPatternField } from '../../../../../../src/plugins/data/public'; import { useOpenSearchDashboards } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; import { MapServices } from '../../../types'; import { DocumentLayerSpecification } from '../../../model/mapLayerType'; import { formatFieldStringToComboBox, formatFieldsStringToComboBox, getFieldsOptions, } from '../../../utils/fields_options'; interface Props { setSelectedLayerConfig: Function; selectedLayerConfig: DocumentLayerSpecification; setIsUpdateDisabled: Function; indexPattern: IndexPattern | null | undefined; setIndexPattern: Function; } interface MemorizedForm { [indexPatternId: string]: | { filters?: Filter[]; geoField?: IndexPatternField; } | undefined; } export const DocumentLayerSource = ({ setSelectedLayerConfig, selectedLayerConfig, setIsUpdateDisabled, indexPattern, setIndexPattern, }: Props) => { const { services: { savedObjects: { client: savedObjectsClient }, data: { ui: { IndexPatternSelect, SearchBar }, indexPatterns, }, }, } = useOpenSearchDashboards(); const [hasInvalidRequestNumber, setHasInvalidRequestNumber] = useState(false); const [enableTooltips, setEnableTooltips] = useState( selectedLayerConfig.source.showTooltips ); const memorizedForm = useRef({}); const acceptedFieldTypes = ['geo_point', 'geo_shape']; const cacheKey = `${selectedLayerConfig.id}/${indexPattern?.id}`; const geoFields = useMemo(() => { return indexPattern?.fields.filter((field) => acceptedFieldTypes.indexOf(field.type) !== -1); }, [indexPattern]); const selectedField = useMemo(() => { return geoFields?.find((field) => field.name === selectedLayerConfig.source.geoFieldName); }, [geoFields, selectedLayerConfig]); const hasInvalidTooltipFields = useMemo(() => { return ( selectedLayerConfig.source.tooltipFields?.length === 0 && selectedLayerConfig.source.showTooltips ); }, [selectedLayerConfig.source.showTooltips, selectedLayerConfig.source.tooltipFields?.length]); // We want to memorize the filters and geoField selection when a map layer config is opened useEffect(() => { if (indexPattern?.id && indexPattern.id === selectedLayerConfig.source.indexPatternId) { if (!memorizedForm.current[cacheKey]) { memorizedForm.current[cacheKey] = { filters: selectedLayerConfig.source.filters, geoField: selectedField, }; } } }, [indexPattern, selectedLayerConfig, selectedField]); const onGeoFieldChange = useCallback( (field: IndexPatternField | null) => { setSelectedLayerConfig({ ...selectedLayerConfig, source: { ...selectedLayerConfig.source, geoFieldName: field?.displayName || undefined, geoFieldType: field?.type || undefined, }, }); // We'd like to memorize the geo field selection so that the selection // can be restored when changing index pattern back and forth if (indexPattern?.id) { memorizedForm.current[cacheKey] = { ...memorizedForm.current[cacheKey], geoField: field || undefined, }; } }, [selectedLayerConfig, setSelectedLayerConfig, indexPattern] ); const errorsMap = { datasource: ['Required'], geoFields: ['Required'], }; useEffect(() => { const disableUpdate = !indexPattern || !selectedField || hasInvalidRequestNumber || hasInvalidTooltipFields; setIsUpdateDisabled(disableUpdate); }, [ setIsUpdateDisabled, indexPattern, selectedField, hasInvalidRequestNumber, hasInvalidTooltipFields, ]); const onDocumentRequestNumberChange = (e: React.ChangeEvent) => { const value = e.target.value; const selectedNumber = parseInt(value, 10); const source = { ...selectedLayerConfig.source, documentRequestNumber: selectedNumber }; setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; const onTooltipSelectionChange = (options: any[]) => { const tooltipSelection: string[] = []; for (const option of options) { tooltipSelection.push(option.label); } const source = { ...selectedLayerConfig.source, tooltipFields: tooltipSelection }; setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; const onFiltersUpdated = useCallback( (filters: Filter[]) => { setSelectedLayerConfig({ ...selectedLayerConfig, source: { ...selectedLayerConfig.source, filters }, }); // We'd like to memorize the filter selection so that the selection // can be restored when changing index pattern back and forth if (indexPattern?.id) { memorizedForm.current[cacheKey] = { ...memorizedForm.current[cacheKey], filters, }; } }, [selectedLayerConfig, indexPattern] ); useEffect(() => { const selectIndexPattern = async () => { if (selectedLayerConfig.source.indexPatternId) { const selectedIndexPattern = await indexPatterns.get( selectedLayerConfig.source.indexPatternId ); setIndexPattern(selectedIndexPattern); } }; selectIndexPattern(); }, [indexPatterns, selectedLayerConfig.source.indexPatternId]); // Handle the side effects of index pattern change useEffect(() => { const source = { ...selectedLayerConfig.source }; // when index pattern changed, reset filters and geo field if (indexPattern && indexPattern.id !== selectedLayerConfig.source.indexPatternId) { source.indexPatternId = indexPattern.id ?? ''; source.indexPatternRefName = indexPattern.title; // Use memorized filters, otherwise, set filter selection to empty const filters = indexPattern.id ? memorizedForm.current[cacheKey]?.filters ?? [] : []; source.filters = filters; // Use memorized geo field, otherwise, set geo filter to empty const geoField = indexPattern.id ? memorizedForm.current[cacheKey]?.geoField : undefined; if (geoField) { source.geoFieldName = geoField.displayName; source.geoFieldType = geoField.type as 'geo_point' | 'geo_shape'; } setSelectedLayerConfig({ ...selectedLayerConfig, source, }); } }, [indexPattern]); useEffect(() => { setHasInvalidRequestNumber( selectedLayerConfig.source.documentRequestNumber < 1 || selectedLayerConfig.source.documentRequestNumber > 10000 ); }, [selectedLayerConfig.source.documentRequestNumber]); const onEnableTooltipsChange = (event: { target: { checked: React.SetStateAction }; }) => { setEnableTooltips(event.target.checked); const source = { ...selectedLayerConfig.source, showTooltips: event.target.checked }; setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; const onDisplayTooltipsOnHoverChange = (event: { target: { checked: React.SetStateAction }; }) => { const source = { ...selectedLayerConfig.source, displayTooltipsOnHover: event.target.checked }; setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; const onToggleGeoBoundingBox = (e: React.ChangeEvent) => { const source = { ...selectedLayerConfig.source, useGeoBoundingBoxFilter: e.target.checked }; setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; const onApplyGlobalFilters = (e: React.ChangeEvent) => { const source = { ...selectedLayerConfig.source, applyGlobalFilters: e.target.checked }; setSelectedLayerConfig({ ...selectedLayerConfig, source }); }; const shouldTooltipSectionOpen = () => { return ( selectedLayerConfig.source.showTooltips && selectedLayerConfig.source.tooltipFields?.length > 0 ); }; const filterPanelInitialIsOpen = selectedLayerConfig.source.filters?.length > 0 || selectedLayerConfig.source.useGeoBoundingBoxFilter; return (
{ const newIndexPattern = await indexPatterns.get(newIndexPatternId); setIndexPattern(newIndexPattern); }} isClearable={false} data-test-subj={'indexPatternSelect'} fullWidth={true} /> { const field = indexPattern?.getFieldByName(option[0]?.label); onGeoFieldChange(field || null); }} sortMatchesBy="startsWith" placeholder={i18n.translate('documentLayer.selectDataFieldPlaceholder', { defaultMessage: 'Select data field', })} data-test-subj={'geoFieldSelect'} fullWidth={true} isClearable={false} /> Number of documents {hasInvalidRequestNumber && ( )} {enableTooltips && ( <> Tooltip fields )}
); };