Skip to content

Commit d31ca94

Browse files
authored
Merge pull request #3754 from ControlSystemStudio/CSSTUDIO-3682
Support add/remove attachments when editing Olog entry
2 parents fe77a7d + 04e023c commit d31ca94

8 files changed

Lines changed: 114 additions & 69 deletions

File tree

app/logbook/olog/client-es/src/main/java/org/phoebus/olog/es/api/OlogHttpClient.java

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.phoebus.util.http.HttpRequestMultipartBody;
3333
import org.phoebus.util.http.QueryParamsHelper;
3434

35+
import javax.ws.rs.HttpMethod;
3536
import javax.ws.rs.core.MultivaluedHashMap;
3637
import javax.ws.rs.core.MultivaluedMap;
3738
import java.io.InputStream;
@@ -158,7 +159,7 @@ private OlogHttpClient(String userName, String password) {
158159

159160
@Override
160161
public LogEntry set(LogEntry log) throws LogbookException {
161-
return save(log, null);
162+
return save(log, null, HttpMethod.PUT);
162163
}
163164

164165
/**
@@ -171,7 +172,7 @@ public LogEntry set(LogEntry log) throws LogbookException {
171172
* @throws LogbookException E.g. due to invalid log entry data, or if attachment content type
172173
* cannot be determined.
173174
*/
174-
private LogEntry save(LogEntry log, LogEntry inReplyTo) throws LogbookException {
175+
private LogEntry save(LogEntry log, LogEntry inReplyTo, String method) throws LogbookException {
175176
try {
176177
javax.ws.rs.core.MultivaluedMap<String, String> queryParams = new javax.ws.rs.core.MultivaluedHashMap<>();
177178
queryParams.putSingle("markup", "commonmark");
@@ -183,24 +184,29 @@ private LogEntry save(LogEntry log, LogEntry inReplyTo) throws LogbookException
183184
httpRequestMultipartBody.addTextPart("logEntry", OlogObjectMappers.logEntrySerializer.writeValueAsString(log), "application/json");
184185

185186
for (Attachment attachment : log.getAttachments()) {
186-
httpRequestMultipartBody.addFilePart(attachment.getFile(), attachment.getUniqueFilename());
187+
if(attachment.getFile() != null){
188+
httpRequestMultipartBody.addFilePart(attachment.getFile(), attachment.getUniqueFilename());
189+
}
187190
}
188191

192+
HttpRequest.BodyPublisher bodyPublisher = HttpRequest.BodyPublishers.ofByteArray(httpRequestMultipartBody.getBytes());
193+
189194
HttpRequest request = HttpRequest.newBuilder()
190195
.uri(URI.create(Preferences.olog_url + OLOG_PREFIX + "/logs/multipart?" + QueryParamsHelper.mapToQueryParams(queryParams)))
191196
.header("Content-Type", httpRequestMultipartBody.getContentType())
192197
.header(OLOG_CLIENT_INFO_HEADER, CLIENT_INFO)
193198
.header("Authorization", basicAuthenticationHeader)
194-
.PUT(HttpRequest.BodyPublishers.ofByteArray(httpRequestMultipartBody.getBytes()))
199+
.method(method, bodyPublisher)
200+
//.PUT(HttpRequest.BodyPublishers.ofByteArray(httpRequestMultipartBody.getBytes()))
195201
.build();
196202

197203
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
198204
if(response.statusCode() == 401){
199-
LOGGER.log(Level.SEVERE, "Failed to create log entry: user not authenticated");
205+
LOGGER.log(Level.SEVERE, "Failed to create or update log entry: user not authenticated");
200206
throw new LogbookException(Messages.SubmissionFailedInvalidCredentials);
201207
}
202208
else if (response.statusCode() >= 300) {
203-
LOGGER.log(Level.SEVERE, "Failed to create log entry: " + response.body());
209+
LOGGER.log(Level.SEVERE, "Failed to create or update log entry: " + response.body());
204210
throw new LogbookException(response.body());
205211
} else {
206212
return OlogObjectMappers.logEntryDeserializer.readValue(response.body(), OlogLog.class);
@@ -213,7 +219,7 @@ else if (response.statusCode() >= 300) {
213219

214220
@Override
215221
public LogEntry reply(LogEntry log, LogEntry inReplyTo) throws LogbookException {
216-
return save(log, inReplyTo);
222+
return save(log, inReplyTo, HttpMethod.PUT);
217223
}
218224

219225
/**
@@ -305,32 +311,21 @@ public String getServiceUrl() {
305311
}
306312

307313
/**
308-
* Updates an existing {@link LogEntry}. Note that unlike the {@link #save(LogEntry, LogEntry)} API,
309-
* this does not support attachments, i.e. it does not set up a multipart request to the service.
314+
* Updates an existing {@link LogEntry}.
315+
* <p>NOTE:</p>
316+
* The list of attachments in the {@link LogEntry} may contain additional attachments added by the user. Moreover,
317+
* attachments listed in the {@link LogEntry} subject to change, but not listed in the {@link LogEntry} submitted
318+
* to the service, will be removed. This makes it possible for a user to update an entry such that a
319+
* potentially erroneous attachment is replaced by a correct one.
310320
*
311321
* @param logEntry - the updated log entry
312322
* @return The updated {@link LogEntry}
313323
*/
314324
@Override
315-
public LogEntry update(LogEntry logEntry) {
316-
317-
try {
318-
HttpRequest request = HttpRequest.newBuilder()
319-
.uri(URI.create(Preferences.olog_url + OLOG_PREFIX + "/logs/" + logEntry.getId() + "?markup=commonmark"))
320-
.header("Content-Type", CONTENT_TYPE_JSON)
321-
.header("Authorization", basicAuthenticationHeader)
322-
.POST(HttpRequest.BodyPublishers.ofString(OlogObjectMappers.logEntrySerializer.writeValueAsString(logEntry)))
323-
.build();
324-
325-
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
326-
327-
LogEntry updated = OlogObjectMappers.logEntryDeserializer.readValue(response.body(), OlogLog.class);
328-
changeHandlers.forEach(h -> h.logEntryChanged(updated));
329-
return updated;
330-
} catch (Exception e) {
331-
LOGGER.log(Level.SEVERE, "Unable to update log entry id=" + logEntry.getId(), e);
332-
return null;
333-
}
325+
public LogEntry update(LogEntry logEntry) throws LogbookException{
326+
LogEntry updated = save(logEntry, null, HttpMethod.POST);
327+
changeHandlers.forEach(h -> h.logEntryChanged(updated));
328+
return updated;
334329
}
335330

336331
@Override
@@ -502,6 +497,25 @@ public InputStream getAttachment(Long logId, String attachmentName) {
502497
}
503498
}
504499

500+
@Override
501+
public InputStream getAttachment(Long logId, Attachment attachment){
502+
try {
503+
HttpRequest request = HttpRequest.newBuilder()
504+
.uri(URI.create(Preferences.olog_url + OLOG_PREFIX + "/attachment/" + attachment.getId()))
505+
.GET()
506+
.build();
507+
HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
508+
if (response.statusCode() >= 300) {
509+
LOGGER.log(Level.WARNING, "failed to obtain attachment: " + new String(response.body().readAllBytes()));
510+
return null;
511+
}
512+
return response.body();
513+
} catch (Exception e) {
514+
LOGGER.log(Level.WARNING, "failed to obtain attachment", e);
515+
return null;
516+
}
517+
}
518+
505519
/**
506520
* @param id Unique log entry id
507521
* @return A {@link SearchResult} containing a list of {@link LogEntry} objects representing the
9.42 KB
Loading
1.74 KB
Loading

app/logbook/olog/ui/doc/index.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ Please consider the following limitations of the log entry group feature:
266266

267267
.. _preferences:
268268

269+
Updating an existing log entry
270+
------------------------------
271+
272+
Existing log entries may be modified with respect to all parts of the log entry, except author and create/modify date.
273+
A right click on an item in the list view will bring up the context menu from which the user can select Edit, see below.
274+
275+
**NOTE**: when editing a log entry, the attachments view will show the attachments added when the entry was created
276+
or modified. In this view users may add further attachments, but also remove the ones added previously. Removed attachments
277+
will then be accessible only when inspecting the history of the entry.
278+
279+
.. image:: images/ContextMenuEdit.png
280+
269281
Preferences
270282
-----------
271283

app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/AttachmentsViewController.java

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@
4747
import org.phoebus.framework.spi.AppResourceDescriptor;
4848
import org.phoebus.framework.workbench.ApplicationService;
4949
import org.phoebus.logbook.Attachment;
50+
import org.phoebus.logbook.LogClient;
5051
import org.phoebus.logbook.LogEntry;
52+
import org.phoebus.logbook.LogService;
53+
import org.phoebus.logbook.LogbookException;
54+
import org.phoebus.logbook.LogbookPreferences;
55+
import org.phoebus.olog.es.api.OlogHttpClient;
5156
import org.phoebus.ui.application.ApplicationLauncherService;
5257
import org.phoebus.ui.application.PhoebusApplication;
5358
import org.phoebus.ui.dialog.ExceptionDetailsErrorDialog;
@@ -57,6 +62,7 @@
5762
import java.awt.image.BufferedImage;
5863
import java.io.File;
5964
import java.io.IOException;
65+
import java.io.InputStream;
6066
import java.net.URI;
6167
import java.nio.file.Files;
6268
import java.nio.file.StandardCopyOption;
@@ -231,6 +237,12 @@ public void invalidateAttachmentList(LogEntry logEntry) {
231237
attachments.setAll(Collections.emptyList());
232238
}
233239

240+
/**
241+
* Sets the list of {@link Attachment}s when UI is created. This list is non-empty for instance if
242+
* log entry is created from OPI (in which case the attschment is automatically added to a log entry),
243+
* or when editing an existing entry containing attachments.
244+
* @param attachmentsList List of {@link Attachment}s
245+
*/
234246
public void setAttachments(Collection<Attachment> attachmentsList) {
235247
Platform.runLater(() -> {
236248
this.attachments.setAll(attachmentsList);
@@ -278,26 +290,33 @@ private void showPreview(Attachment attachment) {
278290
* @param attachment The image {@link Attachment} selected by user.
279291
*/
280292
private void showImagePreview(Attachment attachment) {
281-
if (attachment.getFile() != null && attachment.getFile().exists()) {
282-
// Load image data off UI thread...
283-
JobManager.schedule("Show image attachment", monitor -> {
284-
try {
285-
BufferedImage bufferedImage = ImageIO.read(attachment.getFile());
286-
// BufferedImage may be null due to lazy loading strategy.
287-
if (bufferedImage == null) {
288-
return;
293+
JobManager.schedule("Show image attachment", monitor -> {
294+
try {
295+
BufferedImage bufferedImage = null;
296+
if (attachment.getFile() != null && attachment.getFile().exists()) { // Attachment exists on disk
297+
bufferedImage = ImageIO.read(attachment.getFile());
298+
}
299+
else { // Attachment must be retrieved from service
300+
LogClient logClient = LogService.getInstance().getLogFactories().get(LogbookPreferences.logbook_factory).getLogClient();
301+
InputStream inputStream = logClient.getAttachment(-1L, attachment);
302+
if (inputStream != null) {
303+
bufferedImage = ImageIO.read(inputStream);
289304
}
290-
Platform.runLater(() -> {
291-
Image image = SwingFXUtils.toFXImage(bufferedImage, null);
292-
imagePreview.visibleProperty().setValue(true);
293-
imagePreview.setImage(image);
294-
});
295-
} catch (IOException ex) {
296-
Logger.getLogger(AttachmentsViewController.class.getName())
297-
.log(Level.SEVERE, "Unable to load image file " + attachment.getFile().getAbsolutePath(), ex);
298305
}
299-
});
300-
}
306+
if (bufferedImage == null) {
307+
return;
308+
}
309+
BufferedImage _bufferedImage = bufferedImage;
310+
Platform.runLater(() -> {
311+
Image image = SwingFXUtils.toFXImage(_bufferedImage, null);
312+
imagePreview.visibleProperty().setValue(true);
313+
imagePreview.setImage(image);
314+
});
315+
} catch (Exception ex) {
316+
Logger.getLogger(AttachmentsViewController.class.getName())
317+
.log(Level.SEVERE, "Unable to load image file " + attachment.getName(), ex);
318+
}
319+
});
301320
}
302321

303322
/**

app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/OlogAttributeProvider.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,15 @@
1818

1919
package org.phoebus.logbook.olog.ui;
2020

21+
import org.apache.commons.io.FilenameUtils;
2122
import org.commonmark.ext.gfm.tables.TableBlock;
2223
import org.commonmark.renderer.html.AttributeProvider;
23-
24-
import java.util.Map;
25-
import java.util.logging.Level;
26-
import java.util.logging.Logger;
2724
import org.phoebus.logbook.Attachment;
28-
import java.util.List;
29-
import org.apache.commons.io.FilenameUtils;
3025
import org.phoebus.olog.es.api.OlogHttpClient;
3126

27+
import java.util.List;
28+
import java.util.Map;
29+
3230
/**
3331
* An {@link AttributeProvider} used to style elements of a log entry. Other types of
3432
* attribute processing may be added.
@@ -39,18 +37,19 @@ public class OlogAttributeProvider implements AttributeProvider {
3937
private boolean preview = false;
4038
private List<Attachment> attachments;
4139

42-
public OlogAttributeProvider(String serviceUrl){
40+
public OlogAttributeProvider(String serviceUrl) {
4341
this.serviceUrl = serviceUrl;
4442
}
4543

4644
/**
4745
* This is constructor for HTML preview feature
48-
* @param serviceUrl Olog service url.
49-
* @param preview A boolean flag set true for HTML preview feature parsing.
46+
*
47+
* @param serviceUrl Olog service url.
48+
* @param preview A boolean flag set true for HTML preview feature parsing.
5049
* @param attachments A list of current attachments which can be parsed to
51-
* find attachment file path from attachment id.
50+
* find attachment file path from attachment id.
5251
*/
53-
public OlogAttributeProvider(String serviceUrl, boolean preview, List<Attachment> attachments){
52+
public OlogAttributeProvider(String serviceUrl, boolean preview, List<Attachment> attachments) {
5453
this.serviceUrl = serviceUrl;
5554
this.preview = preview;
5655
this.attachments = attachments;
@@ -80,12 +79,17 @@ public void setAttributes(org.commonmark.node.Node node, String s, Map<String, S
8079
src = serviceUrl + OlogHttpClient.OLOG_PREFIX + "/" + src;
8180
}
8281
// If preview flag is true, the image url 'attachment/attachment_id'
83-
// has to be converted to 'file://attachment_path'
82+
// has to be converted to 'file://attachment_path' if on disk.
83+
// If not on disk (log entry edit case), create a regular URL to retrieve attachment.
8484
if (src.startsWith("attachment") && this.preview) {
85-
String attachmentId = src.substring(11, src.length());
86-
for (Attachment attachment: attachments) {
85+
String attachmentId = src.substring(11);
86+
for (Attachment attachment : attachments) {
8787
if (attachment.getId().equals(attachmentId)) {
88-
src = "file://" + FilenameUtils.separatorsToUnix(attachment.getFile().getAbsolutePath());
88+
if (attachment.getFile() != null) {
89+
src = "file://" + FilenameUtils.separatorsToUnix(attachment.getFile().getAbsolutePath());
90+
} else {
91+
src = serviceUrl + OlogHttpClient.OLOG_PREFIX + "/attachment/" + attachmentId;
92+
}
8993
}
9094
}
9195
}

app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/AttachmentsEditorController.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,13 @@ public void initialize() {
138138
attachments.forEach(a -> {
139139
if (a.getFile() != null) {
140140
attachedFilesSize += getFileSize(a.getFile());
141+
filesToDeleteAfterSubmit.add(a.getFile());
141142
}
142143
});
143144
}
144145

145146
attachmentsViewController.setAttachments(attachments);
146147

147-
filesToDeleteAfterSubmit.addAll(attachments.stream().map(Attachment::getFile).toList());
148-
149148
removeButton.setGraphic(ImageCache.getImageView(ImageCache.class, "/icons/delete.png"));
150149
removeButton.disableProperty().bind(Bindings.isEmpty(attachmentsViewController.getSelectedAttachments()));
151150

app/logbook/olog/ui/src/main/java/org/phoebus/logbook/olog/ui/write/LogEntryEditorController.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,6 @@ public void initialize() {
309309
templateControls.visibleProperty().setValue(editMode.equals(EditMode.NEW_LOG_ENTRY));
310310

311311
attachmentsPane.managedProperty().bind(attachmentsPane.visibleProperty());
312-
attachmentsPane.visibleProperty().setValue(editMode.equals(EditMode.NEW_LOG_ENTRY));
313312

314313
// This could be configured in the fxml, but then these UI components would not be visible
315314
// in Scene Builder.
@@ -734,13 +733,11 @@ public void submit() {
734733
ologLog.setLevel(selectedLevelProperty.get());
735734
ologLog.setLogbooks(getSelectedLogbooks());
736735
ologLog.setTags(getSelectedTags());
737-
if (editMode.equals(EditMode.NEW_LOG_ENTRY)) {
738-
ologLog.setAttachments(attachmentsEditorController.getAttachments());
739-
}
740-
else{
736+
ologLog.setAttachments(attachmentsEditorController.getAttachments());
737+
ologLog.setProperties(logPropertiesEditorController.getProperties());
738+
if (editMode.equals(EditMode.UPDATE_LOG_ENTRY)) {
741739
ologLog.setId(logEntry.getId());
742740
}
743-
ologLog.setProperties(logPropertiesEditorController.getProperties());
744741

745742
LogClient logClient =
746743
logFactory.getLogClient(new SimpleAuthenticationToken(usernameProperty.get(), passwordProperty.get()));

0 commit comments

Comments
 (0)