Skip to content

Commit

Permalink
administration: compare record revisions
Browse files Browse the repository at this point in the history
  • Loading branch information
zzacharo committed Jan 27, 2025
1 parent c96708a commit 394e605
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 3 deletions.
5 changes: 5 additions & 0 deletions invenio_app_rdm/administration/records/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class RecordAdminListView(AdminResourceListView):
"payload_schema": None,
"order": 1,
},
"compare": {
"text": _("Compare revisions"),
"payload_schema": None,
"order": 1,
},
}

search_config_name = "RDM_SEARCH"
Expand Down
3 changes: 3 additions & 0 deletions invenio_app_rdm/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,9 @@ def github_link_render(record):
ADMINISTRATION_DISPLAY_VERSIONS = [("invenio-app-rdm", f"v{__version__}")]
"""Show the InvenioRDM version in the administration panel."""

ADMINISTRATION_THEME_BASE_TEMPLATE = "invenio_app_rdm/administration_page.html"
"""Administration base template."""


APP_RDM_SUBCOMMUNITIES_LABEL = "Subcommunities"
"""Label for the subcommunities in the community browse page."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* // This file is part of Invenio-App-Rdm
* // Copyright (C) 2025 CERN.
* //
* // Invenio-App-Rdm is free software; you can redistribute it and/or modify it
* // under the terms of the MIT License; see LICENSE file for more details.
*/

import React, { useState } from "react";
import PropTypes from "prop-types";
import { Grid, Dropdown, Button } from "semantic-ui-react";

export const CompareRevisionsDropdown = ({
loading,
options,
onCompare,
srcRevision: srcOption,
targetRevision: targetOption,
}) => {
// Local state for selected revisions
const [srcRevision, setSrcRevision] = useState(srcOption);
const [targetRevision, setTargetRevision] = useState(targetOption);

const handleCompare = () => {
onCompare(srcRevision, targetRevision);
};

return (
<Grid>
<Grid.Column mobile={16} computer={6} tablet={16} largeScreen={6} widescreen={6}>
<label htmlFor="source-revision">From</label>
<Dropdown
id="source-revision"
loading={loading}
placeholder="Source revision..."
fluid
selection
value={srcRevision}
onChange={(e, { value }) => setSrcRevision(value)}
options={options}
scrolling
/>
</Grid.Column>
<Grid.Column mobile={16} computer={6} tablet={16} largeScreen={6} widescreen={6}>
<label htmlFor="target-revision">To</label>
<Dropdown
id="target-revision"
loading={loading}
placeholder="Target revision..."
fluid
selection
value={targetRevision}
onChange={(e, { value }) => setTargetRevision(value)}
options={options}
scrolling
/>
</Grid.Column>
<Grid.Column
verticalAlign="bottom"
mobile={16}
computer={2}
tablet={16}
largeScreen={2}
widescreen={2}
>
<Button
onClick={handleCompare}
disabled={loading || !srcRevision || !targetRevision}
>
Compare
</Button>
</Grid.Column>
</Grid>
);
};

CompareRevisionsDropdown.propTypes = {
loading: PropTypes.bool.isRequired,
options: PropTypes.array.isRequired,
onCompare: PropTypes.func.isRequired,
srcRevision: PropTypes.object,

Check warning on line 81 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/CompareRevisionsDropdown.js

View workflow job for this annotation

GitHub Actions / JS / Tests (18.x)

propType "srcRevision" is not required, but has no corresponding defaultProps declaration

Check warning on line 81 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/CompareRevisionsDropdown.js

View workflow job for this annotation

GitHub Actions / JS / Tests (20.x)

propType "srcRevision" is not required, but has no corresponding defaultProps declaration
targetRevision: PropTypes.object,

Check warning on line 82 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/CompareRevisionsDropdown.js

View workflow job for this annotation

GitHub Actions / JS / Tests (18.x)

propType "targetRevision" is not required, but has no corresponding defaultProps declaration

Check warning on line 82 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/CompareRevisionsDropdown.js

View workflow job for this annotation

GitHub Actions / JS / Tests (20.x)

propType "targetRevision" is not required, but has no corresponding defaultProps declaration
};
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ export class ImpersonateUserForm extends Component {
render() {
const { error, loading } = this.state;
const { user } = this.props;
console.log({ user });
return (
<Formik
onSubmit={this.handleSubmit}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* // This file is part of Invenio-App-Rdm
* // Copyright (C) 2025 CERN.
* //
* // Invenio-App-Rdm is free software; you can redistribute it and/or modify it
* // under the terms of the MIT License; see LICENSE file for more details.
*/

import React, { Component } from "react";
import PropTypes from "prop-types";
import { Grid, Container } from "semantic-ui-react";
import { Differ, Viewer } from "json-diff-kit";

export class RevisionsDiffViewer extends Component {
constructor(props) {
super(props);
this.differ = new Differ({
detectCircular: true,
maxDepth: null,
showModifications: true,
arrayDiffMethod: "lcs",
ignoreCase: false,
ignoreCaseForKey: false,
recursiveEqual: true,
});

this.viewerProps = {
indent: 4,
lineNumbers: true,
highlightInlineDiff: true,
inlineDiffOptions: {
mode: "word",
wordSeparator: " ",
},
hideUnchangedLines: true,
syntaxHighlight: false,
virtual: true,
};

this.state = {
currentDiff: undefined,
};
}

componentDidUpdate(prevProps) {
const { diff } = this.props;

if (diff !== prevProps.diff) {
this.computeDiff();
}
}

computeDiff = () => {
const { diff } = this.props;
const _diff = this.differ.diff(diff?.srcRevision, diff?.targetRevision);
this.setState({ currentDiff: _diff });
};

render() {
const { currentDiff } = this.state;

return currentDiff ? (
<Grid>
<Grid.Column width={16}>
<Container fluid>
<Viewer diff={currentDiff} {...this.viewerProps} />
</Container>
</Grid.Column>
</Grid>
) : null;
}
}

RevisionsDiffViewer.propTypes = {
diff: PropTypes.object,

Check warning on line 75 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/RevisionsDiffViewer.js

View workflow job for this annotation

GitHub Actions / JS / Tests (18.x)

propType "diff" is not required, but has no corresponding defaultProps declaration

Check warning on line 75 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/RevisionsDiffViewer.js

View workflow job for this annotation

GitHub Actions / JS / Tests (20.x)

propType "diff" is not required, but has no corresponding defaultProps declaration
viewerProps: PropTypes.object.isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* // This file is part of Invenio-App-Rdm
* // Copyright (C) 2025 CERN.
* //
* // Invenio-App-Rdm is free software; you can redistribute it and/or modify it
* // under the terms of the MIT License; see LICENSE file for more details.
*/
import React, { Component } from "react";
import PropTypes from "prop-types";
import { RecordModerationApi } from "./api";
import { withCancel, ErrorMessage } from "react-invenio-forms";
import { Modal, Button, Grid } from "semantic-ui-react";
import { i18next } from "@translations/invenio_app_rdm/i18next";
import { CompareRevisionsDropdown } from "../components/CompareRevisionsDropdown";
import { RevisionsDiffViewer } from "../components/RevisionsDiffViewer";

export class CompareRevisions extends Component {
constructor(props) {
super(props);

this.state = {
loading: true,
error: undefined,
allRevisions: {},
srcRevision: undefined,
targetRevision: undefined,
diff: undefined,
};
}

async fetchRevisions() {

Check warning on line 31 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/CompareRevisions.js

View workflow job for this annotation

GitHub Actions / JS / Tests (18.x)

fetchRevisions should be placed after componentWillUnmount

Check warning on line 31 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/CompareRevisions.js

View workflow job for this annotation

GitHub Actions / JS / Tests (20.x)

fetchRevisions should be placed after componentWillUnmount
const { resource } = this.props;
this.setState({ loading: true });

try {
this.cancellableAction = withCancel(RecordModerationApi.getRevisions(resource));
const response = await this.cancellableAction.promise;
const revisions = await response.data;

this.setState({
allRevisions: revisions,
targetRevision: revisions[0],
srcRevision: revisions.length > 1 ? revisions[1] : revisions[0],
loading: false,
});
} catch (error) {
if (error === "UNMOUNTED") return;
this.setState({ error: error, loading: false });
console.error(error);
}
}

componentDidMount() {
this.fetchRevisions();
}

componentWillUnmount() {
this.cancellableAction && this.cancellableAction.cancel();
}

handleModalClose = () => {
const { actionCancelCallback } = this.props;
actionCancelCallback();
};

handleCompare = (srcRevision, targetRevision) => {
this.setState({ diff: { srcRevision, targetRevision } });
};

render() {
const { error, loading, allRevisions, srcRevision, targetRevision, diff } =

Check warning on line 71 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/CompareRevisions.js

View workflow job for this annotation

GitHub Actions / JS / Tests (18.x)

'diff' is assigned a value but never used

Check warning on line 71 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/CompareRevisions.js

View workflow job for this annotation

GitHub Actions / JS / Tests (20.x)

'diff' is assigned a value but never used
this.state;
const options = Object.values(allRevisions).map((rev) => ({
key: rev.updated,
text: `${rev.updated} (${rev.revision_id})`,
value: rev,
}));

return loading ? (
<p>Loading...</p>
) : (
<>
<Modal.Content>
<CompareRevisionsDropdown
loading={loading}
options={options}
srcRevision={srcRevision}
targetRevision={targetRevision}
onCompare={this.handleCompare}
/>
{error && (
<Modal.Content>
<ErrorMessage
header={i18next.t("Unable to fetch revisions.")}
content={error}
icon="exclamation"
className="text-align-left"
negative
/>
</Modal.Content>
)}
<Modal.Content scrolling>
<RevisionsDiffViewer diff={this.state.diff} />

Check warning on line 103 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/CompareRevisions.js

View workflow job for this annotation

GitHub Actions / JS / Tests (18.x)

Must use destructuring state assignment

Check warning on line 103 in invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/CompareRevisions.js

View workflow job for this annotation

GitHub Actions / JS / Tests (20.x)

Must use destructuring state assignment
</Modal.Content>
</Modal.Content>
<Modal.Actions>
<Grid>
<Grid.Column floated="left" width={8} textAlign="left">
<Button
onClick={this.handleModalClose}
disabled={loading}
loading={loading}
aria-label={i18next.t("Cancel revision comparison")}
>
Close
</Button>
</Grid.Column>
</Grid>
</Modal.Actions>
</>
);
}
}

CompareRevisions.propTypes = {
resource: PropTypes.object.isRequired,
actionCancelCallback: PropTypes.func.isRequired,
};
Loading

0 comments on commit 394e605

Please sign in to comment.