diff --git a/site/cds_rdm/assets/semantic-ui/js/cds_rdm/administration/harvesterReports/index.js b/site/cds_rdm/assets/semantic-ui/js/cds_rdm/administration/harvesterReports/index.js
index 7666aa20..a264bfd4 100644
--- a/site/cds_rdm/assets/semantic-ui/js/cds_rdm/administration/harvesterReports/index.js
+++ b/site/cds_rdm/assets/semantic-ui/js/cds_rdm/administration/harvesterReports/index.js
@@ -4,16 +4,159 @@
// CDS-RDM is free software; you can redistribute it and/or modify it
// under the terms of the GPL-2.0 License; see LICENSE file for more details.
+import React from "react";
import { initDefaultSearchComponents } from "@js/invenio_administration";
import { createSearchAppInit } from "@js/invenio_search_ui";
import { NotificationController } from "@js/invenio_administration";
import { SearchFacets } from "@js/invenio_administration";
import { SearchBar } from "react-searchkit";
+import { ErrorMessage, withCancel } from "react-invenio-forms";
+import { Modal, Button, Loader, Grid } from "semantic-ui-react";
import { SearchResultItemLayout } from "@js/invenio_app_rdm/administration/auditLogs/search";
import { AuditLogActions } from "@js/invenio_app_rdm/administration/auditLogs/AuditLogActions";
+import { ViewRecentChanges } from "@js/invenio_app_rdm/administration/auditLogs/ViewRecentChanges";
+import { RecordModerationApi } from "@js/invenio_app_rdm/administration/records/api";
+import { RevisionsDiffViewer } from "@js/invenio_app_rdm/administration/components/RevisionsDiffViewer";
+import { i18next } from "@translations/invenio_app_rdm/i18next";
import { HarvesterSearchBarElement } from "./SearchBar";
import { CustomEmptyResults } from "./EmptyResults";
+function formatRevisionError(error) {
+ if (!error) {
+ return null;
+ }
+ if (typeof error === "string") {
+ return error;
+ }
+ if (error.response?.status === 403) {
+ return i18next.t("You do not have access to view changes for this record.");
+ }
+ return (
+ error.response?.data?.message ||
+ error.message ||
+ i18next.t("An unexpected error occurred while fetching revisions.")
+ );
+}
+
+class HarvesterRevisionsDiffViewer extends RevisionsDiffViewer {
+ componentDidMount() {
+ this.computeDiff();
+ }
+}
+
+class HarvesterViewRecentChanges extends ViewRecentChanges {
+ async fetchPreviousRevision() {
+ const { resource } = this.props;
+ const {
+ resource: record,
+ metadata: { revision_id: targetRevision } = { revision_id: null },
+ } = resource;
+ this.setState({ loading: true });
+
+ try {
+ if (!targetRevision) {
+ this.setState({
+ error: i18next.t("No revision ID found."),
+ loading: false,
+ });
+ return;
+ }
+ this.cancellableAction = withCancel(
+ RecordModerationApi.getLastRevision(record, targetRevision, true)
+ );
+ const response = await this.cancellableAction.promise;
+ const revisions = await response.data;
+
+ this.setState({
+ diff: {
+ targetRevision: revisions[0],
+ srcRevision: revisions.length > 1 ? revisions[1] : {},
+ },
+ loading: false,
+ });
+ } catch (error) {
+ if (error === "UNMOUNTED") return;
+ this.setState({ error: formatRevisionError(error), loading: false });
+ console.error(error);
+ }
+ }
+
+ render() {
+ const { error, loading, diff } = this.state;
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ const errorMessage = formatRevisionError(error);
+ if (errorMessage) {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+}
+
+class HarvesterAuditLogActions extends AuditLogActions {
+ baseOnModalTriggerClick = this.onModalTriggerClick;
+
+ onModalTriggerClick = (e, params) => {
+ if (params.dataActionKey !== "view_changes") {
+ this.baseOnModalTriggerClick(e, params);
+ return;
+ }
+ const { resource } = this.props;
+ this.setState({
+ modalOpen: true,
+ modalHeader: i18next.t("Recent changes"),
+ modalProps: { size: "large" },
+ modalBody: (
+
+ ),
+ });
+ };
+}
+
const domContainer = document.getElementById("invenio-search-config");
if (domContainer) {
const defaultComponents = initDefaultSearchComponents(domContainer);
@@ -22,7 +165,7 @@ if (domContainer) {
...defaultComponents,
"InvenioAdministration.SearchResultItem.layout": SearchResultItemLayout,
"SearchApp.facets": SearchFacets,
- "InvenioAdministration.ResourceActions": AuditLogActions,
+ "InvenioAdministration.ResourceActions": HarvesterAuditLogActions,
"SearchBar.element": HarvesterSearchBarElement,
"EmptyResults.element": CustomEmptyResults,
"SearchApp.searchbarContainer": SearchBar,