From dd9c07b1a45c4a754407c8d16e017e2db0780c9c Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Sat, 9 Dec 2023 20:31:49 +0100 Subject: [PATCH] feat(web-scraping): support rendering markdown in content trackers UI --- .../web_page_content_tracker_revision.tsx | 52 +++++++++++++--- .../web_page_content_trackers.tsx | 4 +- .../web_scraping/web_page_tracker_history.tsx | 59 ++++++++++++++----- 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/src/pages/workspace/utils/web_scraping/web_page_content_tracker_revision.tsx b/src/pages/workspace/utils/web_scraping/web_page_content_tracker_revision.tsx index 9617c60..bbb8df5 100644 --- a/src/pages/workspace/utils/web_scraping/web_page_content_tracker_revision.tsx +++ b/src/pages/workspace/utils/web_scraping/web_page_content_tracker_revision.tsx @@ -1,26 +1,60 @@ -import { EuiCodeBlock } from '@elastic/eui'; +import { EuiCodeBlock, EuiMarkdownFormat } from '@elastic/eui'; import type { WebPageContentRevision } from './web_page_data_revision'; +import type { WebPageTrackerHistoryMode } from './web_page_tracker_history'; export interface WebPageContentTrackerRevisionProps { revision: WebPageContentRevision; - showDiff?: boolean; + mode: WebPageTrackerHistoryMode; } -export function WebPageContentTrackerRevision({ revision, showDiff }: WebPageContentTrackerRevisionProps) { +function containHTMLTags(data: string) { + try { + const doc = new DOMParser().parseFromString(data, 'text/html'); + return Array.from(doc.body.childNodes).some((node) => node.nodeType === Node.ELEMENT_NODE); + } catch { + return false; + } +} + +export function WebPageContentTrackerRevision({ revision, mode }: WebPageContentTrackerRevisionProps) { let dataToRender; + let codeBlockType = null; try { dataToRender = JSON.parse(revision.data) as string | object; - if (typeof dataToRender !== 'string') { + if (typeof dataToRender === 'object' && dataToRender) { dataToRender = JSON.stringify(dataToRender, null, 2); + codeBlockType = 'json'; + } else if (typeof dataToRender !== 'string') { + dataToRender = JSON.stringify(dataToRender, null, 2); + } else if (containHTMLTags(dataToRender)) { + codeBlockType = 'html'; } } catch { dataToRender = revision.data; } - return ( - - {dataToRender} - - ); + if (mode === 'source' || (mode === 'default' && codeBlockType)) { + return ( + + {dataToRender} + + ); + } + + if (mode === 'diff' && dataToRender.startsWith('@@')) { + return ( + + {dataToRender} + + ); + } + + return {dataToRender}; } diff --git a/src/pages/workspace/utils/web_scraping/web_page_content_trackers.tsx b/src/pages/workspace/utils/web_scraping/web_page_content_trackers.tsx index 438d191..ca1e039 100644 --- a/src/pages/workspace/utils/web_scraping/web_page_content_trackers.tsx +++ b/src/pages/workspace/utils/web_scraping/web_page_content_trackers.tsx @@ -154,8 +154,8 @@ export default function WebPageContentTrackers() { } else { itemIdToExpandedRowMapValues[tracker.name] = ( - {(revision, showDiff) => ( - + {(revision, mode) => ( + )} ); diff --git a/src/pages/workspace/utils/web_scraping/web_page_tracker_history.tsx b/src/pages/workspace/utils/web_scraping/web_page_tracker_history.tsx index f13c02d..c4b6ea8 100644 --- a/src/pages/workspace/utils/web_scraping/web_page_tracker_history.tsx +++ b/src/pages/workspace/utils/web_scraping/web_page_tracker_history.tsx @@ -3,6 +3,7 @@ import type { ReactNode } from 'react'; import { EuiButton, + EuiButtonGroup, EuiConfirmModal, EuiEmptyPrompt, EuiFlexGroup, @@ -10,7 +11,6 @@ import { EuiIcon, EuiPanel, EuiSelect, - EuiSwitch, } from '@elastic/eui'; import { css } from '@emotion/react'; import axios from 'axios'; @@ -25,20 +25,35 @@ import { useWorkspaceContext } from '../../hooks'; export interface WebPageTrackerHistoryProps { tracker: WebPageTracker; kind: 'content' | 'resources'; - children: (revision: WebPageDataRevision, showDiff: boolean) => ReactNode; + children: (revision: WebPageDataRevision, mode: WebPageTrackerHistoryMode) => ReactNode; } +export type WebPageTrackerHistoryMode = 'default' | 'diff' | 'source'; + export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTrackerHistoryProps) { const { uiState, addToast } = useWorkspaceContext(); - const [showDiff, setShowDiff] = useState(true); const [revisions, setRevisions] = useState>({ status: 'pending', state: null, }); const [revisionIndex, setRevisionIndex] = useState(null); + + const modes = [ + { id: 'default' as const, label: 'Default', isDisabled: revisions.status !== 'succeeded' }, + { id: 'diff' as const, label: 'Diff', isDisabled: revisionIndex === 0 || revisions.status !== 'succeeded' }, + ...(kind === 'content' + ? [{ id: 'source' as const, label: 'Source', isDisabled: revisions.status !== 'succeeded' }] + : []), + ]; + const [mode, setMode] = useState('default'); + const fetchHistory = useCallback( - ({ refresh }: { refresh: boolean } = { refresh: false }) => { + ( + { refresh, forceMode }: { refresh: boolean; forceMode?: WebPageTrackerHistoryMode } = { + refresh: false, + }, + ) => { setRevisions((currentRevisions) => currentRevisions.status === 'succeeded' ? { status: 'pending', state: currentRevisions.data } @@ -47,7 +62,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke axios .post( getApiUrl(`/api/utils/web_scraping/${kind}/${encodeURIComponent(tracker.id)}/history`), - { refresh, calculateDiff: showDiff }, + { refresh, calculateDiff: (forceMode ?? mode) === 'diff' }, getApiRequestConfig(), ) .then( @@ -58,6 +73,12 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke if (refresh || revisionIndex === null || revisionIndex >= response.data.length) { setRevisionIndex(response.data.length > 0 ? response.data.length - 1 : null); } + + if (response.data.length < 2) { + setMode('default'); + } else if (forceMode && forceMode !== mode) { + setMode(forceMode); + } }, (err: Error) => { setRevisions((currentRevisions) => ({ @@ -69,7 +90,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke }, ); }, - [getApiUrl, revisionIndex, showDiff], + [getApiUrl, revisionIndex, mode], ); useEffect(() => { @@ -78,7 +99,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke } fetchHistory(); - }, [uiState, tracker, showDiff]); + }, [uiState, tracker]); const onRevisionChange = useCallback( (revisionId: string) => { @@ -163,7 +184,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke /> ); } else if (revisionIndex !== null) { - history = children(revisions.data[revisionIndex], showDiff); + history = children(revisions.data[revisionIndex], mode); } else { const updateButton = ( - setShowDiff(e.target.checked)} + onChange={(id) => { + const newMode = id as WebPageTrackerHistoryMode; + setMode(newMode); + + const shouldFetch = (mode === 'diff' && id !== 'diff') || (mode !== 'diff' && id === 'diff'); + if (shouldFetch) { + fetchHistory({ forceMode: newMode, refresh: false }); + } + }} />