Skip to content

Commit

Permalink
feat(web-scraping): support Show diff functionality for the web pag…
Browse files Browse the repository at this point in the history
…e content tracker
  • Loading branch information
azasypkin committed Dec 7, 2023
1 parent 9a6893d commit 22bea69
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import { EuiCodeBlock } from '@elastic/eui';
import { EuiCodeBlock, useEuiTextDiff } from '@elastic/eui';

import type { WebPageContentRevision } from './web_page_data_revision';

export interface WebPageContentTrackerRevisionProps {
revision: WebPageContentRevision;
previousRevision?: WebPageContentRevision;
showDiff?: boolean;
}

export function WebPageContentTrackerRevision({ revision }: WebPageContentTrackerRevisionProps) {
const parsedData = JSON.parse(revision.data) as string | number | object;
function getTextToRender(text: string): [string, string | undefined] {
const parsedData = JSON.parse(text) as string | object;
if (parsedData && typeof parsedData === 'object') {
return (
<EuiCodeBlock fontSize={'l'} language={'json'} isCopyable>
{JSON.stringify(parsedData, null, 2)}
</EuiCodeBlock>
);
return [JSON.stringify(parsedData, null, 2), 'json'];
}

return [parsedData, undefined];
}

export function WebPageContentTrackerRevision({
revision,
previousRevision,
showDiff,
}: WebPageContentTrackerRevisionProps) {
const [afterText, language] = getTextToRender(revision.data);
const [beforeText] = previousRevision && showDiff ? getTextToRender(previousRevision.data) : [afterText];

const [textToRender] = useEuiTextDiff({ beforeText, afterText });
return (
<EuiCodeBlock fontSize={'l'} isCopyable>
{parsedData}
<EuiCodeBlock fontSize={'l'} language={language} isCopyable>
{!showDiff || afterText === beforeText ? afterText : textToRender}
</EuiCodeBlock>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,13 @@ export default function WebPageContentTrackers() {
} else {
itemIdToExpandedRowMapValues[tracker.name] = (
<WebPageTrackerHistory kind={'content'} tracker={tracker}>
{(revision) => <WebPageContentTrackerRevision revision={revision as WebPageContentRevision} />}
{(revision, previousRevision, showDiff) => (
<WebPageContentTrackerRevision
revision={revision as WebPageContentRevision}
previousRevision={previousRevision as WebPageContentRevision | undefined}
showDiff={showDiff}
/>
)}
</WebPageTrackerHistory>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export function WebPageResourcesTrackerRevision({ revision }: WebPageResourcesTr
<EuiFlexGroup>
<EuiFlexItem>
<EuiStat
title={<b>{unix(revision.createdAt).format('LL HH:mm:ss')}</b>}
title={<b>{unix(revision.createdAt).format('ll HH:mm:ss')}</b>}
titleSize={'xs'}
description={'Last updated'}
/>
Expand Down
109 changes: 67 additions & 42 deletions src/pages/workspace/utils/web_scraping/web_page_tracker_history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiIcon,
EuiPanel,
EuiSelect,
EuiSpacer,
EuiSwitch,
} from '@elastic/eui';
import { css } from '@emotion/react';
import axios from 'axios';
import { unix } from 'moment';

Expand All @@ -25,17 +25,22 @@ import { useWorkspaceContext } from '../../hooks';
export interface WebPageTrackerHistoryProps {
tracker: WebPageTracker;
kind: 'content' | 'resources';
children: (revision: WebPageDataRevision) => ReactNode;
children: (
revision: WebPageDataRevision,
previousRevision: WebPageDataRevision | undefined,
showDiff: boolean,
) => ReactNode;
}

export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTrackerHistoryProps) {
const { uiState, addToast } = useWorkspaceContext();

const [showDiff, setShowDiff] = useState<boolean>(true);
const [revisions, setRevisions] = useState<AsyncData<WebPageContentRevision[], WebPageContentRevision[] | null>>({
status: 'pending',
state: null,
});
const [revision, setRevision] = useState<WebPageContentRevision | null>(null);
const [revisionIndex, setRevisionIndex] = useState<number | null>(null);
const fetchHistory = useCallback(
({ refresh }: { refresh: boolean } = { refresh: false }) => {
setRevisions((currentRevisions) =>
Expand All @@ -46,25 +51,29 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
axios
.post<WebPageContentRevision[]>(
getApiUrl(`/api/utils/web_scraping/${kind}/${encodeURIComponent(tracker.id)}/history`),
{ refresh, calculateDiff: true },
{ refresh, calculateDiff: showDiff },
getApiRequestConfig(),
)
.then(
(response) => {
setRevisions({ status: 'succeeded', data: response.data });
setRevision(response.data.length > 0 ? response.data[response.data.length - 1] : null);

// Reset revision index only if it's not set or doesn't exist in the new data.
if (revisionIndex === null || revisionIndex >= response.data.length) {
setRevisionIndex(response.data.length > 0 ? response.data.length - 1 : null);
}
},
(err: Error) => {
setRevisions((currentRevisions) => ({
status: 'failed',
error: getErrorMessage(err),
state: currentRevisions.state,
}));
setRevision(null);
setRevisionIndex(null);
},
);
},
[getApiUrl],
[getApiUrl, revisionIndex, showDiff],
);

useEffect(() => {
Expand All @@ -73,12 +82,12 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
}

fetchHistory();
}, [uiState, tracker]);
}, [uiState, tracker, showDiff]);

const onRevisionChange = useCallback(
(revisionId: string) => {
if (revisions.status === 'succeeded') {
setRevision(revisions.data?.find((revision) => revision.id === revisionId) ?? null);
setRevisionIndex(revisions.data?.findIndex((revision) => revision.id === revisionId) ?? null);
}
},
[revisions],
Expand Down Expand Up @@ -106,7 +115,7 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
.then(
() => {
setRevisions({ status: 'succeeded', data: [] });
setRevision(null);
setRevisionIndex(null);

addToast({
id: `success-clear-tracker-history-${tracker.name}`,
Expand Down Expand Up @@ -157,8 +166,12 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
}
/>
);
} else if (revision) {
history = children(revision);
} else if (revisionIndex !== null) {
history = children(
revisions.data[revisionIndex],
revisionIndex > 0 ? revisions.data[revisionIndex - 1] : undefined,
showDiff,
);
} else {
const updateButton = (
<EuiButton
Expand Down Expand Up @@ -203,44 +216,56 @@ export function WebPageTrackerHistory({ kind, tracker, children }: WebPageTracke
(revisions.status === 'succeeded' && revisions.data.length > 0) || (revisions.state?.length ?? 0 > 0);
const controlPanel = shouldDisplayControlPanel ? (
<EuiFlexItem>
<EuiSpacer size={'m'} />
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow>
<EuiSelect
options={revisionsToSelect.map((rev) => ({
value: rev.id,
text: unix(rev.createdAt).format('LL HH:mm:ss'),
}))}
disabled={revisions.status === 'pending'}
value={revision?.id}
onChange={(e) => onRevisionChange(e.target.value)}
/>
</EuiFormRow>
<EuiFlexGroup alignItems={'center'}>
<EuiFlexItem
css={css`
min-width: 200px;
`}
>
<EuiSelect
options={revisionsToSelect.map((rev) => ({
value: rev.id,
text: unix(rev.createdAt).format('ll HH:mm:ss'),
}))}
disabled={revisions.status === 'pending'}
value={
revisionsToSelect.length > 0 && revisionIndex !== null ? revisionsToSelect[revisionIndex].id : undefined
}
onChange={(e) => onRevisionChange(e.target.value)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSwitch
label="Show diff"
disabled={revisions.status === 'pending' || (revisions.status === 'succeeded' && revisions.data.length < 2)}
checked={showDiff}
onChange={(e) => setShowDiff(e.target.checked)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFormRow isDisabled={revisions.status === 'pending'}>
<EuiButton iconType="refresh" onClick={() => fetchHistory({ refresh: true })}>
Update
</EuiButton>
</EuiFormRow>
<EuiButton
iconType="refresh"
isDisabled={revisions.status === 'pending'}
onClick={() => fetchHistory({ refresh: true })}
>
Update
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFormRow isDisabled={revisions.status === 'pending'}>
<EuiButton
iconType="cross"
color={'danger'}
onClick={() => setClearHistoryStatus({ isModalVisible: true, isInProgress: false })}
>
Clear
</EuiButton>
</EuiFormRow>
<EuiButton
iconType="cross"
color={'danger'}
isDisabled={revisions.status === 'pending'}
onClick={() => setClearHistoryStatus({ isModalVisible: true, isInProgress: false })}
>
Clear
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
) : null;
return (
<EuiFlexGroup direction={'column'} style={{ height: '100%' }}>
<EuiFlexGroup direction={'column'} style={{ height: '100%' }} gutterSize={'s'}>
{controlPanel}
<EuiFlexItem>
<EuiPanel hasShadow={false} hasBorder={true}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
EuiFormRow,
EuiIcon,
EuiPanel,
EuiSpacer,
} from '@elastic/eui';
import axios from 'axios';
import { unix } from 'moment';
Expand Down Expand Up @@ -280,7 +279,6 @@ export function ResponderRequestsTable({ responder }: ResponderRequestsTableProp
const shouldDisplayControlPanel = requests.status === 'succeeded' && requests.data.length > 0;
const controlPanel = shouldDisplayControlPanel ? (
<EuiFlexItem>
<EuiSpacer size={'m'} />
<EuiFlexGroup justifyContent={'flexEnd'}>
<EuiFlexItem grow={false}>
<EuiFormRow>
Expand All @@ -298,7 +296,7 @@ export function ResponderRequestsTable({ responder }: ResponderRequestsTableProp
) : null;

return (
<EuiFlexGroup direction={'column'} style={{ height: '100%' }}>
<EuiFlexGroup direction={'column'} style={{ height: '100%' }} gutterSize={'s'}>
{controlPanel}
<EuiFlexItem>
<EuiPanel hasShadow={false} hasBorder={true}>
Expand Down

0 comments on commit 22bea69

Please sign in to comment.