Skip to content
This repository was archived by the owner on Apr 23, 2025. It is now read-only.

Commit dd9c07b

Browse files
committed
feat(web-scraping): support rendering markdown in content trackers UI
1 parent 3ddca9a commit dd9c07b

3 files changed

Lines changed: 90 additions & 25 deletions

File tree

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,60 @@
1-
import { EuiCodeBlock } from '@elastic/eui';
1+
import { EuiCodeBlock, EuiMarkdownFormat } from '@elastic/eui';
22

33
import type { WebPageContentRevision } from './web_page_data_revision';
4+
import type { WebPageTrackerHistoryMode } from './web_page_tracker_history';
45

56
export interface WebPageContentTrackerRevisionProps {
67
revision: WebPageContentRevision;
7-
showDiff?: boolean;
8+
mode: WebPageTrackerHistoryMode;
89
}
910

10-
export function WebPageContentTrackerRevision({ revision, showDiff }: WebPageContentTrackerRevisionProps) {
11+
function containHTMLTags(data: string) {
12+
try {
13+
const doc = new DOMParser().parseFromString(data, 'text/html');
14+
return Array.from(doc.body.childNodes).some((node) => node.nodeType === Node.ELEMENT_NODE);
15+
} catch {
16+
return false;
17+
}
18+
}
19+
20+
export function WebPageContentTrackerRevision({ revision, mode }: WebPageContentTrackerRevisionProps) {
1121
let dataToRender;
22+
let codeBlockType = null;
1223
try {
1324
dataToRender = JSON.parse(revision.data) as string | object;
14-
if (typeof dataToRender !== 'string') {
25+
if (typeof dataToRender === 'object' && dataToRender) {
1526
dataToRender = JSON.stringify(dataToRender, null, 2);
27+
codeBlockType = 'json';
28+
} else if (typeof dataToRender !== 'string') {
29+
dataToRender = JSON.stringify(dataToRender, null, 2);
30+
} else if (containHTMLTags(dataToRender)) {
31+
codeBlockType = 'html';
1632
}
1733
} catch {
1834
dataToRender = revision.data;
1935
}
2036

21-
return (
22-
<EuiCodeBlock fontSize={'m'} language={showDiff && dataToRender.startsWith('@@') ? 'diff' : 'json'} isCopyable>
23-
{dataToRender}
24-
</EuiCodeBlock>
25-
);
37+
if (mode === 'source' || (mode === 'default' && codeBlockType)) {
38+
return (
39+
<EuiCodeBlock
40+
fontSize="m"
41+
transparentBackground
42+
isCopyable
43+
paddingSize={'none'}
44+
language={codeBlockType ?? undefined}
45+
>
46+
{dataToRender}
47+
</EuiCodeBlock>
48+
);
49+
}
50+
51+
if (mode === 'diff' && dataToRender.startsWith('@@')) {
52+
return (
53+
<EuiCodeBlock fontSize="m" transparentBackground isCopyable paddingSize={'s'} language={'diff'}>
54+
{dataToRender}
55+
</EuiCodeBlock>
56+
);
57+
}
58+
59+
return <EuiMarkdownFormat textSize={'s'}>{dataToRender}</EuiMarkdownFormat>;
2660
}

src/pages/workspace/utils/web_scraping/web_page_content_trackers.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ export default function WebPageContentTrackers() {
154154
} else {
155155
itemIdToExpandedRowMapValues[tracker.name] = (
156156
<WebPageTrackerHistory kind={'content'} tracker={tracker}>
157-
{(revision, showDiff) => (
158-
<WebPageContentTrackerRevision revision={revision as WebPageContentRevision} showDiff={showDiff} />
157+
{(revision, mode) => (
158+
<WebPageContentTrackerRevision revision={revision as WebPageContentRevision} mode={mode} />
159159
)}
160160
</WebPageTrackerHistory>
161161
);

src/pages/workspace/utils/web_scraping/web_page_tracker_history.tsx

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import type { ReactNode } from 'react';
33

44
import {
55
EuiButton,
6+
EuiButtonGroup,
67
EuiConfirmModal,
78
EuiEmptyPrompt,
89
EuiFlexGroup,
910
EuiFlexItem,
1011
EuiIcon,
1112
EuiPanel,
1213
EuiSelect,
13-
EuiSwitch,
1414
} from '@elastic/eui';
1515
import { css } from '@emotion/react';
1616
import axios from 'axios';
@@ -25,20 +25,35 @@ import { useWorkspaceContext } from '../../hooks';
2525
export interface WebPageTrackerHistoryProps {
2626
tracker: WebPageTracker;
2727
kind: 'content' | 'resources';
28-
children: (revision: WebPageDataRevision, showDiff: boolean) => ReactNode;
28+
children: (revision: WebPageDataRevision, mode: WebPageTrackerHistoryMode) => ReactNode;
2929
}
3030

31+
export type WebPageTrackerHistoryMode = 'default' | 'diff' | 'source';
32+
3133
export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTrackerHistoryProps) {
3234
const { uiState, addToast } = useWorkspaceContext();
3335

34-
const [showDiff, setShowDiff] = useState<boolean>(true);
3536
const [revisions, setRevisions] = useState<AsyncData<WebPageContentRevision[], WebPageContentRevision[] | null>>({
3637
status: 'pending',
3738
state: null,
3839
});
3940
const [revisionIndex, setRevisionIndex] = useState<number | null>(null);
41+
42+
const modes = [
43+
{ id: 'default' as const, label: 'Default', isDisabled: revisions.status !== 'succeeded' },
44+
{ id: 'diff' as const, label: 'Diff', isDisabled: revisionIndex === 0 || revisions.status !== 'succeeded' },
45+
...(kind === 'content'
46+
? [{ id: 'source' as const, label: 'Source', isDisabled: revisions.status !== 'succeeded' }]
47+
: []),
48+
];
49+
const [mode, setMode] = useState<WebPageTrackerHistoryMode>('default');
50+
4051
const fetchHistory = useCallback(
41-
({ refresh }: { refresh: boolean } = { refresh: false }) => {
52+
(
53+
{ refresh, forceMode }: { refresh: boolean; forceMode?: WebPageTrackerHistoryMode } = {
54+
refresh: false,
55+
},
56+
) => {
4257
setRevisions((currentRevisions) =>
4358
currentRevisions.status === 'succeeded'
4459
? { status: 'pending', state: currentRevisions.data }
@@ -47,7 +62,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
4762
axios
4863
.post<WebPageContentRevision[]>(
4964
getApiUrl(`/api/utils/web_scraping/${kind}/${encodeURIComponent(tracker.id)}/history`),
50-
{ refresh, calculateDiff: showDiff },
65+
{ refresh, calculateDiff: (forceMode ?? mode) === 'diff' },
5166
getApiRequestConfig(),
5267
)
5368
.then(
@@ -58,6 +73,12 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
5873
if (refresh || revisionIndex === null || revisionIndex >= response.data.length) {
5974
setRevisionIndex(response.data.length > 0 ? response.data.length - 1 : null);
6075
}
76+
77+
if (response.data.length < 2) {
78+
setMode('default');
79+
} else if (forceMode && forceMode !== mode) {
80+
setMode(forceMode);
81+
}
6182
},
6283
(err: Error) => {
6384
setRevisions((currentRevisions) => ({
@@ -69,7 +90,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
6990
},
7091
);
7192
},
72-
[getApiUrl, revisionIndex, showDiff],
93+
[getApiUrl, revisionIndex, mode],
7394
);
7495

7596
useEffect(() => {
@@ -78,7 +99,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
7899
}
79100

80101
fetchHistory();
81-
}, [uiState, tracker, showDiff]);
102+
}, [uiState, tracker]);
82103

83104
const onRevisionChange = useCallback(
84105
(revisionId: string) => {
@@ -163,7 +184,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
163184
/>
164185
);
165186
} else if (revisionIndex !== null) {
166-
history = children(revisions.data[revisionIndex], showDiff);
187+
history = children(revisions.data[revisionIndex], mode);
167188
} else {
168189
const updateButton = (
169190
<EuiButton
@@ -227,13 +248,23 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
227248
/>
228249
</EuiFlexItem>
229250
<EuiFlexItem grow={false}>
230-
<EuiSwitch
231-
label="Show diff"
232-
disabled={
233-
revisions.status !== 'succeeded' || (revisions.status === 'succeeded' && revisions.data.length < 2)
251+
<EuiButtonGroup
252+
legend="View mode"
253+
options={modes}
254+
idSelected={mode}
255+
isDisabled={
256+
revisions.status !== 'succeeded' ||
257+
(kind === 'resources' && revisions.status === 'succeeded' && revisions.data.length < 2)
234258
}
235-
checked={showDiff}
236-
onChange={(e) => setShowDiff(e.target.checked)}
259+
onChange={(id) => {
260+
const newMode = id as WebPageTrackerHistoryMode;
261+
setMode(newMode);
262+
263+
const shouldFetch = (mode === 'diff' && id !== 'diff') || (mode !== 'diff' && id === 'diff');
264+
if (shouldFetch) {
265+
fetchHistory({ forceMode: newMode, refresh: false });
266+
}
267+
}}
237268
/>
238269
</EuiFlexItem>
239270
<EuiFlexItem grow={false}>

0 commit comments

Comments
 (0)