"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.valueTypeToSelectedType = exports.useFieldPreviewContext = exports.defaultValueFormatter = exports.FieldPreviewProvider = void 0; var _react = _interopRequireWildcard(require("react")); var _server = require("react-dom/server"); var _useDebounce = _interopRequireDefault(require("react-use/lib/useDebounce")); var _i18n = require("@kbn/i18n"); var _lodash = require("lodash"); var _rxjs = require("rxjs"); var _state_utils = require("../../state_utils"); var _runtime_field_validation = require("../../lib/runtime_field_validation"); var _field_editor_context = require("../field_editor_context"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * 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. */ const fieldPreviewContext = /*#__PURE__*/(0, _react.createContext)(undefined); const defaultParams = { name: null, index: null, script: null, document: null, type: null, format: null, parentName: null }; const defaultValueFormatter = value => { var _String; const content = typeof value === 'object' ? JSON.stringify(value) : (_String = String(value)) !== null && _String !== void 0 ? _String : '-'; return (0, _server.renderToString)( /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, content)); }; exports.defaultValueFormatter = defaultValueFormatter; const valueTypeToSelectedType = value => { const valueType = typeof value; if (valueType === 'string') return 'keyword'; if (valueType === 'number') return 'double'; if (valueType === 'boolean') return 'boolean'; return 'keyword'; }; exports.valueTypeToSelectedType = valueTypeToSelectedType; const documentsSelector = state => { const currentDocument = state.documents[state.currentIdx]; return { currentDocument, totalDocs: state.documents.length, currentDocIndex: currentDocument === null || currentDocument === void 0 ? void 0 : currentDocument._index, currentDocId: currentDocument === null || currentDocument === void 0 ? void 0 : currentDocument._id }; }; const FieldPreviewProvider = ({ controller, children }) => { const { dataView, services: { notifications, api: { getFieldPreview } }, fieldName$ } = (0, _field_editor_context.useFieldEditorContext)(); const fieldPreview$ = (0, _react.useRef)(new _rxjs.BehaviorSubject(undefined)); /** The parameters required for the Painless _execute API */ const [params, setParams] = (0, _react.useState)(defaultParams); const [scriptEditorValidation, setScriptEditorValidation] = (0, _react.useState)({ isValidating: false, isValid: true, message: null }); const { currentDocument, currentDocIndex, currentDocId } = (0, _state_utils.useStateSelector)(controller.state$, documentsSelector); const { name, document, script, format, type, parentName } = params; const updateParams = (0, _react.useCallback)(updated => { setParams(prev => ({ ...prev, ...updated })); }, []); const updatePreview = (0, _react.useCallback)(async () => { var _dataView$getRuntimeF; // don't prevent rendering if we're working with a composite subfield (has parentName) if (!parentName && scriptEditorValidation.isValidating) { return; } if (!parentName && (!controller.allParamsDefined(type, script === null || script === void 0 ? void 0 : script.source, currentDocIndex) || !controller.hasSomeParamsChanged(type, script === null || script === void 0 ? void 0 : script.source, currentDocId) || scriptEditorValidation.isValid === false)) { controller.setIsLoadingPreview(false); return; } // Not sure why this is getting called without currentDocIndex // would be much better to prevent this function from being called at all if (!currentDocIndex) { controller.setIsLoadingPreview(false); return; } controller.setLastExecutePainlessRequestParams({ type, script: script === null || script === void 0 ? void 0 : script.source, documentId: currentDocId }); const currentApiCall = controller.incrementPreviewCount(); // ++previewCount.current; const previewScript = parentName && ((_dataView$getRuntimeF = dataView.getRuntimeField(parentName)) === null || _dataView$getRuntimeF === void 0 ? void 0 : _dataView$getRuntimeF.script) || script; const response = await getFieldPreview({ index: currentDocIndex, document: document === null || document === void 0 ? void 0 : document._source, context: parentName ? 'composite_field' : `${type}_field`, script: previewScript }); if (currentApiCall !== controller.getPreviewCount()) { // Discard this response as there is another one inflight // or we have called reset() and no longer need the response. return; } const { error: serverError } = response; if (serverError) { // Server error (not an ES error) const title = _i18n.i18n.translate('indexPatternFieldEditor.fieldPreview.errorTitle', { defaultMessage: 'Failed to load field preview' }); notifications.toasts.addError(serverError, { title }); controller.setIsLoadingPreview(false); return; } if (response.data) { const { values, error } = response.data; if (error) { controller.setPreviewResponse({ fields: [{ key: name !== null && name !== void 0 ? name : '', value: '', formattedValue: defaultValueFormatter('') }], error: { code: 'PAINLESS_SCRIPT_ERROR', error: (0, _runtime_field_validation.parseEsError)(error) } }); } else { if (!Array.isArray(values)) { controller.updateCompositeFieldPreview(values, parentName, name, fieldName$.getValue(), type, format, value => fieldPreview$.current.next(value)); } else { controller.updateSingleFieldPreview(name, values, type, format); } } } controller.setInitialPreviewComplete(true); controller.setIsLoadingPreview(false); }, [name, type, script, parentName, dataView, document, currentDocId, getFieldPreview, notifications.toasts, scriptEditorValidation, currentDocIndex, controller, format, fieldName$]); const ctx = (0, _react.useMemo)(() => ({ controller, fieldPreview$: fieldPreview$.current, params: { value: params, update: updateParams }, validation: { // todo do this next setScriptEditorValidation } }), [controller, fieldPreview$, params, updateParams]); /** * In order to immediately display the "Updating..." state indicator and not have to wait * the 500ms of the debounce, we set the isFetchingDocument state in this effect whenever * "customDocIdToLoad" changes */ /** * Each time the current document changes we update the parameters * that will be sent in the _execute HTTP request. */ // This game me problems (0, _react.useEffect)(() => { updateParams({ document: currentDocument, index: currentDocument === null || currentDocument === void 0 ? void 0 : currentDocument._index }); }, [currentDocument, updateParams]); /** * Whenever the name or the format changes we immediately update the preview */ (0, _react.useEffect)(() => { const { previewResponse: prev } = controller.state$.getValue(); const { fields } = prev; let updatedFields = fields.map(field => { let key = name !== null && name !== void 0 ? name : ''; if (type === 'composite') { // restore initial key segement (the parent name), which was not returned const { 1: fieldName } = field.key.split('.'); key = `${name !== null && name !== void 0 ? name : ''}.${fieldName}`; } return { ...field, key }; }); // If the user has entered a name but not yet any script we will display // the field in the preview with just the name if (updatedFields.length === 0 && name !== null) { updatedFields = [{ key: name, value: undefined, formattedValue: undefined, type: undefined }]; } controller.setPreviewResponse({ ...prev, fields: updatedFields }); }, [name, type, parentName, controller]); /** * Whenever the format changes we immediately update the preview */ (0, _react.useEffect)(() => { const { previewResponse: prev } = controller.state$.getValue(); const { fields } = prev; controller.setPreviewResponse({ ...prev, fields: fields.map(field => { var _get; const nextValue = script === null && Boolean(document) ? (_get = (0, _lodash.get)(document === null || document === void 0 ? void 0 : document._source, name !== null && name !== void 0 ? name : '')) !== null && _get !== void 0 ? _get : (0, _lodash.get)(document === null || document === void 0 ? void 0 : document.fields, name !== null && name !== void 0 ? name : '') // When there is no script we try to read the value from _source/fields : field === null || field === void 0 ? void 0 : field.value; const formattedValue = controller.valueFormatter({ value: nextValue, type, format }); return { ...field, value: nextValue, formattedValue }; }) }); }, [name, script, document, controller, type, format]); (0, _react.useEffect)(() => { if ((script === null || script === void 0 ? void 0 : script.source) === undefined) { // Whenever the source is not defined ("Set value" is toggled off or the // script is empty) we clear the error and update the params cache. controller.setLastExecutePainlessRequestParams({ script: undefined }); controller.setPreviewError(null); } }, [script === null || script === void 0 ? void 0 : script.source, controller]); // Handle the validation state coming from the Painless DiagnosticAdapter // (see @kbn-monaco/src/painless/diagnostics_adapter.ts) (0, _react.useEffect)(() => { if (scriptEditorValidation.isValidating) { return; } if (scriptEditorValidation.isValid === false) { var _scriptEditorValidati; // Make sure to remove the "Updating..." spinner controller.setIsLoadingPreview(false); // Set preview response error so it is displayed in the flyout footer const error = (script === null || script === void 0 ? void 0 : script.source) === undefined ? null : { code: 'PAINLESS_SYNTAX_ERROR', error: { reason: (_scriptEditorValidati = scriptEditorValidation.message) !== null && _scriptEditorValidati !== void 0 ? _scriptEditorValidati : _i18n.i18n.translate('indexPatternFieldEditor.fieldPreview.error.painlessSyntax', { defaultMessage: 'Invalid Painless syntax' }) } }; controller.setPreviewError(error); // Make sure to update the lastExecutePainlessRequestParams cache so when the user updates // the script and fixes the syntax the "updatePreview()" will run // lastExecutePainlessRequestParams.current.script = script?.source; controller.setLastExecutePainlessRequestParams({ script: script === null || script === void 0 ? void 0 : script.source }); } else { // Clear possible previous syntax error controller.clearPreviewError('PAINLESS_SYNTAX_ERROR'); } }, [scriptEditorValidation, script === null || script === void 0 ? void 0 : script.source, controller]); /** * Whenever updatePreview() changes (meaning whenever a param changes) * we call it to update the preview response with the field(s) value or possible error. */ (0, _useDebounce.default)(updatePreview, 500, [updatePreview]); return /*#__PURE__*/_react.default.createElement(fieldPreviewContext.Provider, { value: ctx }, children); }; exports.FieldPreviewProvider = FieldPreviewProvider; const useFieldPreviewContext = () => { const ctx = (0, _react.useContext)(fieldPreviewContext); if (ctx === undefined) { throw new Error('useFieldPreviewContext must be used within a '); } return ctx; }; exports.useFieldPreviewContext = useFieldPreviewContext;