import { debounce } from 'lodash'; import QueryResultFilter from './QueryResultFilter'; import { JSONPath } from 'jsonpath-plus'; import React from 'react'; import classnames from 'classnames'; import iconv from 'iconv-lite'; import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common'; import { getCodeMirrorModeBasedOnContentType } from 'utils/common/codemirror'; import QueryResultPreview from './QueryResultPreview'; import StyledWrapper from './StyledWrapper'; import { useState, useMemo, useEffect } from 'react'; import { useTheme } from 'providers/Theme/index'; import { getEncoding, uuid } from 'utils/common/index'; import LargeResponseWarning from '../LargeResponseWarning'; const formatResponse = (data, dataBuffer, encoding, mode, filter) => { if (data === undefined || !dataBuffer || !mode) { return ''; } // TODO: We need a better way to get the raw response-data here instead // of using this dataBuffer param. // Also, we only need the raw response-data and content-type to show the preview. const rawData = iconv.decode( Buffer.from(dataBuffer, "base64"), iconv.encodingExists(encoding) ? encoding : "utf-8" ); if (mode.includes('json')) { try { JSON.parse(rawData); } catch (error) { // If the response content-type is JSON and it fails parsing, its an invalid JSON. // In that case, just show the response as it is in the preview. return rawData; } if (filter) { try { data = JSONPath({ path: filter, json: data }); } catch (e) { console.warn('Could not apply JSONPath filter:', e.message); } } return safeStringifyJSON(data, true); } if (mode.includes('xml')) { let parsed = safeParseXML(data, { collapseContent: true }); if (typeof parsed === 'string') { return parsed; } return safeStringifyJSON(parsed, true); } if (typeof data === 'string') { return data; } return safeStringifyJSON(data, true); }; const formatErrorMessage = (error) => { if (!error) return 'Something went wrong'; const remoteMethodError = "Error invoking remote method 'send-http-request':"; if (error?.includes(remoteMethodError)) { const parts = error.split(remoteMethodError); return parts[1]?.trim() || error; } return error; }; const QueryResult = ({ item, collection, data, dataBuffer, disableRunEventListener, headers, error }) => { const contentType = getContentType(headers); const mode = getCodeMirrorModeBasedOnContentType(contentType, data); const [filter, setFilter] = useState(null); const [showLargeResponse, setShowLargeResponse] = useState(false); const responseEncoding = getEncoding(headers); const { displayedTheme } = useTheme(); const responseSize = useMemo(() => { const response = item.response || {}; if (typeof response.size === 'number') { return response.size; } if (!dataBuffer) return 0; try { // dataBuffer is base64 encoded, so we need to calculate the actual size const buffer = Buffer.from(dataBuffer, 'base64'); return buffer.length; } catch (error) { return 0; } }, [dataBuffer, item.response]); const isLargeResponse = responseSize > 10 * 1024 * 1024; // 10 MB const formattedData = useMemo( () => { if (isLargeResponse && !showLargeResponse) { return ''; } return formatResponse(data, dataBuffer, responseEncoding, mode, filter); }, [data, dataBuffer, responseEncoding, mode, filter, isLargeResponse, showLargeResponse] ); const debouncedResultFilterOnChange = debounce((e) => { setFilter(e.target.value); }, 250); const allowedPreviewModes = useMemo(() => { // Always show raw const allowedPreviewModes = [{ mode: 'raw', name: 'Raw', uid: uuid() }]; if (!mode || !contentType) return allowedPreviewModes; if (mode?.includes('html') && typeof data === 'string') { allowedPreviewModes.unshift({ mode: 'preview-web', name: 'Web', uid: uuid() }); } else if (mode.includes('image')) { allowedPreviewModes.unshift({ mode: 'preview-image', name: 'Image', uid: uuid() }); } else if (contentType.includes('pdf')) { allowedPreviewModes.unshift({ mode: 'preview-pdf', name: 'PDF', uid: uuid() }); } else if (contentType.includes('audio')) { allowedPreviewModes.unshift({ mode: 'preview-audio', name: 'Audio', uid: uuid() }); } else if (contentType.includes('video')) { allowedPreviewModes.unshift({ mode: 'preview-video', name: 'Video', uid: uuid() }); } return allowedPreviewModes; }, [mode, data, formattedData]); const [previewTab, setPreviewTab] = useState(allowedPreviewModes[0]); // Ensure the active Tab is always allowed useEffect(() => { if (!allowedPreviewModes.find((previewMode) => previewMode?.uid == previewTab?.uid)) { setPreviewTab(allowedPreviewModes[0]); } }, [previewTab, allowedPreviewModes]); const tabs = useMemo(() => { if (allowedPreviewModes.length === 1) { return null; } return allowedPreviewModes.map((previewMode) => (