"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useIndexData = void 0; var _react = require("react"); var _ebtTools = require("@kbn/ebt-tools"); var _mlRuntimeFieldUtils = require("@kbn/ml-runtime-field-utils"); var _mlQueryUtils = require("@kbn/ml-query-utils"); var _mlDataGrid = require("@kbn/ml-data-grid"); var _type_guards = require("../../../common/api_schemas/type_guards"); var _field_utils = require("../../../common/utils/field_utils"); var _errors = require("../../../common/utils/errors"); var _common = require("../common"); var _app_dependencies = require("../app_dependencies"); var _use_api = require("./use_api"); var _use_data_search = require("./use_data_search"); /* * 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; you may not use this file except in compliance with the Elastic License * 2.0. */ const useIndexData = (dataView, query, combinedRuntimeMappings, timeRangeMs) => { const { analytics } = (0, _app_dependencies.useAppDependencies)(); // Store the performance metric's start time using a ref // to be able to track it across rerenders. const loadIndexDataStartTime = (0, _react.useRef)(window.performance.now()); const indexPattern = (0, _react.useMemo)(() => dataView.getIndexPattern(), [dataView]); const api = (0, _use_api.useApi)(); const dataSearch = (0, _use_data_search.useDataSearch)(); const toastNotifications = (0, _app_dependencies.useToastNotifications)(); const [dataViewFields, setDataViewFields] = (0, _react.useState)(); const baseFilterCriteria = (0, _mlQueryUtils.buildBaseFilterCriteria)(dataView.timeFieldName, timeRangeMs === null || timeRangeMs === void 0 ? void 0 : timeRangeMs.from, timeRangeMs === null || timeRangeMs === void 0 ? void 0 : timeRangeMs.to, query); const defaultQuery = (0, _react.useMemo)(() => timeRangeMs && dataView.timeFieldName ? baseFilterCriteria[0] : _common.matchAllQuery, [baseFilterCriteria, dataView, timeRangeMs]); const queryWithBaseFilterCriteria = { bool: { filter: baseFilterCriteria } }; (0, _react.useEffect)(() => { if (dataView.timeFieldName !== undefined && timeRangeMs === undefined) { return; } const abortController = new AbortController(); // Fetch 500 random documents to determine populated fields. // This is a workaround to avoid passing potentially thousands of unpopulated fields // (for example, as part of filebeat/metricbeat/ECS based indices) // to the data grid component which would significantly slow down the page. const fetchDataGridSampleDocuments = async function () { setErrorMessage(''); setStatus(_mlDataGrid.INDEX_STATUS.LOADING); const esSearchRequest = { index: indexPattern, body: { fields: ['*'], _source: false, query: { function_score: { query: defaultQuery, random_score: {} } }, size: 500 } }; const resp = await dataSearch(esSearchRequest, abortController.signal); if (!(0, _type_guards.isEsSearchResponse)(resp)) { setErrorMessage((0, _errors.getErrorMessage)(resp)); setStatus(_mlDataGrid.INDEX_STATUS.ERROR); return; } const isCrossClusterSearch = indexPattern.includes(':'); const isMissingFields = resp.hits.hits.every(d => typeof d.fields === 'undefined'); const docs = resp.hits.hits.map(d => { var _d$fields; return (0, _mlDataGrid.getProcessedFields)((_d$fields = d.fields) !== null && _d$fields !== void 0 ? _d$fields : {}); }); // Get all field names for each returned doc and flatten it // to a list of unique field names used across all docs. const allDataViewFields = (0, _mlDataGrid.getFieldsFromKibanaIndexPattern)(dataView); const populatedFields = [...new Set(docs.map(Object.keys).flat(1))].filter(d => allDataViewFields.includes(d)).sort(); setCcsWarning(isCrossClusterSearch && isMissingFields); setStatus(_mlDataGrid.INDEX_STATUS.LOADED); setDataViewFields(populatedFields); }; fetchDataGridSampleDocuments(); return () => { abortController.abort(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [timeRangeMs]); const columns = (0, _react.useMemo)(() => { if (typeof dataViewFields === 'undefined') { return []; } let result = []; // Get the the runtime fields that are defined from API field and index patterns if (combinedRuntimeMappings !== undefined) { result = Object.keys(combinedRuntimeMappings).map(fieldName => { const field = combinedRuntimeMappings[fieldName]; const schema = (0, _mlDataGrid.getDataGridSchemaFromESFieldType)(field.type); return { id: fieldName, schema }; }); } // Combine the runtime field that are defined from API field dataViewFields.forEach(id => { const field = dataView.fields.getByName(id); if (!(field !== null && field !== void 0 && field.runtimeField)) { const schema = (0, _mlDataGrid.getDataGridSchemaFromKibanaFieldType)(field); result.push({ id, schema }); } }); return result.sort((a, b) => a.id.localeCompare(b.id)); }, [dataViewFields, dataView.fields, combinedRuntimeMappings]); // EuiDataGrid State const dataGrid = (0, _mlDataGrid.useDataGrid)(columns); const { chartsVisible, pagination, resetPagination, setColumnCharts, setCcsWarning, setErrorMessage, setRowCountInfo, setStatus, setTableItems, sortingColumns, tableItems } = dataGrid; (0, _react.useEffect)(() => { resetPagination(); // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify([query, timeRangeMs])]); (0, _react.useEffect)(() => { if (typeof dataViewFields === 'undefined') { return; } const abortController = new AbortController(); const fetchDataGridData = async function () { setErrorMessage(''); setStatus(_mlDataGrid.INDEX_STATUS.LOADING); const sort = sortingColumns.reduce((s, column) => { s[column.id] = { order: column.direction }; return s; }, {}); const esSearchRequest = { index: indexPattern, body: { fields: ['*'], _source: false, query: (0, _common.isDefaultQuery)(query) ? defaultQuery : queryWithBaseFilterCriteria, from: pagination.pageIndex * pagination.pageSize, size: pagination.pageSize, ...(Object.keys(sort).length > 0 ? { sort } : {}), ...((0, _mlRuntimeFieldUtils.isRuntimeMappings)(combinedRuntimeMappings) ? { runtime_mappings: combinedRuntimeMappings } : {}) } }; const resp = await dataSearch(esSearchRequest, abortController.signal); if (!(0, _type_guards.isEsSearchResponse)(resp)) { setErrorMessage((0, _errors.getErrorMessage)(resp)); setStatus(_mlDataGrid.INDEX_STATUS.ERROR); return; } const isCrossClusterSearch = indexPattern.includes(':'); const isMissingFields = resp.hits.hits.every(d => typeof d.fields === 'undefined'); const docs = resp.hits.hits.map(d => { var _d$fields2; return (0, _mlDataGrid.getProcessedFields)((_d$fields2 = d.fields) !== null && _d$fields2 !== void 0 ? _d$fields2 : {}); }); setCcsWarning(isCrossClusterSearch && isMissingFields); setRowCountInfo({ rowCount: typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total.value, rowCountRelation: typeof resp.hits.total === 'number' ? 'eq' : resp.hits.total.relation }); setTableItems(docs); setStatus(_mlDataGrid.INDEX_STATUS.LOADED); }; fetchDataGridData(); return () => { abortController.abort(); }; // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps }, [indexPattern, // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify([query, pagination, sortingColumns, dataViewFields, combinedRuntimeMappings, timeRangeMs])]); (0, _react.useEffect)(() => { const fetchColumnChartsData = async function () { const allDataViewFieldNames = new Set(dataView.fields.map(f => f.name)); const columnChartsData = await api.getHistogramsForFields(indexPattern, columns.filter(cT => dataGrid.visibleColumns.includes(cT.id)).map(cT => { // If a column field name has a corresponding keyword field, // fetch the keyword field instead to be able to do aggregations. const fieldName = cT.id; return (0, _field_utils.hasKeywordDuplicate)(fieldName, allDataViewFieldNames) ? { fieldName: `${fieldName}.keyword`, type: (0, _mlDataGrid.getFieldType)(undefined) } : { fieldName, type: (0, _mlDataGrid.getFieldType)(cT.schema) }; }), (0, _common.isDefaultQuery)(query) ? defaultQuery : queryWithBaseFilterCriteria, combinedRuntimeMappings); if (!(0, _type_guards.isFieldHistogramsResponseSchema)(columnChartsData)) { (0, _mlDataGrid.showDataGridColumnChartErrorMessageToast)(columnChartsData, toastNotifications); return; } setColumnCharts( // revert field names with `.keyword` used to do aggregations to their original column name columnChartsData.map(d => ({ ...d, ...((0, _field_utils.isKeywordDuplicate)(d.id, allDataViewFieldNames) ? { id: (0, _field_utils.removeKeywordPostfix)(d.id) } : {}) }))); }; if (chartsVisible) { fetchColumnChartsData(); } // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps }, [chartsVisible, indexPattern, // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify([query, dataGrid.visibleColumns, combinedRuntimeMappings, timeRangeMs])]); const renderCellValue = (0, _mlDataGrid.useRenderCellValue)(dataView, pagination, tableItems); if (dataGrid.status === _mlDataGrid.INDEX_STATUS.LOADED && dataViewFields !== undefined && loadIndexDataStartTime.current !== undefined) { const loadIndexDataDuration = window.performance.now() - loadIndexDataStartTime.current; // Set this to undefined so reporting the metric gets triggered only once. loadIndexDataStartTime.current = undefined; (0, _ebtTools.reportPerformanceMetricEvent)(analytics, { eventName: 'transformLoadIndexPreview', duration: loadIndexDataDuration }); } return { ...dataGrid, renderCellValue }; }; exports.useIndexData = useIndexData;