From 3a3c072b7f0cdf6b7e8521e5c0e08c3ccc514ccd Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sun, 17 Dec 2023 16:24:39 +0100 Subject: [PATCH 1/5] #4381 - Allow users to browse their past activity - Added activity browser for project-related activities accessible from the recent activity sidebar in the project dashboard --- .../ukp/inception/log/EventRepository.java | 5 + .../inception/log/EventRepositoryImpl.java | 45 +++ .../log/model/SummarizedLoggedEvent.java | 56 +++ .../EventRepositoryImplIntegrationTest.java | 100 +++-- .../ExportServiceControllerImplTest.java | 17 +- ...ationEventWebsocketControllerImplTest.java | 24 +- .../dashboard/activity/ActivitiesDashlet.html | 14 +- .../dashboard/activity/ActivitiesDashlet.java | 30 +- .../activity/ActivitiesDashletController.java | 18 +- .../ActivitiesDashletControllerImpl.java | 211 ++++++++-- .../activity/panel/ActivityOverview.java | 46 +++ .../activity/panel/ActivityOverviewItem.java | 32 ++ .../activity/panel/ActivityPanel.html | 28 ++ .../activity/panel/ActivityPanel.java | 53 +++ .../activity/panel/ActivityPanelTestPage.html | 25 ++ .../activity/panel/ActivityPanelTestPage.java | 40 ++ .../activity/panel/ActivitySummary.java | 45 +++ .../activity/panel/ActivitySummaryItem.java | 25 ++ .../src/main/ts/build.mjs | 48 ++- .../src/main/ts/src/ActivitiesDashlet.svelte | 3 +- .../src/main/ts/src/ActivityPanel.svelte | 374 ++++++++++++++++++ .../websocket/WebSocketIntegrationTest.java | 8 +- 22 files changed, 1145 insertions(+), 102 deletions(-) create mode 100644 inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/model/SummarizedLoggedEvent.java create mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityOverview.java create mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityOverviewItem.java create mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanel.html create mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanel.java create mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.html create mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.java create mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivitySummary.java create mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivitySummaryItem.java create mode 100644 inception/inception-ui-dashboard-activity/src/main/ts/src/ActivityPanel.svelte diff --git a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventRepository.java b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventRepository.java index e51f83bc19f..cb9f571f10f 100644 --- a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventRepository.java +++ b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventRepository.java @@ -17,6 +17,7 @@ */ package de.tudarmstadt.ukp.inception.log; +import java.time.Instant; import java.util.Collection; import java.util.List; @@ -24,6 +25,7 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.log.model.LoggedEvent; +import de.tudarmstadt.ukp.inception.log.model.SummarizedLoggedEvent; public interface EventRepository { @@ -100,4 +102,7 @@ List listRecentActivity(Project aProject, String aUsername, * return this number of recent events or less */ List listRecentActivity(String aUsername, int aMaxSize); + + List summarizeEvents(String aUsername, Project aProject, Instant aNow, + Instant aMinus); } diff --git a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventRepositoryImpl.java b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventRepositoryImpl.java index cdd27b80af1..b10ca812640 100644 --- a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventRepositoryImpl.java +++ b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/EventRepositoryImpl.java @@ -18,17 +18,23 @@ package de.tudarmstadt.ukp.inception.log; import static java.lang.String.join; +import static java.time.temporal.ChronoUnit.DAYS; import java.lang.invoke.MethodHandles; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import javax.persistence.Tuple; import org.apache.commons.lang3.function.FailableConsumer; import org.apache.commons.lang3.stream.Streams; @@ -41,6 +47,8 @@ import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.inception.log.config.EventLoggingAutoConfiguration; import de.tudarmstadt.ukp.inception.log.model.LoggedEvent; +import de.tudarmstadt.ukp.inception.log.model.LoggedEvent_; +import de.tudarmstadt.ukp.inception.log.model.SummarizedLoggedEvent; /** *

@@ -213,4 +221,41 @@ public void forEachLoggedEvent(Project aProject, Streams.failableStream(eventStream).forEach(aConsumer); } } + + @Override + public List summarizeEvents(String aUsername, Project aProject, + Instant aFrom, Instant aTo) + { + var cb = entityManager.getCriteriaBuilder(); + var query = cb.createQuery(Tuple.class); + var root = query.from(LoggedEvent.class); + + query // + .multiselect( // + root.get(LoggedEvent_.created), // + root.get(LoggedEvent_.document), // + root.get(LoggedEvent_.event)) + .where( // + cb.equal(root.get(LoggedEvent_.user), aUsername), // + cb.equal(root.get(LoggedEvent_.project), aProject.getId()), // + cb.between(root.get(LoggedEvent_.created), Date.from(aFrom), + Date.from(aTo))); + + var aggregator = new HashMap(); + + entityManager.createQuery(query).getResultStream().forEach(tuple -> { + var truncDate = tuple.get(0, Date.class).toInstant().truncatedTo(DAYS); + var document = tuple.get(1, Long.class); + var event = tuple.get(2, String.class); + var key = new SummarizedLoggedEventKey(event, truncDate, document); + aggregator.computeIfAbsent(key, $ -> new AtomicLong()).addAndGet(1); + }); + + return aggregator.entrySet().stream() // + .map(e -> new SummarizedLoggedEvent(e.getKey().event(), e.getKey().document(), + e.getKey().date(), e.getValue().get())) // + .toList(); + } + + private static record SummarizedLoggedEventKey(String event, Instant date, long document) {} } diff --git a/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/model/SummarizedLoggedEvent.java b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/model/SummarizedLoggedEvent.java new file mode 100644 index 00000000000..c282a80689f --- /dev/null +++ b/inception/inception-log/src/main/java/de/tudarmstadt/ukp/inception/log/model/SummarizedLoggedEvent.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.log.model; + +import java.time.Instant; + +public class SummarizedLoggedEvent +{ + private final long document; + private final String event; + private final Instant date; + private final long count; + + public SummarizedLoggedEvent(String aEvent, long aDocument, Instant aDate, long aCount) + { + document = aDocument; + event = aEvent; + date = aDate; + count = aCount; + } + + public Instant getDate() + { + return date; + } + + public long getCount() + { + return count; + } + + public String getEvent() + { + return event; + } + + public long getDocument() + { + return document; + } +} diff --git a/inception/inception-log/src/test/java/de/tudarmstadt/ukp/inception/log/EventRepositoryImplIntegrationTest.java b/inception/inception-log/src/test/java/de/tudarmstadt/ukp/inception/log/EventRepositoryImplIntegrationTest.java index c68c11d383d..350f469849a 100644 --- a/inception/inception-log/src/test/java/de/tudarmstadt/ukp/inception/log/EventRepositoryImplIntegrationTest.java +++ b/inception/inception-log/src/test/java/de/tudarmstadt/ukp/inception/log/EventRepositoryImplIntegrationTest.java @@ -20,16 +20,18 @@ import static java.util.Calendar.HOUR_OF_DAY; import static org.apache.uima.fit.factory.TypeSystemDescriptionFactory.createTypeSystemDescription; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; import java.util.Calendar; import java.util.Date; -import java.util.List; import org.apache.uima.util.CasCreationUtils; import org.junit.jupiter.api.AfterEach; @@ -54,6 +56,7 @@ import de.tudarmstadt.ukp.inception.documents.api.RepositoryAutoConfiguration; import de.tudarmstadt.ukp.inception.documents.config.DocumentServiceAutoConfiguration; import de.tudarmstadt.ukp.inception.log.model.LoggedEvent; +import de.tudarmstadt.ukp.inception.log.model.SummarizedLoggedEvent; import de.tudarmstadt.ukp.inception.schema.api.AnnotationSchemaService; import de.tudarmstadt.ukp.inception.schema.config.AnnotationSchemaServiceAutoConfiguration; @@ -110,8 +113,8 @@ public void thatApplicationContextStarts() @Test public void getLoggedEventsForDoc_WithoutLoggedEvent_ShouldReturnEmptyList() { - List loggedEvents = sut.listUniqueLoggedEventsForDoc(project, - user.getUsername(), new String[] { EVENT_TYPE_AFTER_ANNO_EVENT }, 10); + var loggedEvents = sut.listUniqueLoggedEventsForDoc(project, user.getUsername(), + new String[] { EVENT_TYPE_AFTER_ANNO_EVENT }, 10); assertThat(loggedEvents).as("Check that no logged event is found").isEmpty(); } @@ -120,17 +123,17 @@ public void getLoggedEventsForDoc_WithoutLoggedEvent_ShouldReturnEmptyList() public void getLoggedEventsForDoc_WithStoredLoggedEvent_ShouldReturnStoredLoggedEvent() throws ParseException { - DateFormat df = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); + var df = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); le = buildLoggedEvent(project, USERNAME, EVENT_TYPE_AFTER_ANNO_EVENT, df.parse("19-04-03 10:00:00"), 1, DETAIL_JSON); - LoggedEvent excludeTypeEvent = buildLoggedEvent(project, USERNAME, + var excludeTypeEvent = buildLoggedEvent(project, USERNAME, EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, df.parse("19-04-03 11:00:00"), 1, DETAIL_JSON); - LoggedEvent includeTypeEvent = buildLoggedEvent(project, USERNAME, SPAN_CREATED_EVENT, + var includeTypeEvent = buildLoggedEvent(project, USERNAME, SPAN_CREATED_EVENT, df.parse("19-04-03 07:00:00"), 1, DETAIL_JSON); - LoggedEvent le2 = buildLoggedEvent(project, USERNAME, EVENT_TYPE_AFTER_ANNO_EVENT, + var le2 = buildLoggedEvent(project, USERNAME, EVENT_TYPE_AFTER_ANNO_EVENT, df.parse("19-04-03 9:00:00"), 1, DETAIL_JSON); - LoggedEvent le3 = buildLoggedEvent(project, USERNAME, EVENT_TYPE_AFTER_ANNO_EVENT, + var le3 = buildLoggedEvent(project, USERNAME, EVENT_TYPE_AFTER_ANNO_EVENT, df.parse("19-04-03 8:00:00"), 2, DETAIL_JSON); sut.create(le); @@ -138,8 +141,7 @@ public void getLoggedEventsForDoc_WithStoredLoggedEvent_ShouldReturnStoredLogged sut.create(excludeTypeEvent); sut.create(le2); sut.create(le3); - List loggedEvents = sut.listUniqueLoggedEventsForDoc(project, - user.getUsername(), + var loggedEvents = sut.listUniqueLoggedEventsForDoc(project, user.getUsername(), new String[] { EVENT_TYPE_AFTER_ANNO_EVENT, SPAN_CREATED_EVENT }, 5); assertThat(loggedEvents).as("Check that last created logged events are found").hasSize(2) @@ -153,8 +155,8 @@ public void getLoggedEvents_WithOneStoredLoggedEvent_ShouldReturnStoredLoggedEve new Date(), -1, DETAIL_JSON); sut.create(le); - List loggedEvents = sut.listLoggedEventsForRecommender(project, - user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); + var loggedEvents = sut.listLoggedEventsForRecommender(project, user.getUsername(), + EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); assertThat(loggedEvents).as("Check that only the previously created logged event is found") .hasSize(1).contains(le); @@ -163,8 +165,8 @@ public void getLoggedEvents_WithOneStoredLoggedEvent_ShouldReturnStoredLoggedEve @Test public void getLoggedEvents_WithoutLoggedEvent_ShouldReturnEmptyList() { - List loggedEvents = sut.listLoggedEventsForRecommender(project, - user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); + var loggedEvents = sut.listLoggedEventsForRecommender(project, user.getUsername(), + EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); assertThat(loggedEvents).as("Check that no logged event is found").isEmpty(); } @@ -177,8 +179,8 @@ public void getLoggedEvents_WithLoggedEventOfOtherUser_ShouldReturnEmptyList() sut.create(le); - List loggedEvents = sut.listLoggedEventsForRecommender(project, - user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); + var loggedEvents = sut.listLoggedEventsForRecommender(project, user.getUsername(), + EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); assertThat(loggedEvents).as("Check that no logged event is found").isEmpty(); } @@ -186,14 +188,14 @@ public void getLoggedEvents_WithLoggedEventOfOtherUser_ShouldReturnEmptyList() @Test public void getLoggedEvents_WithLoggedEventOfOtherProject_ShouldReturnEmptyList() { - Project otherProject = createProject("otherProject"); + var otherProject = createProject("otherProject"); le = buildLoggedEvent(otherProject, user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, new Date(), -1, DETAIL_JSON); sut.create(le); - List loggedEvents = sut.listLoggedEventsForRecommender(project, - user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); + var loggedEvents = sut.listLoggedEventsForRecommender(project, user.getUsername(), + EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); assertThat(loggedEvents).as("Check that no logged event is found").isEmpty(); } @@ -207,8 +209,8 @@ public void getLoggedEvents_WithLoggedEventOfOtherType_ShouldReturnEmptyList() sut.create(le); - List loggedEvents = sut.listLoggedEventsForRecommender(project, - user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); + var loggedEvents = sut.listLoggedEventsForRecommender(project, user.getUsername(), + EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); assertThat(loggedEvents).as("Check that no logged event is found").isEmpty(); } @@ -216,17 +218,17 @@ public void getLoggedEvents_WithLoggedEventOfOtherType_ShouldReturnEmptyList() @Test public void getLoggedEvents_WithLoggedEventsMoreThanGivenSize_ShouldReturnListOfGivenSize() { - for (int i = 0; i < 6; i++) { + for (var i = 0; i < 6; i++) { le = buildLoggedEvent(project, user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, new Date(), -1, DETAIL_JSON); - Calendar cal = Calendar.getInstance(); + var cal = Calendar.getInstance(); cal.set(HOUR_OF_DAY, i); le.setCreated(cal.getTime()); sut.create(le); } - List loggedEvents = sut.listLoggedEventsForRecommender(project, - user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); + var loggedEvents = sut.listLoggedEventsForRecommender(project, user.getUsername(), + EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); assertThat(loggedEvents).as("Check that the number of logged events is 5").hasSize(5); } @@ -234,7 +236,7 @@ public void getLoggedEvents_WithLoggedEventsMoreThanGivenSize_ShouldReturnListOf @Test public void getLoggedEvents_WithLoggedEventsCreatedAtDifferentTimes_ShouldReturnSortedList() { - for (int i = 0; i < 5; i++) { + for (var i = 0; i < 5; i++) { le = buildLoggedEvent(project, user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, new Date(), -1, DETAIL_JSON); @@ -244,12 +246,12 @@ public void getLoggedEvents_WithLoggedEventsCreatedAtDifferentTimes_ShouldReturn sut.create(le); } - List loggedEvents = sut.listLoggedEventsForRecommender(project, - user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); + var loggedEvents = sut.listLoggedEventsForRecommender(project, user.getUsername(), + EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, RECOMMENDER_ID); assertThat(loggedEvents).as("Check that the returned list is not empty").isNotEmpty(); - for (int i = 1; i < 5; i++) { + for (var i = 1; i < 5; i++) { assertThat(loggedEvents.get(i - 1).getCreated()).as( "Check that the list of logged events is ordered by created time in descending order") .isAfterOrEqualTo(loggedEvents.get(i).getCreated()); @@ -263,10 +265,10 @@ public void getLoggedEvents_WithLoggedEventOfOtherRecommenderId_ShouldReturnEmpt new Date(), -1, DETAIL_JSON); sut.create(le); - int otherRecommenderId = 6; + var otherRecommenderId = 6; - List loggedEvents = sut.listLoggedEventsForRecommender(project, - user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, otherRecommenderId); + var loggedEvents = sut.listLoggedEventsForRecommender(project, user.getUsername(), + EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, 5, otherRecommenderId); assertThat(loggedEvents).as("Check that no logged event is found").isEmpty(); } @@ -274,19 +276,45 @@ public void getLoggedEvents_WithLoggedEventOfOtherRecommenderId_ShouldReturnEmpt @Test public void getFilteredRecentLoggedEvents_ShouldReturnEvent() { - LoggedEvent evalEvent = buildLoggedEvent(project, "!" + user.getUsername(), + var evalEvent = buildLoggedEvent(project, "!" + user.getUsername(), EVENT_TYPE_RECOMMENDER_EVALUATION_EVENT, new Date(), -1, DETAIL_JSON); - LoggedEvent spanEvent = buildLoggedEvent(project, user.getUsername(), SPAN_CREATED_EVENT, + var spanEvent = buildLoggedEvent(project, user.getUsername(), SPAN_CREATED_EVENT, new Date(), -1, ""); sut.create(evalEvent); sut.create(spanEvent); - List loggedEvents = sut.listRecentActivity(user.getUsername(), 3); + var loggedEvents = sut.listRecentActivity(user.getUsername(), 3); assertThat(loggedEvents).hasSize(1); assertThat(loggedEvents).contains(spanEvent); } + @Test + void summarizeEvents() + { + for (var i = 0; i < 5; i++) { + le = buildLoggedEvent(project, user.getUsername(), SPAN_CREATED_EVENT, new Date(), -1, + DETAIL_JSON); + + var cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, i + 1); + le.setCreated(cal.getTime()); + sut.create(le); + } + + var today = LocalDate.now(); + var beginOfDay = today.atTime(LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant(); + var endOfDay = today.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant(); + + var summarizedEvents = sut.summarizeEvents(USERNAME, project, beginOfDay, endOfDay); + + assertThat(summarizedEvents).as("Check that the returned list is not empty").isNotEmpty(); + + assertThat(summarizedEvents) + .extracting(SummarizedLoggedEvent::getEvent, SummarizedLoggedEvent::getCount) + .containsExactly(tuple("SpanCreatedEvent", 5L)); + } + // Helper private Project createProject(String aName) { diff --git a/inception/inception-project-export/src/test/java/de/tudarmstadt/ukp/inception/project/export/controller/ExportServiceControllerImplTest.java b/inception/inception-project-export/src/test/java/de/tudarmstadt/ukp/inception/project/export/controller/ExportServiceControllerImplTest.java index fd6b9772ddc..e0a0a30999b 100644 --- a/inception/inception-project-export/src/test/java/de/tudarmstadt/ukp/inception/project/export/controller/ExportServiceControllerImplTest.java +++ b/inception/inception-project-export/src/test/java/de/tudarmstadt/ukp/inception/project/export/controller/ExportServiceControllerImplTest.java @@ -189,16 +189,17 @@ void thatSubscriptionWithoutProjectPermissionIsRejected() throws Exception var session = stompClient.connect(websocketUrl, sessionHandler).get(10, SECONDS); responseRecievedLatch.await(20, SECONDS); + + assertThat(messageRecieved).isFalse(); + assertThat(sessionHandler.errorMsg).containsIgnoringCase("Failed to send message"); + assertThat(errorRecieved).isTrue(); + try { session.disconnect(); } catch (Exception e) { // Ignore exceptions during disconnect } - - assertThat(messageRecieved).isFalse(); - assertThat(sessionHandler.errorMsg).containsIgnoringCase("Failed to send message"); - assertThat(errorRecieved).isTrue(); } @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED") @@ -217,11 +218,17 @@ void thatSubscriptionWithProjectPermissionIsAccepted() throws Exception var session = stompClient.connect(websocketUrl, sessionHandler).get(10, SECONDS); responseRecievedLatch.await(20, SECONDS); - session.disconnect(); assertThat(messageRecieved).isTrue(); assertThat(sessionHandler.errorMsg).isNull(); assertThat(errorRecieved).isFalse(); + + try { + session.disconnect(); + } + catch (Exception e) { + // Ignore exceptions during disconnect + } } private final class SessionHandler diff --git a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/footer/RecommendationEventWebsocketControllerImplTest.java b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/footer/RecommendationEventWebsocketControllerImplTest.java index 1587453e4ef..73cbcdb122d 100644 --- a/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/footer/RecommendationEventWebsocketControllerImplTest.java +++ b/inception/inception-recommendation/src/test/java/de/tudarmstadt/ukp/inception/recommendation/footer/RecommendationEventWebsocketControllerImplTest.java @@ -190,16 +190,18 @@ void thatSubscriptionWithoutProjectPermissionIsRejected() throws Exception .build(); var session = stompClient.connect(websocketUrl, sessionHandler).get(10, SECONDS); + Awaitility.await().atMost(20, SECONDS).until(sessionHandler::messagesProcessed); + + sessionHandler + .assertError(msg -> assertThat(msg).containsIgnoringCase("AccessDeniedException")); + try { session.disconnect(); } catch (Exception e) { // Ignore exceptions during disconnect } - - sessionHandler - .assertError(msg -> assertThat(msg).containsIgnoringCase("AccessDeniedException")); } @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED") @@ -217,16 +219,18 @@ void thatSubscriptionAsOtherUserIsRejected() throws Exception .build(); var session = stompClient.connect(websocketUrl, sessionHandler).get(10, SECONDS); + Awaitility.await().atMost(20, SECONDS).until(sessionHandler::messagesProcessed); + + sessionHandler + .assertError(msg -> assertThat(msg).containsIgnoringCase("AccessDeniedException")); + try { session.disconnect(); } catch (Exception e) { // Ignore exceptions during disconnect } - - sessionHandler - .assertError(msg -> assertThat(msg).containsIgnoringCase("AccessDeniedException")); } @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED") @@ -247,9 +251,15 @@ void thatSubscriptionWithProjectPermissionIsAccepted() throws Exception var session = stompClient.connect(websocketUrl, sessionHandler).get(10, SECONDS); Awaitility.await().atMost(20, SECONDS).until(sessionHandler::messagesProcessed); - session.disconnect(); sessionHandler.assertSuccess(); + + try { + session.disconnect(); + } + catch (Exception e) { + // Ignore exceptions during disconnect + } } private void sendTestMessage() diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashlet.html b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashlet.html index 2b181cb3c6c..c720cd8a160 100644 --- a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashlet.html +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashlet.html @@ -19,7 +19,19 @@ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd"> -

+
+
+ Recent activity +
+ +
+
+
+
+
diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashlet.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashlet.java index f8a0501c3eb..496981c21f2 100644 --- a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashlet.java +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashlet.java @@ -19,7 +19,7 @@ import java.util.Map; -import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.IModel; @@ -27,7 +27,10 @@ import org.apache.wicket.spring.injection.annot.SpringBean; import de.tudarmstadt.ukp.clarin.webanno.model.Project; +import de.tudarmstadt.ukp.inception.bootstrap.BootstrapModalDialog; +import de.tudarmstadt.ukp.inception.support.lambda.LambdaAjaxLink; import de.tudarmstadt.ukp.inception.support.svelte.SvelteBehavior; +import de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel.ActivityPanel; public class ActivitiesDashlet extends Panel @@ -36,22 +39,35 @@ public class ActivitiesDashlet private @SpringBean ActivitiesDashletController controller; - public ActivitiesDashlet(String aId, IModel aCurrentProject) + private IModel project; + private BootstrapModalDialog dialog; + + public ActivitiesDashlet(String aId, IModel aProject) { super(aId); - long projectId = aCurrentProject.map(Project::getId).orElse(-1l).getObject(); - setDefaultModel(Model.ofMap(Map.of("dataUrl", controller.listActivitiesUrl(projectId)))); + project = aProject; + + long projectId = aProject.map(Project::getId).orElse(-1l).getObject(); + setDefaultModel(Model.ofMap(Map.of("dataUrl", controller.getListActivitiesUrl(projectId)))); setOutputMarkupPlaceholderTag(true); add(new WebMarkupContainer("content").setOutputMarkupId(true) .add(new SvelteBehavior(this))); + + dialog = new BootstrapModalDialog("dialog"); + dialog.closeOnClick(); + dialog.closeOnEscape(); + add(dialog); + + add(new LambdaAjaxLink("showActivity", this::actionShowActivity)); } - @Override - public void renderHead(IHeaderResponse aResponse) + public void actionShowActivity(AjaxRequestTarget aTarget) { - super.renderHead(aResponse); + var dialogContent = new ActivityPanel(BootstrapModalDialog.CONTENT_ID, project); + + dialog.open(dialogContent, aTarget); } } diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashletController.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashletController.java index 9fc77ea46ba..dcea3adc9dc 100644 --- a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashletController.java +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashletController.java @@ -20,13 +20,27 @@ import static de.tudarmstadt.ukp.inception.security.config.InceptionSecurityWebUIApiAutoConfiguration.BASE_API_URL; import java.util.List; +import java.util.Optional; + +import de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel.ActivityOverview; +import de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel.ActivitySummary; public interface ActivitiesDashletController { String BASE_URL = BASE_API_URL + "/activity"; - String LIST_PATH = "/project/{projectId}/list"; + String RECENT_ACTIVITY_PATH = "/project/{projectId}/recent"; + String ACTIVITY_OVERVIEW_PATH = "/project/{projectId}/overview"; + String ACTIVITY_PATH = "/project/{projectId}"; - String listActivitiesUrl(long aProjectId); + String getListActivitiesUrl(long aProjectId); List listActivities(long aProjectId); + + String getActivityOverviewUrl(long aProjectId); + + ActivityOverview activityOverview(long aProjectId, Optional aYear); + + ActivitySummary activitySummary(long aProjectId, Optional aFrom, Optional aTo); + + String getActivitySummaryUrl(long aProjectId); } diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashletControllerImpl.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashletControllerImpl.java index 2626abecf28..85f4895946c 100644 --- a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashletControllerImpl.java +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/ActivitiesDashletControllerImpl.java @@ -20,15 +20,29 @@ import static de.tudarmstadt.ukp.clarin.webanno.model.PermissionLevel.CURATOR; import static de.tudarmstadt.ukp.inception.support.WebAnnoConst.CURATION_USER; import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static org.apache.commons.collections4.SetUtils.unmodifiableSet; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import javax.persistence.NoResultException; import javax.servlet.ServletContext; import org.springframework.beans.factory.annotation.Autowired; @@ -36,8 +50,10 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import de.tudarmstadt.ukp.clarin.webanno.model.Project; import de.tudarmstadt.ukp.clarin.webanno.model.SourceDocument; import de.tudarmstadt.ukp.clarin.webanno.security.UserDao; import de.tudarmstadt.ukp.clarin.webanno.ui.annotation.AnnotationPageMenuItem; @@ -54,7 +70,12 @@ import de.tudarmstadt.ukp.inception.curation.service.CurationDocumentService; import de.tudarmstadt.ukp.inception.documents.api.DocumentService; import de.tudarmstadt.ukp.inception.log.EventRepository; +import de.tudarmstadt.ukp.inception.log.model.LoggedEvent; import de.tudarmstadt.ukp.inception.project.api.ProjectService; +import de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel.ActivityOverview; +import de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel.ActivityOverviewItem; +import de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel.ActivitySummary; +import de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel.ActivitySummaryItem; @ConditionalOnWebApplication @RestController @@ -103,14 +124,137 @@ public ActivitiesDashletControllerImpl(EventRepository aEventRepository, } @Override - public String listActivitiesUrl(long aProjectId) + public String getActivitySummaryUrl(long aProjectId) { return servletContext.getContextPath() + BASE_URL - + LIST_PATH.replace("{projectId}", String.valueOf(aProjectId)); + + ACTIVITY_PATH.replace("{projectId}", String.valueOf(aProjectId)); } @Override - @GetMapping(LIST_PATH) + @GetMapping(ACTIVITY_PATH) + public ActivitySummary activitySummary(@PathVariable("projectId") long aProjectId, + @RequestParam("from") Optional aFrom, @RequestParam("to") Optional aTo) + { + var user = userRepository.getCurrentUser(); + var project = projectRepository.getProject(aProjectId); + + var today = LocalDate.now(); + var beginDate = aFrom.map(this::dateStringToInstant).orElse(today); + var begin = startOfDay(beginDate); + var end = endOfDay(aTo.map(this::dateStringToInstant).orElse(beginDate)); + if (!end.isAfter(begin)) { + end = endOfDay(beginDate); + } + + if (!projectRepository.hasAnyRole(user, project)) { + return new ActivitySummary(begin, end, emptyList(), emptyMap()); + } + + var recentEvents = eventRepository.summarizeEvents(user.getUsername(), project, begin, end); + var documentNameCache = new HashMap(); + var globalItems = new ArrayList(); + var itemsByDoc = new HashMap>(); + for (var event : recentEvents) { + if (event.getDocument() > 0) { + var documentName = documentNameCache.computeIfAbsent(event.getDocument(), + docId -> getDocumentName(aProjectId, docId)); + var items = itemsByDoc.computeIfAbsent(documentName, $ -> new ArrayList<>()); + items.add(new ActivitySummaryItem(event.getEvent(), event.getCount())); + } + else { + globalItems.add(new ActivitySummaryItem(event.getEvent(), event.getCount())); + } + } + + return new ActivitySummary(begin, end, globalItems, itemsByDoc); + } + + private String getDocumentName(long aProjectId, Long docId) + { + try { + return documentService.getSourceDocument(aProjectId, docId).getName(); + } + catch (NoResultException e) { + return "Deleted document"; + } + } + + private LocalDate dateStringToInstant(String aDateString) + { + return LocalDate.parse(aDateString, DateTimeFormatter.ISO_LOCAL_DATE); + } + + private Instant startOfDay(LocalDate aDate) + { + return aDate.atTime(LocalTime.MIN).atOffset(ZoneOffset.UTC).toInstant(); + } + + private Instant endOfDay(LocalDate aDate) + { + return aDate.atTime(LocalTime.MAX).atOffset(ZoneOffset.UTC).toInstant(); + } + + private Instant startOfYear(LocalDate aDate) + { + return startOfDay(LocalDate.of(aDate.getYear(), Month.JANUARY, 1)); + } + + private Instant endOfYear(LocalDate aDate) + { + return endOfDay(LocalDate.of(aDate.getYear(), Month.DECEMBER, 31)); + } + + @Override + public String getActivityOverviewUrl(long aProjectId) + { + return servletContext.getContextPath() + BASE_URL + + ACTIVITY_OVERVIEW_PATH.replace("{projectId}", String.valueOf(aProjectId)); + } + + @Override + @GetMapping(ACTIVITY_OVERVIEW_PATH) + public ActivityOverview activityOverview(@PathVariable("projectId") long aProjectId, + @RequestParam("year") Optional aYear) + { + var user = userRepository.getCurrentUser(); + var project = projectRepository.getProject(aProjectId); + + var today = LocalDate.now(); + var year = aYear.map(y -> String.format("%04d-01-01", y)).map(this::dateStringToInstant) + .orElse(today); + var begin = startOfYear(year); + var end = endOfYear(year); + + if (!projectRepository.hasAnyRole(user, project)) { + return new ActivityOverview(begin, end, emptyMap()); + } + + var recentEvents = eventRepository.summarizeEvents(user.getUsername(), project, begin, end); + + var aggregator = new LinkedHashMap(); + recentEvents.forEach(summarizedEvent -> { + aggregator.computeIfAbsent(summarizedEvent.getDate(), $ -> new AtomicLong()) + .addAndGet(summarizedEvent.getCount()); + }); + + Map items = aggregator.entrySet().stream().collect( + LinkedHashMap::new, + (m, e) -> m.put(e.getKey(), + new ActivityOverviewItem(e.getKey(), e.getValue().get())), + LinkedHashMap::putAll); + + return new ActivityOverview(begin, end, items); + } + + @Override + public String getListActivitiesUrl(long aProjectId) + { + return servletContext.getContextPath() + BASE_URL + + RECENT_ACTIVITY_PATH.replace("{projectId}", String.valueOf(aProjectId)); + } + + @Override + @GetMapping(RECENT_ACTIVITY_PATH) public List listActivities(@PathVariable("projectId") long aProjectId) { var user = userRepository.getCurrentUser(); @@ -137,33 +281,44 @@ public List listActivities(@PathVariable("projectId") long aProjectId) return recentEvents.stream() // .filter(Objects::nonNull) // .filter(event -> event.getDocument() != -1l) // + .filter(event -> event.getAnnotator() != null) // // Filter out documents which are not annotatable or curatable - .filter(event -> { - if (CURATION_USER.equals(event.getAnnotator())) { - if (!isCurator) { - return false; - } - - return curatableSourceDocuments.containsKey(event.getDocument()); - } - else { - return annotatableSourceDocuments.containsKey(event.getDocument()); - } - }) + .filter(event -> isEditable(annotatableSourceDocuments, curatableSourceDocuments, + isCurator, event)) // Link events and documents before returning them - .map(event -> { - if (CURATION_USER.equals(event.getAnnotator())) { - return new Activity(event, - curatableSourceDocuments.get(event.getDocument()), - curationPageMenuItem.getUrl(project, event.getDocument())); - } - else { - return new Activity(event, - annotatableSourceDocuments.get(event.getDocument()), - annotationPageMenuItem.getUrl(project, event.getDocument(), - event.getAnnotator())); - } - })// + .map(event -> toActivity(project, annotatableSourceDocuments, + curatableSourceDocuments, event))// .collect(toList()); } + + private Activity toActivity(Project project, + Map annotatableSourceDocuments, + Map curatableSourceDocuments, LoggedEvent event) + { + if (CURATION_USER.equals(event.getAnnotator())) { + return new Activity(event, curatableSourceDocuments.get(event.getDocument()), + curationPageMenuItem.getUrl(project, event.getDocument())); + } + else { + return new Activity(event, annotatableSourceDocuments.get(event.getDocument()), + annotationPageMenuItem.getUrl(project, event.getDocument(), + event.getAnnotator())); + } + } + + private boolean isEditable(Map annotatableSourceDocuments, + Map curatableSourceDocuments, boolean isCurator, + LoggedEvent event) + { + if (CURATION_USER.equals(event.getAnnotator())) { + if (!isCurator) { + return false; + } + + return curatableSourceDocuments.containsKey(event.getDocument()); + } + else { + return annotatableSourceDocuments.containsKey(event.getDocument()); + } + } } diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityOverview.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityOverview.java new file mode 100644 index 00000000000..c679c009c08 --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityOverview.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel; + +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE; + +import java.time.Instant; +import java.time.ZoneId; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ActivityOverview +{ + public final @JsonProperty String from; + public final @JsonProperty String to; + public final @JsonProperty Map items; + + public ActivityOverview(Instant aFrom, Instant aTo, Map aItems) + { + var dateFormat = ISO_LOCAL_DATE.withZone(ZoneId.of("UTC")); + from = dateFormat.format(aFrom); + to = dateFormat.format(aTo); + + items = new LinkedHashMap(); + for (var entry : aItems.entrySet()) { + items.put(dateFormat.format(entry.getKey()), entry.getValue()); + } + } +} diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityOverviewItem.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityOverviewItem.java new file mode 100644 index 00000000000..a33b0f665e7 --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityOverviewItem.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel; + +import java.time.Instant; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ActivityOverviewItem +{ + public final @JsonProperty long count; + + public ActivityOverviewItem(Instant aDate, long aCount) + { + count = aCount; + } +} diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanel.html b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanel.html new file mode 100644 index 00000000000..ad0c4aa7db9 --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanel.html @@ -0,0 +1,28 @@ + + + + +
+
Activities
+
+
+ + + diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanel.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanel.java new file mode 100644 index 00000000000..be15a204ed0 --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanel.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel; + +import java.util.Map; + +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.spring.injection.annot.SpringBean; + +import de.tudarmstadt.ukp.clarin.webanno.model.Project; +import de.tudarmstadt.ukp.inception.support.svelte.SvelteBehavior; +import de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.ActivitiesDashletController; + +public class ActivityPanel + extends Panel +{ + private static final long serialVersionUID = -1689813207024061423L; + + private @SpringBean ActivitiesDashletController controller; + + public ActivityPanel(String aId, IModel aModel) + { + super(aId); + + long projectId = aModel.map(Project::getId).orElse(-1l).getObject(); + setDefaultModel(Model.ofMap(Map.of( // + "overviewDataUrl", controller.getActivityOverviewUrl(projectId), // + "summaryDataUrl", controller.getActivitySummaryUrl(projectId)))); + + setOutputMarkupPlaceholderTag(true); + + add(new WebMarkupContainer("content").setOutputMarkupId(true) + .add(new SvelteBehavior(this))); + } +} diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.html b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.html new file mode 100644 index 00000000000..a0a2ace8555 --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.html @@ -0,0 +1,25 @@ + + + + + +
+ + + diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.java new file mode 100644 index 00000000000..45060b9e9cc --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel; + +import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.NS_PROJECT; +import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.PAGE_PARAM_PROJECT; + +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.wicketstuff.annotation.mount.MountPath; + +import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase; + +@MountPath(NS_PROJECT + "/${" + PAGE_PARAM_PROJECT + "}/activities") +public class ActivityPanelTestPage + extends ProjectPageBase +{ + private static final long serialVersionUID = -2301327315880182512L; + + public ActivityPanelTestPage(PageParameters aParameters) + { + super(aParameters); + + add(new ActivityPanel("panel", getProjectModel())); + } +} \ No newline at end of file diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivitySummary.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivitySummary.java new file mode 100644 index 00000000000..fac1e5368fd --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivitySummary.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel; + +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE; + +import java.time.Instant; +import java.time.ZoneId; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ActivitySummary +{ + public final @JsonProperty String from; + public final @JsonProperty String to; + public final @JsonProperty List globalItems; + public final @JsonProperty Map> perDocumentItems; + + public ActivitySummary(Instant aFrom, Instant aTo, List aGlobalItems, + Map> aPerDocumentItems) + { + var formatter = ISO_LOCAL_DATE.withZone(ZoneId.of("UTC")); + from = formatter.format(aFrom); + to = formatter.format(aTo); + globalItems = aGlobalItems; + perDocumentItems = aPerDocumentItems; + } +} diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivitySummaryItem.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivitySummaryItem.java new file mode 100644 index 00000000000..09c71f3dd9e --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivitySummaryItem.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Technische Universität Darmstadt under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The Technische Universität Darmstadt + * licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ActivitySummaryItem( // + @JsonProperty String event, // + @JsonProperty long count) +{} diff --git a/inception/inception-ui-dashboard-activity/src/main/ts/build.mjs b/inception/inception-ui-dashboard-activity/src/main/ts/build.mjs index a5b9bfbe58e..644648098fa 100644 --- a/inception/inception-ui-dashboard-activity/src/main/ts/build.mjs +++ b/inception/inception-ui-dashboard-activity/src/main/ts/build.mjs @@ -15,17 +15,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import esbuild from "esbuild" -import esbuildSvelte from "esbuild-svelte" -import sveltePreprocess from "svelte-preprocess" +import esbuild from 'esbuild' +import esbuildSvelte from 'esbuild-svelte' +import sveltePreprocess from 'svelte-preprocess' import yargs from 'yargs/yargs' import { hideBin } from 'yargs/helpers' +import { sassPlugin } from 'esbuild-sass-plugin' import fs from 'fs-extra' const argv = yargs(hideBin(process.argv)).argv -const packagePath = "de/tudarmstadt/ukp/inception/ui/core/dashboard/activity" +const packagePath = 'de/tudarmstadt/ukp/inception/ui/core/dashboard/activity' let outbase = `../../../target/js/${packagePath}` if (argv.live) { @@ -33,14 +33,22 @@ if (argv.live) { } const defaults = { - entryPoints: ["src/ActivitiesDashlet.svelte"], - outfile: `${outbase}/ActivitiesDashlet.min.js`, - mainFields: ["svelte", "browser", "module", "main"], - format: "esm", + mainFields: ['svelte', 'browser', 'module', 'main'], + format: 'esm', plugins: [ + sassPlugin(), esbuildSvelte({ + compilerOptions: { dev: argv.live }, preprocess: sveltePreprocess(), - }), + filterWarnings: (warning) => { + // Ignore warnings about unused CSS selectors in Svelte components which appear as we import + // Bootstrap CSS files. We do not use all selectors in the files and thus the warnings are + // expected. + if (warning.code === 'css-unused-selector') { + return false + } + } + }) ], bundle: true, sourcemap: true, @@ -54,8 +62,22 @@ fs.mkdirsSync(`${outbase}`) fs.emptyDirSync(outbase) if (argv.live) { - const context = await esbuild.context(defaults) - await context.watch() + const context1 = await esbuild.context(Object.assign({ + entryPoints: ['src/ActivitiesDashlet.svelte'], + outfile: `${outbase}/ActivitiesDashlet.min.js` + }, defaults)) + const context2 = await esbuild.context(Object.assign({ + entryPoints: ['src/ActivityPanel.svelte'], + outfile: `${outbase}/panel/ActivityPanel.min.js` + }, defaults)) + await Promise.all([context1.watch(), context2.watch()]) } else { - esbuild.build(defaults) + esbuild.build(Object.assign({ + entryPoints: ['src/ActivitiesDashlet.svelte'], + outfile: `${outbase}/ActivitiesDashlet.min.js` + }, defaults)) + esbuild.build(Object.assign({ + entryPoints: ['src/ActivityPanel.svelte'], + outfile: `${outbase}/panel/ActivityPanel.min.js` + }, defaults)) } diff --git a/inception/inception-ui-dashboard-activity/src/main/ts/src/ActivitiesDashlet.svelte b/inception/inception-ui-dashboard-activity/src/main/ts/src/ActivitiesDashlet.svelte index 4a9dccb29c1..9b1ae26182d 100644 --- a/inception/inception-ui-dashboard-activity/src/main/ts/src/ActivitiesDashlet.svelte +++ b/inception/inception-ui-dashboard-activity/src/main/ts/src/ActivitiesDashlet.svelte @@ -51,8 +51,7 @@ } -
-
Recent activity
+
{#if loading}
diff --git a/inception/inception-ui-dashboard-activity/src/main/ts/src/ActivityPanel.svelte b/inception/inception-ui-dashboard-activity/src/main/ts/src/ActivityPanel.svelte new file mode 100644 index 00000000000..b8a6252f380 --- /dev/null +++ b/inception/inception-ui-dashboard-activity/src/main/ts/src/ActivityPanel.svelte @@ -0,0 +1,374 @@ + + +{#if overviewDataLoading} +
+
+
+ Loading... +
+
+
+{:else if !overviewData} +
+ No recent activity +
+{:else} +
+
+ +

{year}

+ +
+
+ + + + {#each { length: 53 } as _, week} + {@const monthStartingInWeek = + getMonthStartingInWeek(week)} + {#if monthStartingInWeek} + + {:else} + + {/if} + {/each} + + {#each { length: 7 } as _, dayOfWeek} + + {#if dayOfWeek === 0 || dayOfWeek === 1 || dayOfWeek === 3 || dayOfWeek === 5} + + {/if} + {#each { length: 53 } as _, week} + {@const item = getActivity(week, dayOfWeek)} + + + {/each} + + {/each} +
{monthStartingInWeek}
+ {#if dayOfWeek === 1} + Mon + {:else if dayOfWeek === 3} + Wed + {:else if dayOfWeek === 5} + Fri + {/if} + + !item?.outOfRange && + loadSummaryData(item.date)} + > +
+
+
+{/if} + +{#if summaryDataLoading} +
+
+
+ Loading... +
+
+
+{:else} + {#if summaryData} +
+
{dayjs(summaryData.from).format("LL")}
+
+ {/if} + {#if summaryData && !summaryData?.globalItems?.length && !summaryData?.perDocumentItems?.length} +
+ No activity +
+ {:else if summaryData} +
+ {#each Object.entries(summaryData.perDocumentItems).sort( ([a], [b]) => a.localeCompare(b), ) as [document, items]} +
+
+
+ + {document} +
+ {#each items || [] as item} +
+ {item.count} + {item.event} +
+ {/each} +
+
+ {/each} +
+
+
+ Project-level activities +
+ {#each summaryData.globalItems || [] as item} +
+ {item.count} + {item.event} +
+ {/each} +
+
+
+ {/if} +{/if} + + diff --git a/inception/inception-websocket/src/test/java/de/tudarmstadt/ukp/inception/websocket/WebSocketIntegrationTest.java b/inception/inception-websocket/src/test/java/de/tudarmstadt/ukp/inception/websocket/WebSocketIntegrationTest.java index 97b8824d6b9..da09a494ab1 100644 --- a/inception/inception-websocket/src/test/java/de/tudarmstadt/ukp/inception/websocket/WebSocketIntegrationTest.java +++ b/inception/inception-websocket/src/test/java/de/tudarmstadt/ukp/inception/websocket/WebSocketIntegrationTest.java @@ -191,11 +191,17 @@ public void thatRecentMessageIsReceived() session = stompClient.connect(websocketUrl, sessionHandler).get(5, SECONDS); latch.await(10, SECONDS); - session.disconnect(); assertThat(receivedMessages.size()).isEqualTo(1); LoggedEventMessage msg1 = receivedMessages.get(0); assertThat(msg1.getEventType()).isEqualTo(DocumentStateChangedEvent.class.getSimpleName()); + + try { + session.disconnect(); + } + catch (Exception e) { + // Ignore exceptions during disconnect + } } private final class SessionHandler From fc5d45e284bc8d2c3416e8a6fdd0a331f66b4c76 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sun, 17 Dec 2023 17:20:17 +0100 Subject: [PATCH 2/5] #4381 - Allow users to browse their past activity - Added missing dependencies --- .../inception-ui-dashboard-activity/pom.xml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/inception/inception-ui-dashboard-activity/pom.xml b/inception/inception-ui-dashboard-activity/pom.xml index af82f072589..454a3f7a3a7 100644 --- a/inception/inception-ui-dashboard-activity/pom.xml +++ b/inception/inception-ui-dashboard-activity/pom.xml @@ -74,6 +74,14 @@ de.tudarmstadt.ukp.inception.app inception-security + + de.tudarmstadt.ukp.inception.app + inception-support-bootstrap + + + de.tudarmstadt.ukp.inception.app + inception-ui-core + org.springframework @@ -104,6 +112,18 @@ org.apache.wicket wicket-spring + + org.apache.wicket + wicket-request + + + org.apache.wicket + wicket-extensions + + + org.wicketstuff + wicketstuff-annotation + org.danekja jdk-serializable-functional @@ -119,6 +139,11 @@ commons-collections4 + + javax.persistence + javax.persistence-api + + javax.servlet javax.servlet-api From aa02128ad6ba90a89f6fd3d6873c8c59080bce4b Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sun, 17 Dec 2023 17:41:55 +0100 Subject: [PATCH 3/5] #4381 - Allow users to browse their past activity - Auto-format --- .../active/learning/ActiveLearningService.java | 4 ++-- .../multistring/MultiSelectTextFeatureEditor.java | 2 +- .../diam/model/compactv2/CompactSerializerV2Impl.java | 3 ++- .../model/compactv2/CompactSerializerV2ImplTest.java | 2 +- .../opensearch/OpenSearchProviderTest.java | 2 +- .../externalsearch/pubmed/PubMedProviderTest.java | 7 ++++--- .../inception/feature/lookup/LookupFeatureSupport.java | 6 +++--- .../ApacheAnnotatorHtmlAnnotationEditor.java | 10 +++++----- .../ApacheAnnotatorHtmlAnnotationEditorFactory.java | 6 +++--- ...orHtmlAnnotationEditorSupportAutoConfiguration.java | 2 +- .../imls/external/v1/ExternalRecommenderTraits.java | 4 ++-- .../recommendation/imls/ollama/OllamaRecommender.java | 3 ++- .../imls/ollama/response/ResponseAsLabelExtractor.java | 3 +-- .../inception/io/tei/dkprobackport/TeiConstants.java | 2 +- .../io/xmi/dkprobackport/BinaryCasReader.java | 6 ++---- .../recommendation/api/LearningRecordService.java | 8 ++++---- .../service/RecommendationServiceImpl.java | 3 +-- .../support/xml/sanitizer/PolicyCollectionBuilder.java | 4 ++-- .../config/AnnotationUIAutoConfiguration.java | 2 +- .../ui/project/casdoctor/ProjectCasDoctorPanel.java | 6 ++++-- 20 files changed, 43 insertions(+), 42 deletions(-) diff --git a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningService.java b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningService.java index ca36822134b..52d93b02c68 100644 --- a/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningService.java +++ b/inception/inception-active-learning/src/main/java/de/tudarmstadt/ukp/inception/active/learning/ActiveLearningService.java @@ -44,8 +44,8 @@ public interface ActiveLearningService List> getSuggestions(User aDataOwner, AnnotationLayer aLayer); /** - * @return if the are any records of type {@link LearningRecordUserAction#SKIPPED} in the history of - * the given layer for the given user. + * @return if the are any records of type {@link LearningRecordUserAction#SKIPPED} in the + * history of the given layer for the given user. * * @param aDataOwner * annotator user to check suggestions for diff --git a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiSelectTextFeatureEditor.java b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiSelectTextFeatureEditor.java index 85bb06f7e52..b5a40341f26 100644 --- a/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiSelectTextFeatureEditor.java +++ b/inception/inception-api-annotation/src/main/java/de/tudarmstadt/ukp/inception/annotation/feature/multistring/MultiSelectTextFeatureEditor.java @@ -98,7 +98,7 @@ public void onConfigure() super.addFeatureUpdateBehavior(); featureUpdateBehaviorAdded = true; } - + // Hides feature if "Hide un-constraint feature" is enabled and constraint rules are applied // and feature doesn't match any constraint rule // if enabled and constraints rule execution returns anything other than green diff --git a/inception/inception-diam-compactv2/src/main/java/de/tudarmstadt/ukp/inception/diam/model/compactv2/CompactSerializerV2Impl.java b/inception/inception-diam-compactv2/src/main/java/de/tudarmstadt/ukp/inception/diam/model/compactv2/CompactSerializerV2Impl.java index 0a598e9dc4a..e327adec082 100644 --- a/inception/inception-diam-compactv2/src/main/java/de/tudarmstadt/ukp/inception/diam/model/compactv2/CompactSerializerV2Impl.java +++ b/inception/inception-diam-compactv2/src/main/java/de/tudarmstadt/ukp/inception/diam/model/compactv2/CompactSerializerV2Impl.java @@ -44,7 +44,8 @@ /** *

- * This class is exposed as a Spring Component via {@link DiamCompactV2AutoConfig#compactSerializerV2}. + * This class is exposed as a Spring Component via + * {@link DiamCompactV2AutoConfig#compactSerializerV2}. *

*/ public class CompactSerializerV2Impl diff --git a/inception/inception-diam-compactv2/src/test/java/de/tudarmstadt/ukp/inception/diam/model/compactv2/CompactSerializerV2ImplTest.java b/inception/inception-diam-compactv2/src/test/java/de/tudarmstadt/ukp/inception/diam/model/compactv2/CompactSerializerV2ImplTest.java index 3a49e0bb17f..6ad41083db6 100644 --- a/inception/inception-diam-compactv2/src/test/java/de/tudarmstadt/ukp/inception/diam/model/compactv2/CompactSerializerV2ImplTest.java +++ b/inception/inception-diam-compactv2/src/test/java/de/tudarmstadt/ukp/inception/diam/model/compactv2/CompactSerializerV2ImplTest.java @@ -48,7 +48,7 @@ public class CompactSerializerV2ImplTest { private @Mock AnnotationSchemaProperties annotationSchemaProperties; - + @Test void thatSerializationWorks() throws Exception { diff --git a/inception/inception-external-search-opensearch/src/test/java/de/tudarmstadt/ukp/inception/externalsearch/opensearch/OpenSearchProviderTest.java b/inception/inception-external-search-opensearch/src/test/java/de/tudarmstadt/ukp/inception/externalsearch/opensearch/OpenSearchProviderTest.java index 5e6a1642f09..4dbae5dcc5e 100644 --- a/inception/inception-external-search-opensearch/src/test/java/de/tudarmstadt/ukp/inception/externalsearch/opensearch/OpenSearchProviderTest.java +++ b/inception/inception-external-search-opensearch/src/test/java/de/tudarmstadt/ukp/inception/externalsearch/opensearch/OpenSearchProviderTest.java @@ -58,7 +58,7 @@ public void setup() @Override public void build(int aIndex, Builder aBuilder) { - // This should hopefully allow running tests on machines with less than the + // This should hopefully allow running tests on machines with less than the // default 90% high watermark disk space free (e.g on a 1TB drive less than 100 GB). aBuilder.put("cluster.routing.allocation.disk.threshold_enabled", false); } diff --git a/inception/inception-external-search-pubmed/src/test/java/de/tudarmstadt/ukp/inception/externalsearch/pubmed/PubMedProviderTest.java b/inception/inception-external-search-pubmed/src/test/java/de/tudarmstadt/ukp/inception/externalsearch/pubmed/PubMedProviderTest.java index b2ad5853994..206169e4eb0 100644 --- a/inception/inception-external-search-pubmed/src/test/java/de/tudarmstadt/ukp/inception/externalsearch/pubmed/PubMedProviderTest.java +++ b/inception/inception-external-search-pubmed/src/test/java/de/tudarmstadt/ukp/inception/externalsearch/pubmed/PubMedProviderTest.java @@ -72,12 +72,13 @@ public void thatExecuteQueryWorks() throws Exception public void thatGetDocumentTextWorks() throws Exception { when(annotationService.getFullProjectTypeSystem(any())) - .thenReturn(createTypeSystemDescription()); - + .thenReturn(createTypeSystemDescription()); + String results = sut.getDocumentText(repo, traits, "PMC", "PMC7096989"); // System.out.println(results); - assertThat(results).contains("Asthma is the most common inflammatory disease of the lungs."); + assertThat(results) + .contains("Asthma is the most common inflammatory disease of the lungs."); } } diff --git a/inception/inception-feature-lookup/src/main/java/de/tudarmstadt/ukp/inception/feature/lookup/LookupFeatureSupport.java b/inception/inception-feature-lookup/src/main/java/de/tudarmstadt/ukp/inception/feature/lookup/LookupFeatureSupport.java index c302e47b508..b6e142fff9c 100644 --- a/inception/inception-feature-lookup/src/main/java/de/tudarmstadt/ukp/inception/feature/lookup/LookupFeatureSupport.java +++ b/inception/inception-feature-lookup/src/main/java/de/tudarmstadt/ukp/inception/feature/lookup/LookupFeatureSupport.java @@ -233,13 +233,13 @@ public void generateFeature(TypeSystemDescription aTSD, TypeDescription aTD, { aTD.addFeature(aFeature.getName(), "", CAS.TYPE_NAME_STRING); } - + @Override public List lookupLazyDetails(AnnotationFeature aFeature, Object aValue) { if (aValue instanceof LookupEntry) { var handle = (LookupEntry) aValue; - + var result = new VLazyDetailGroup(); result.addDetail(new VLazyDetail("Label", handle.getUiLabel())); @@ -249,7 +249,7 @@ public List lookupLazyDetails(AnnotationFeature aFeature, Obje return asList(result); } - + return Collections.emptyList(); } } diff --git a/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/ApacheAnnotatorHtmlAnnotationEditor.java b/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/ApacheAnnotatorHtmlAnnotationEditor.java index 366ea1e1655..d931b080831 100644 --- a/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/ApacheAnnotatorHtmlAnnotationEditor.java +++ b/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/ApacheAnnotatorHtmlAnnotationEditor.java @@ -75,13 +75,13 @@ protected AnnotationEditorProperties getProperties() props.setEditorFactoryId(getFactory().getBeanName()); if (getFactory() instanceof ClientSideUserPreferencesProvider) { ((ClientSideUserPreferencesProvider) getFactory()).getUserPreferencesKey() - .ifPresent(key -> props.setUserPreferencesKey(key.getClientSideKey())); + .ifPresent(key -> props.setUserPreferencesKey(key.getClientSideKey())); } props.setDiamAjaxCallbackUrl(getDiamBehavior().getCallbackUrl().toString()); - props.setStylesheetSources( - asList(referenceToUrl(servletContext, ApacheAnnotatorJsCssResourceReference.get()))); - props.setScriptSources(asList( - referenceToUrl(servletContext, ApacheAnnotatorJsJavascriptResourceReference.get()))); + props.setStylesheetSources(asList( + referenceToUrl(servletContext, ApacheAnnotatorJsCssResourceReference.get()))); + props.setScriptSources(asList(referenceToUrl(servletContext, + ApacheAnnotatorJsJavascriptResourceReference.get()))); return props; } } diff --git a/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/ApacheAnnotatorHtmlAnnotationEditorFactory.java b/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/ApacheAnnotatorHtmlAnnotationEditorFactory.java index 8a653472c8e..03340fb00a3 100644 --- a/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/ApacheAnnotatorHtmlAnnotationEditorFactory.java +++ b/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/ApacheAnnotatorHtmlAnnotationEditorFactory.java @@ -77,7 +77,7 @@ public int accepts(Project aProject, String aFormat) if (aFormat.startsWith(CustomXmlFormatLoader.CUSTOM_XML_FORMAT_PREFIX)) { return PREFERRED; } - + switch (aFormat) { case HtmlFormatSupport.ID: // fall-through case XmlFormatSupport.ID: @@ -105,8 +105,8 @@ public void initState(AnnotatorState aModelObject) @Override public Optional> getUserPreferencesKey() { - return Optional - .of(new ClientSidePreferencesKey<>(Map.class, "annotation/apache-annotator-editor")); + return Optional.of( + new ClientSidePreferencesKey<>(Map.class, "annotation/apache-annotator-editor")); } @Override diff --git a/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/config/ApacheAnnotatorHtmlAnnotationEditorSupportAutoConfiguration.java b/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/config/ApacheAnnotatorHtmlAnnotationEditorSupportAutoConfiguration.java index 1c39d55e797..200de805392 100644 --- a/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/config/ApacheAnnotatorHtmlAnnotationEditorSupportAutoConfiguration.java +++ b/inception/inception-html-apache-annotator-editor/src/main/java/de/tudarmstadt/ukp/inception/apacheannotatoreditor/config/ApacheAnnotatorHtmlAnnotationEditorSupportAutoConfiguration.java @@ -25,7 +25,7 @@ @Configuration @ConditionalOnProperty(prefix = "ui.html-apacheannotator", name = "enabled", // -havingValue = "true", matchIfMissing = true) + havingValue = "true", matchIfMissing = true) public class ApacheAnnotatorHtmlAnnotationEditorSupportAutoConfiguration { @Bean diff --git a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraits.java b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraits.java index a9765586679..80001a4bd25 100644 --- a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraits.java +++ b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraits.java @@ -50,12 +50,12 @@ public void setTrainable(boolean aTrainable) { trainable = aTrainable; } - + public void setVerifyCertificates(boolean aVerifyCertificates) { verifyCertificates = aVerifyCertificates; } - + public boolean isVerifyCertificates() { return verifyCertificates; diff --git a/inception/inception-imls-ollama/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/ollama/OllamaRecommender.java b/inception/inception-imls-ollama/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/ollama/OllamaRecommender.java index 9d4e2421834..f67526eda4d 100644 --- a/inception/inception-imls-ollama/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/ollama/OllamaRecommender.java +++ b/inception/inception-imls-ollama/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/ollama/OllamaRecommender.java @@ -92,7 +92,8 @@ public Range predict(RecommenderContext aContext, CAS aCas, int aBegin, int aEnd throws RecommendationException { var responseExtractor = getResponseExtractor(); - List examples = responseExtractor.generate(this, aCas, MAX_FEW_SHOT_EXAMPLES); + List examples = responseExtractor.generate(this, aCas, + MAX_FEW_SHOT_EXAMPLES); getPromptContextGenerator().generate(this, aCas, aBegin, aEnd).forEach(promptContext -> { try { diff --git a/inception/inception-imls-ollama/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/ollama/response/ResponseAsLabelExtractor.java b/inception/inception-imls-ollama/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/ollama/response/ResponseAsLabelExtractor.java index 75b2ed5aad1..369636cc8f8 100644 --- a/inception/inception-imls-ollama/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/ollama/response/ResponseAsLabelExtractor.java +++ b/inception/inception-imls-ollama/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/ollama/response/ResponseAsLabelExtractor.java @@ -33,8 +33,7 @@ public class ResponseAsLabelExtractor private final static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @Override - public List generate(RecommendationEngine aEngine, CAS aCas, - int aNum) + public List generate(RecommendationEngine aEngine, CAS aCas, int aNum) { return null; } diff --git a/inception/inception-io-tei/src/main/java/de/tudarmstadt/ukp/inception/io/tei/dkprobackport/TeiConstants.java b/inception/inception-io-tei/src/main/java/de/tudarmstadt/ukp/inception/io/tei/dkprobackport/TeiConstants.java index 0213d6bf29b..7da9595d4cc 100644 --- a/inception/inception-io-tei/src/main/java/de/tudarmstadt/ukp/inception/io/tei/dkprobackport/TeiConstants.java +++ b/inception/inception-io-tei/src/main/java/de/tudarmstadt/ukp/inception/io/tei/dkprobackport/TeiConstants.java @@ -81,7 +81,7 @@ public final class TeiConstants public static final String ATTR_LEMMA = "lemma"; public static final String ATTR_XML_ID = "xml:id"; - + public static final String TEI_NS = "http://www.tei-c.org/ns/1.0"; public static final QName E_TEI_TEI = new QName(TEI_NS, TAG_TEI_DOC); public static final QName E_TEI_HEADER = new QName(TEI_NS, "teiHeader"); diff --git a/inception/inception-io-xmi/src/main/java/de/tudarmstadt/ukp/inception/io/xmi/dkprobackport/BinaryCasReader.java b/inception/inception-io-xmi/src/main/java/de/tudarmstadt/ukp/inception/io/xmi/dkprobackport/BinaryCasReader.java index f707a5bd6ac..114710ec3d9 100644 --- a/inception/inception-io-xmi/src/main/java/de/tudarmstadt/ukp/inception/io/xmi/dkprobackport/BinaryCasReader.java +++ b/inception/inception-io-xmi/src/main/java/de/tudarmstadt/ukp/inception/io/xmi/dkprobackport/BinaryCasReader.java @@ -174,12 +174,10 @@ public void getNext(CAS aCAS) throws IOException, CollectionException (TypePriorities) null, (FsIndexDescription[]) null).getJCas(); // Create a holder for the CAS metadata - var cms = Serialization - .serializeCASMgr((mergedTypeSystemCas).getCasImpl()); + var cms = Serialization.serializeCASMgr((mergedTypeSystemCas).getCasImpl()); // Reinitialize CAS with merged type system - ((CASImpl) aCAS).getBinaryCasSerDes() - .setupCasFromCasMgrSerializer(cms); + ((CASImpl) aCAS).getBinaryCasSerDes().setupCasFromCasMgrSerializer(cms); } catch (CASException | ResourceInitializationException e) { throw new CollectionException(e); diff --git a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/LearningRecordService.java b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/LearningRecordService.java index ee3e6d87f11..c77a3fa1f68 100644 --- a/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/LearningRecordService.java +++ b/inception/inception-recommendation-api/src/main/java/de/tudarmstadt/ukp/inception/recommendation/api/LearningRecordService.java @@ -83,15 +83,15 @@ void logRecord(String aSessionOwner, SourceDocument aDocument, String aDataOwner * the annotator user * @param aLayer * the layer - * @return if the are any records of type {@link LearningRecordUserAction#SKIPPED} in the history of - * the given layer for the given user. + * @return if the are any records of type {@link LearningRecordUserAction#SKIPPED} in the + * history of the given layer for the given user. * */ boolean hasSkippedSuggestions(String aSessionOwner, User aDataOwner, AnnotationLayer aLayer); /** - * Removes all records of type {@link LearningRecordUserAction#SKIPPED} in the history of the given - * layer for the given user. + * Removes all records of type {@link LearningRecordUserAction#SKIPPED} in the history of the + * given layer for the given user. * * @param aDataOwner * the annotator user diff --git a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java index 6c1c5f23aca..9407c06c0c4 100644 --- a/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java +++ b/inception/inception-recommendation/src/main/java/de/tudarmstadt/ukp/inception/recommendation/service/RecommendationServiceImpl.java @@ -2756,6 +2756,5 @@ public void close() final record ReconciliationResult(int added, int removed, int aged, List suggestions) - { - } + {} } diff --git a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/PolicyCollectionBuilder.java b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/PolicyCollectionBuilder.java index bd9d6d36e7a..0af7f64c74f 100644 --- a/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/PolicyCollectionBuilder.java +++ b/inception/inception-support/src/main/java/de/tudarmstadt/ukp/inception/support/xml/sanitizer/PolicyCollectionBuilder.java @@ -262,8 +262,8 @@ void attributePolicy(QName aElementName, QName aAttributeName, AttributePolicy a private void _attributePolicy(QName aElementName, QName aAttributeName, AttributePolicy aPolicy) { @SuppressWarnings("unchecked") - Map attributePolicies = elementAttributePolicies.computeIfAbsent(aElementName, - k -> mapSupplier.get()); + Map attributePolicies = elementAttributePolicies + .computeIfAbsent(aElementName, k -> mapSupplier.get()); var attributePolicy = attributePolicies.computeIfAbsent(aAttributeName, k -> AttributePolicy.UNDEFINED); diff --git a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/config/AnnotationUIAutoConfiguration.java b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/config/AnnotationUIAutoConfiguration.java index ead14d689e5..7840779d383 100644 --- a/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/config/AnnotationUIAutoConfiguration.java +++ b/inception/inception-ui-annotation/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/annotation/config/AnnotationUIAutoConfiguration.java @@ -86,7 +86,7 @@ public FeatureValueActionUndoSupport featureValueActionUndoSupport() { return new FeatureValueActionUndoSupport(); } - + @Bean public CloseSessionActionBarExtension closeSessionActionBarExtension() { diff --git a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/casdoctor/ProjectCasDoctorPanel.java b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/casdoctor/ProjectCasDoctorPanel.java index 51d6ebfd9a1..55fe20a27cc 100644 --- a/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/casdoctor/ProjectCasDoctorPanel.java +++ b/inception/inception-ui-project/src/main/java/de/tudarmstadt/ukp/clarin/webanno/ui/project/casdoctor/ProjectCasDoctorPanel.java @@ -159,7 +159,8 @@ private void actionRepair(AjaxRequestTarget aTarget, Form aForm) try { casStorageService.forceActionOnCas(sd, INITIAL_CAS_PSEUDO_USER, - (doc, user) -> createOrReadInitialCasWithoutSavingOrChecks(doc, messageSet), + (doc, user) -> createOrReadInitialCasWithoutSavingOrChecks(doc, + messageSet), (cas) -> casDoctor.repair(project, cas, messageSet.messages), // true); } @@ -257,7 +258,8 @@ private void actionCheck(AjaxRequestTarget aTarget, Form aForm) try { objectCount++; casStorageService.forceActionOnCas(sd, INITIAL_CAS_PSEUDO_USER, - (doc, user) -> createOrReadInitialCasWithoutSavingOrChecks(doc, messageSet), + (doc, user) -> createOrReadInitialCasWithoutSavingOrChecks(doc, + messageSet), (cas) -> casDoctor.analyze(project, cas, messageSet.messages), // false); } From 8f1671f753bf81c676e426187d35057734e45816 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sun, 17 Dec 2023 17:45:34 +0100 Subject: [PATCH 4/5] #4381 - Allow users to browse their past activity - Remove test page which is no longer needed --- .../activity/panel/ActivityPanelTestPage.html | 25 ------------ .../activity/panel/ActivityPanelTestPage.java | 40 ------------------- 2 files changed, 65 deletions(-) delete mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.html delete mode 100644 inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.java diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.html b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.html deleted file mode 100644 index a0a2ace8555..00000000000 --- a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - -
- - - diff --git a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.java b/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.java deleted file mode 100644 index 45060b9e9cc..00000000000 --- a/inception/inception-ui-dashboard-activity/src/main/java/de/tudarmstadt/ukp/inception/ui/core/dashboard/activity/panel/ActivityPanelTestPage.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Technische Universität Darmstadt under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The Technische Universität Darmstadt - * licenses this file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package de.tudarmstadt.ukp.inception.ui.core.dashboard.activity.panel; - -import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.NS_PROJECT; -import static de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase.PAGE_PARAM_PROJECT; - -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.wicketstuff.annotation.mount.MountPath; - -import de.tudarmstadt.ukp.clarin.webanno.ui.core.page.ProjectPageBase; - -@MountPath(NS_PROJECT + "/${" + PAGE_PARAM_PROJECT + "}/activities") -public class ActivityPanelTestPage - extends ProjectPageBase -{ - private static final long serialVersionUID = -2301327315880182512L; - - public ActivityPanelTestPage(PageParameters aParameters) - { - super(aParameters); - - add(new ActivityPanel("panel", getProjectModel())); - } -} \ No newline at end of file From 895a16d4b8603ad2efa1becfd9805d2ee2c792f0 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Sun, 17 Dec 2023 18:52:35 +0100 Subject: [PATCH 5/5] #4381 - Allow users to browse their past activity - Removed extra dependencies --- inception/inception-ui-dashboard-activity/pom.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/inception/inception-ui-dashboard-activity/pom.xml b/inception/inception-ui-dashboard-activity/pom.xml index 454a3f7a3a7..cee135c8397 100644 --- a/inception/inception-ui-dashboard-activity/pom.xml +++ b/inception/inception-ui-dashboard-activity/pom.xml @@ -78,10 +78,6 @@ de.tudarmstadt.ukp.inception.app inception-support-bootstrap - - de.tudarmstadt.ukp.inception.app - inception-ui-core - org.springframework @@ -112,18 +108,10 @@ org.apache.wicket wicket-spring - - org.apache.wicket - wicket-request - org.apache.wicket wicket-extensions - - org.wicketstuff - wicketstuff-annotation - org.danekja jdk-serializable-functional