Skip to content

Commit 8553d77

Browse files
authored
Merge pull request #11237 from IQSS/11198-list-file-versions-api
List File Versions API
2 parents bd24a24 + 7db967f commit 8553d77

File tree

7 files changed

+455
-112
lines changed

7 files changed

+455
-112
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Feature: New API for SPA to get the file versions metadata to populate the File Page Version Tab
2+
3+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
4+
export SERVER_URL=https://demo.dataverse.org
5+
export ID=19
6+
7+
ID can be either a DataFileID or ":persistentId" with a query parameter ?persistentId=doi:10.5072/FK2/ADMYJF
8+
9+
``curl -H "X-Dataverse-key: $API_TOKEN" -X GET "$SERVER_URL/api/files/${ID}/versionDifferences"``
10+
``curl -H "X-Dataverse-key: $API_TOKEN" -X GET "$SERVER_URL/api/files/:persistentId/versionDifferences?persistentId=${ID}"``

doc/sphinx-guides/source/api/native-api.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3936,7 +3936,29 @@ The fully expanded example above (without environment variables) looks like this
39363936
39373937
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/files/:persistentId/versions/:draft?persistentId=doi:10.5072/FK2/J8SJZB&returnOwners=true"
39383938
3939+
Get JSON Representation of a file's versions
3940+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3941+
Gets a list of versions of a data file showing any changes that affected the file with each version.
3942+
The fileIdOrPersistentId can be either "persistentId": "doi:10.5072/FK2/ADMYJF" or "datafileId": 19.
3943+
3944+
Usage example:
3945+
3946+
.. code-block:: bash
3947+
3948+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3949+
export SERVER_URL=https://demo.dataverse.org
3950+
export ID=1234
3951+
export PERSISTENT_ID=doi:10.5072/FK2/J8SJZB
3952+
3953+
curl -H "X-Dataverse-key: $API_TOKEN" -X GET "$SERVER_URL/api/files/$ID/versionDifferences"
3954+
curl -H "X-Dataverse-key: $API_TOKEN" -X GET "$SERVER_URL/api/files/:persistentId/versionDifferences?persistentId=$PERSISTENT_ID"
3955+
3956+
The fully expanded example above (without environment variables) looks like this:
3957+
3958+
.. code-block:: bash
39393959
3960+
curl -X GET "https://demo.dataverse.org/api/files/1234/versionDifferences"
3961+
curl -X GET "https://demo.dataverse.org/api/files/:persistentId/versionDifferences?persistentId=doi:10.5072/FK2/J8SJZB"
39403962
39413963
Adding Files
39423964
~~~~~~~~~~~~
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
package edu.harvard.iq.dataverse;
2+
3+
import edu.harvard.iq.dataverse.authorization.Permission;
4+
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
5+
import edu.harvard.iq.dataverse.util.StringUtil;
6+
import jakarta.ejb.EJB;
7+
import jakarta.ejb.Stateless;
8+
import jakarta.json.*;
9+
10+
import java.util.*;
11+
import java.util.logging.Logger;
12+
import java.util.stream.Collectors;
13+
14+
import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;
15+
16+
@Stateless
17+
public class FileMetadataVersionsHelper {
18+
private static final Logger logger = Logger.getLogger(FileMetadataVersionsHelper.class.getCanonicalName());
19+
20+
@EJB
21+
DataFileServiceBean datafileService;
22+
@EJB
23+
DatasetVersionServiceBean datasetVersionService;
24+
@EJB
25+
PermissionServiceBean permissionService;
26+
27+
// Groups that are single element groups and therefore not arrays.
28+
private static final List<String> SINGLE_ELEMENT_GROUPS = List.of("File Access");
29+
30+
public List<FileMetadata> loadFileVersionList(DataverseRequest req, FileMetadata fileMetadata) {
31+
List<DataFile> allfiles = allRelatedFiles(fileMetadata);
32+
List<FileMetadata> retList = new ArrayList<>();
33+
boolean hasPermission = permissionService.requestOn(req, fileMetadata.getDatasetVersion().getDataset()).has(Permission.ViewUnpublishedDataset);
34+
for (DatasetVersion versionLoop : fileMetadata.getDatasetVersion().getDataset().getVersions()) {
35+
boolean foundFmd = false;
36+
if (versionLoop.isReleased() || versionLoop.isDeaccessioned() || hasPermission) {
37+
foundFmd = false;
38+
//TODO: Improve this code to get the FileMetadata directly from the list of allfiles without the need to double loop
39+
for (DataFile df : allfiles) {
40+
FileMetadata fmd = datafileService.findFileMetadataByDatasetVersionIdAndDataFileId(versionLoop.getId(), df.getId());
41+
if (fmd != null) {
42+
fmd.setContributorNames(datasetVersionService.getContributorsNames(versionLoop));
43+
FileVersionDifference fvd = new FileVersionDifference(fmd, getPreviousFileMetadata(fileMetadata, fmd), true);
44+
fmd.setFileVersionDifference(fvd);
45+
retList.add(fmd);
46+
foundFmd = true;
47+
break;
48+
}
49+
}
50+
// no File metadata found make dummy one
51+
if (!foundFmd) {
52+
FileMetadata dummy = new FileMetadata();
53+
dummy.setDatasetVersion(versionLoop);
54+
dummy.setDataFile(null);
55+
FileVersionDifference fvd = new FileVersionDifference(dummy, getPreviousFileMetadata(fileMetadata, versionLoop), true);
56+
dummy.setFileVersionDifference(fvd);
57+
retList.add(dummy);
58+
}
59+
}
60+
}
61+
return retList;
62+
}
63+
64+
public JsonObjectBuilder jsonDataFileVersions(FileMetadata fileMetadata) {
65+
JsonObjectBuilder job = jsonObjectBuilder();
66+
if (fileMetadata.getDatasetVersion() != null) {
67+
job.add("datasetVersion", fileMetadata.getDatasetVersion().getFriendlyVersionNumber());
68+
if (fileMetadata.getDatasetVersion().getVersionNumber() != null) {
69+
job
70+
.add("versionNumber", fileMetadata.getDatasetVersion().getVersionNumber())
71+
.add("versionMinorNumber", fileMetadata.getDatasetVersion().getMinorVersionNumber());
72+
}
73+
74+
job
75+
.add("isDraft", fileMetadata.getDatasetVersion().isDraft())
76+
.add("isReleased", fileMetadata.getDatasetVersion().isReleased())
77+
.add("isDeaccessioned", fileMetadata.getDatasetVersion().isDeaccessioned())
78+
.add("versionState", fileMetadata.getDatasetVersion().getVersionState().name())
79+
.add("summary", fileMetadata.getDatasetVersion().getVersionNote())
80+
.add("contributors", fileMetadata.getContributorNames())
81+
;
82+
}
83+
if (fileMetadata.getDataFile() != null) {
84+
job.add("datafileId", fileMetadata.getDataFile().getId());
85+
job.add("persistentId", (fileMetadata.getDataFile().getGlobalId() != null ? fileMetadata.getDataFile().getGlobalId().asString() : ""));
86+
if (fileMetadata.getDataFile().getPublicationDate() != null) {
87+
job.add("publishedDate", fileMetadata.getDataFile().getPublicationDate().toString());
88+
}
89+
}
90+
FileVersionDifference fvd = fileMetadata.getFileVersionDifference();
91+
if (fvd != null) {
92+
List<FileVersionDifference.FileDifferenceSummaryGroup> groups = fvd.getDifferenceSummaryGroups();
93+
JsonObjectBuilder fileDifferenceSummary = jsonObjectBuilder();
94+
95+
if (fileMetadata.getDatasetVersion().isDeaccessioned() && fileMetadata.getDatasetVersion().getVersionNote() != null) {
96+
fileDifferenceSummary.add("deaccessionedReason", fileMetadata.getDatasetVersion().getVersionNote());
97+
}
98+
String fileAction = getFileAction(fvd.getOriginalFileMetadata(), fvd.getNewFileMetadata());
99+
if (fileAction != null) {
100+
fileDifferenceSummary.add("file", fileAction);
101+
}
102+
103+
if (groups != null && !groups.isEmpty()) {
104+
List<FileVersionDifference.FileDifferenceSummaryGroup> sortedGroups = groups.stream()
105+
.sorted(Comparator.comparing(FileVersionDifference.FileDifferenceSummaryGroup::getName))
106+
.collect(Collectors.toList());
107+
String groupName = null;
108+
final JsonArrayBuilder groupsArrayBuilder = Json.createArrayBuilder();
109+
final JsonObjectBuilder groupsObjectBuilder = jsonObjectBuilder();
110+
Map<String, Integer> itemCounts = new HashMap<>();
111+
112+
for (FileVersionDifference.FileDifferenceSummaryGroup group : sortedGroups) {
113+
if (!StringUtil.isEmpty(group.getName())) {
114+
// if the group name changed then add its data to the fileDifferenceSummary and reset list for next group
115+
if (groupName != null && groupName.compareTo(group.getName()) != 0) {
116+
addJsonGroupObject(fileDifferenceSummary, groupName, groupsObjectBuilder.build(), groupsArrayBuilder.build(), itemCounts);
117+
// Note: groupsArrayBuilder.build() also clears the data within it
118+
itemCounts.clear();
119+
}
120+
groupName = group.getName();
121+
122+
group.getFileDifferenceSummaryItems().forEach(item -> {
123+
JsonObjectBuilder itemObjectBuilder = jsonObjectBuilder();
124+
if (item.getName().isEmpty()) {
125+
// 'groupName': {'Added'=#, 'Changed'=#, ...}
126+
// accumulate the counts since we can't make a separate array item
127+
itemCounts.merge("Added", item.getAdded(), Integer::sum);
128+
itemCounts.merge("Changed", item.getChanged(), Integer::sum);
129+
itemCounts.merge("Deleted", item.getDeleted(), Integer::sum);
130+
itemCounts.merge("Replaced", item.getReplaced(), Integer::sum);
131+
} else if (SINGLE_ELEMENT_GROUPS.contains(group.getName())) {
132+
// 'groupName': 'getNameValue'
133+
groupsObjectBuilder.add(group.getName(), group.getFileDifferenceSummaryItems().get(0).getName());
134+
} else {
135+
// 'groupName': [{name='', action=''}, {name='', action=''}]
136+
String action = item.getAdded() > 0 ? "Added" : item.getChanged() > 0 ? "Changed" :
137+
item.getDeleted() > 0 ? "Deleted" : item.getReplaced() > 0 ? "Replaced" : "";
138+
itemObjectBuilder.add("name", item.getName());
139+
if (!action.isEmpty()) {
140+
itemObjectBuilder.add("action", action);
141+
}
142+
groupsArrayBuilder.add(itemObjectBuilder.build());
143+
}
144+
});
145+
}
146+
}
147+
// process last group
148+
addJsonGroupObject(fileDifferenceSummary, groupName, groupsObjectBuilder.build(), groupsArrayBuilder.build(), itemCounts);
149+
}
150+
JsonObject fileDifferenceSummaryObject = fileDifferenceSummary.build();
151+
if (!fileDifferenceSummaryObject.isEmpty()) {
152+
job.add("fileDifferenceSummary", fileDifferenceSummaryObject);
153+
}
154+
}
155+
return job;
156+
}
157+
158+
//TODO: this could use some refactoring to cut down on the number of for loops!
159+
private FileMetadata getPreviousFileMetadata(FileMetadata fileMetadata, FileMetadata fmdIn){
160+
161+
DataFile dfPrevious = datafileService.findPreviousFile(fmdIn.getDataFile());
162+
DatasetVersion dvPrevious = null;
163+
boolean gotCurrent = false;
164+
for (DatasetVersion dvloop: fileMetadata.getDatasetVersion().getDataset().getVersions()) {
165+
if(gotCurrent){
166+
dvPrevious = dvloop;
167+
break;
168+
}
169+
if(dvloop.equals(fmdIn.getDatasetVersion())){
170+
gotCurrent = true;
171+
}
172+
}
173+
174+
List<DataFile> allfiles = allRelatedFiles(fileMetadata);
175+
176+
if (dvPrevious != null && dvPrevious.getFileMetadatasSorted() != null) {
177+
for (FileMetadata fmdTest : dvPrevious.getFileMetadatasSorted()) {
178+
for (DataFile fileTest : allfiles) {
179+
if (fmdTest.getDataFile().equals(fileTest)) {
180+
return fmdTest;
181+
}
182+
}
183+
}
184+
}
185+
186+
Long dfId = fmdIn.getDataFile().getId();
187+
if (dfPrevious != null){
188+
dfId = dfPrevious.getId();
189+
}
190+
Long versionId = null;
191+
if (dvPrevious !=null){
192+
versionId = dvPrevious.getId();
193+
}
194+
195+
FileMetadata fmd = datafileService.findFileMetadataByDatasetVersionIdAndDataFileId(versionId, dfId);
196+
197+
return fmd;
198+
}
199+
200+
//TODO: this could use some refactoring to cut down on the number of for loops!
201+
private FileMetadata getPreviousFileMetadata(FileMetadata fileMetadata, DatasetVersion currentversion) {
202+
List<DataFile> allfiles = allRelatedFiles(fileMetadata);
203+
boolean foundCurrent = false;
204+
DatasetVersion priorVersion = null;
205+
for (DatasetVersion versionLoop : fileMetadata.getDatasetVersion().getDataset().getVersions()) {
206+
if (foundCurrent) {
207+
priorVersion = versionLoop;
208+
break;
209+
}
210+
if (versionLoop.equals(currentversion)) {
211+
foundCurrent = true;
212+
}
213+
214+
}
215+
if (priorVersion != null && priorVersion.getFileMetadatasSorted() != null) {
216+
for (FileMetadata fmdTest : priorVersion.getFileMetadatasSorted()) {
217+
for (DataFile fileTest : allfiles) {
218+
if (fmdTest.getDataFile().equals(fileTest)) {
219+
return fmdTest;
220+
}
221+
}
222+
}
223+
}
224+
return null;
225+
}
226+
private List<DataFile> allRelatedFiles(FileMetadata fileMetadata) {
227+
List<DataFile> dataFiles = new ArrayList<>();
228+
DataFile dataFileToTest = fileMetadata.getDataFile();
229+
Long rootDataFileId = dataFileToTest.getRootDataFileId();
230+
if (rootDataFileId < 0) {
231+
dataFiles.add(dataFileToTest);
232+
} else {
233+
dataFiles.addAll(datafileService.findAllRelatedByRootDatafileId(rootDataFileId));
234+
}
235+
return dataFiles;
236+
}
237+
238+
private String getFileAction(FileMetadata originalFileMetadata, FileMetadata newFileMetadata) {
239+
if (newFileMetadata.getDataFile() != null && originalFileMetadata == null) {
240+
return "Added";
241+
} else if (newFileMetadata.getDataFile() == null && originalFileMetadata != null) {
242+
return "Deleted";
243+
} else if (originalFileMetadata != null &&
244+
newFileMetadata.getDataFile() != null && originalFileMetadata.getDataFile() != null &&!originalFileMetadata.getDataFile().equals(newFileMetadata.getDataFile())) {
245+
return "Replaced";
246+
} else {
247+
return null;
248+
}
249+
}
250+
251+
private void addJsonGroupObject(JsonObjectBuilder jsonObjectBuilder, String key, JsonObject jsonObjectValue, JsonArray jsonArrayValue, Map<String, Integer> itemCounts) {
252+
if (key != null && !key.isEmpty()) {
253+
String sanitizedKey = key.replaceAll("\\s+", "");
254+
if (itemCounts.isEmpty()) {
255+
if (jsonArrayValue.isEmpty()) {
256+
// add the object
257+
jsonObjectBuilder.add(sanitizedKey, jsonObjectValue.getValue("/"+key));
258+
} else {
259+
// add the array
260+
jsonObjectBuilder.add(sanitizedKey, jsonArrayValue);
261+
}
262+
} else {
263+
// add the accumulated totals
264+
JsonObjectBuilder accumulatedTotalsObjectBuilder = jsonObjectBuilder();
265+
itemCounts.forEach((k, v) -> {
266+
if (v != 0) {
267+
accumulatedTotalsObjectBuilder.add(k, v);
268+
}
269+
});
270+
jsonObjectBuilder.add(sanitizedKey, accumulatedTotalsObjectBuilder.build());
271+
}
272+
}
273+
}
274+
}

0 commit comments

Comments
 (0)