Adapt Statistic Endpoint for Statistics independent of Taskgroup
All checks were successful
Java CI with Maven / build-and-push-frontend (push) Successful in 21s
Java CI with Maven / build-and-push-backend (push) Successful in 11s

This commit is contained in:
Sebastian Böckelmann 2024-03-18 09:42:56 +01:00
parent 985aa5f83b
commit e1f69b340d
7 changed files with 151 additions and 99 deletions

View File

@ -5,10 +5,7 @@ import core.api.models.timemanager.history.TaskgroupActivityInfo;
import core.api.models.timemanager.history.WorkingStatus; import core.api.models.timemanager.history.WorkingStatus;
import core.entities.timemanager.AbstractSchedule; import core.entities.timemanager.AbstractSchedule;
import core.entities.timemanager.Taskgroup; import core.entities.timemanager.Taskgroup;
import core.services.PermissionResult; import core.services.*;
import core.services.ServiceExitCode;
import core.services.TaskScheduleService;
import core.services.TaskgroupService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -24,7 +21,7 @@ import java.util.*;
public class StatisticController { public class StatisticController {
@Autowired private TaskScheduleService taskScheduleService; @Autowired private TaskScheduleService taskScheduleService;
@Autowired private TaskgroupService taskgroupService; @Autowired private StatisticService statisticService;
@GetMapping("/history/workingStatus") @GetMapping("/history/workingStatus")
public ResponseEntity<?> getWorkingStatus() { public ResponseEntity<?> getWorkingStatus() {
@ -42,24 +39,14 @@ public class StatisticController {
return ResponseEntity.ok(new WorkingStatus(missedSchedules, activeTime)); return ResponseEntity.ok(new WorkingStatus(missedSchedules, activeTime));
} }
@GetMapping("/statistics/taskgroup-activity/{taskgroupID}/{startingDate}/{endingDate}/{includeSubTaskgroups}") @GetMapping("/statistics/{startingDate}/{endingDate}/{includeSubTaskgroups}")
public ResponseEntity<?> getTaskgroupActivity(@PathVariable long taskgroupID, @PathVariable String startingDate, @PathVariable String endingDate, @PathVariable boolean includeSubTaskgroups){ public ResponseEntity<?> getTaskgroupActivity(@PathVariable String startingDate, @PathVariable String endingDate){
PermissionResult<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName()); LocalDate starting = LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
if(taskgroupPermissionResult.isNoPermissions()) { LocalDate ending = LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
} else if(taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
}
List<TaskgroupActivityInfo> activityInfos = taskgroupPermissionResult.getResult().calcActivityInfo(includeSubTaskgroups, var taskgroupActivityInfos = statisticService.calcActivityByUser(SecurityContextHolder.getContext().getAuthentication().getName(), starting, ending);
LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")), LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"))); List<TaskgroupActivityInfo> outgoingResult = StatisticService.convertInternActivityInfo(taskgroupActivityInfos);
activityInfos.sort(new Comparator<TaskgroupActivityInfo>() { return ResponseEntity.ok(outgoingResult);
@Override
public int compare(TaskgroupActivityInfo o1, TaskgroupActivityInfo o2) {
return o1.getDate().compareTo(o2.getDate());
}
});
return ResponseEntity.ok(activityInfos);
} }
@GetMapping("/history/schedules/{date}") @GetMapping("/history/schedules/{date}")

View File

@ -0,0 +1,30 @@
package core.api.models.timemanager.history;
import java.time.LocalDate;
public class ActivityInfo {
private LocalDate scheduleDate;
private int workedMinutes;
public ActivityInfo(LocalDate scheduleDate, int workedMinutes) {
this.scheduleDate = scheduleDate;
this.workedMinutes = workedMinutes;
}
public LocalDate getScheduleDate() {
return scheduleDate;
}
public void setScheduleDate(LocalDate scheduleDate) {
this.scheduleDate = scheduleDate;
}
public int getWorkedMinutes() {
return workedMinutes;
}
public void setWorkedMinutes(int workedMinutes) {
this.workedMinutes = workedMinutes;
}
}

View File

@ -4,25 +4,23 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo; import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
public class TaskgroupActivityInfo { public class TaskgroupActivityInfo {
@JsonProperty private TaskgroupEntityInfo taskgroup;
private LocalDate date; private List<ActivityInfo> activityInfos;
@JsonProperty public TaskgroupActivityInfo(TaskgroupEntityInfo taskgroup, List<ActivityInfo> activityInfos) {
private int activeMinutes; this.taskgroup = taskgroup;
this.activityInfos = activityInfos;
public TaskgroupActivityInfo(int activeMinutes, LocalDate localDate) {
this.date = localDate;
this.activeMinutes = activeMinutes;
} }
public LocalDate getDate() { public TaskgroupEntityInfo getTaskgroup() {
return date; return taskgroup;
} }
public int getActiveMinutes() { public List<ActivityInfo> getActivityInfos() {
return activeMinutes; return activityInfos;
} }
} }

View File

@ -136,54 +136,5 @@ public class Taskgroup {
return Objects.hash(taskgroupID); return Objects.hash(taskgroupID);
} }
public List<TaskgroupActivityInfo> calcActivityInfo(boolean includeChildTasks, LocalDate start, LocalDate end) {
HashMap<LocalDate, Integer> activityInfos = new HashMap<>();
if(includeChildTasks) {
Queue<Taskgroup> queue = new LinkedList<>(children);
while(!queue.isEmpty()) {
Taskgroup childTraskgroup = queue.poll();
LocalDate currentDate = start;
while(!currentDate.isAfter(end)) {
int activeMinutes = 0;
for(Task task : childTraskgroup.getTasks()) {
activeMinutes += task.calcOverallActivityInfo(currentDate);
}
if(activityInfos.containsKey(currentDate)) {
activityInfos.put(currentDate, activityInfos.get(currentDate) + activeMinutes);
} else {
activityInfos.put(currentDate, activeMinutes);
}
currentDate = currentDate.plusDays(1);
}
queue.addAll(childTraskgroup.getChildren());
}
}
LocalDate currentDate = start;
while(!currentDate.isAfter(end)) {
int activeMinutes = 0;
for(Task task : tasks) {
activeMinutes += task.calcOverallActivityInfo(currentDate);
}
if(activityInfos.containsKey(currentDate)) {
activityInfos.put(currentDate, activityInfos.get(currentDate) + activeMinutes);
} else {
activityInfos.put(currentDate, activeMinutes);
}
currentDate = currentDate.plusDays(1);
}
List<TaskgroupActivityInfo> taskgroupActivityInfos = new ArrayList<>();
for(Map.Entry<LocalDate, Integer> entry : activityInfos.entrySet()) {
taskgroupActivityInfos.add(new TaskgroupActivityInfo(entry.getValue(), entry.getKey()));
}
return taskgroupActivityInfos;
}
} }

View File

@ -27,4 +27,7 @@ public interface ScheduleRepository extends CrudRepository<AbstractSchedule, Lon
@Transactional @Transactional
@Query(value = "DELETE FROM AbstractSchedule a WHERE a.task IN (SELECT t FROM Task t WHERE t.taskgroup = ?1)") @Query(value = "DELETE FROM AbstractSchedule a WHERE a.task IN (SELECT t FROM Task t WHERE t.taskgroup = ?1)")
void deleteByTaskgroup(Taskgroup taskgroup); void deleteByTaskgroup(Taskgroup taskgroup);
@Query(value = "SELECT s FROM AbstractSchedule s WHERE s.task.taskgroup.user.username = ?1 AND s.startTime is NOT NULL AND s.stopTime IS NOT NULL")
List<AbstractSchedule> getAllFinishedSchedulesByUser(String username);
} }

View File

@ -0,0 +1,84 @@
package core.services;
import core.api.models.timemanager.history.ActivityInfo;
import core.api.models.timemanager.history.TaskgroupActivityInfo;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import core.entities.timemanager.AbstractSchedule;
import core.entities.timemanager.Taskgroup;
import core.repositories.timemanager.ScheduleRepository;
import core.repositories.timemanager.TaskgroupRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
@Service
public class StatisticService {
@Autowired private ScheduleRepository scheduleRepository;
public HashMap<Taskgroup, HashMap<LocalDate, Integer>> calcActivityByUser(String username, LocalDate startinDate, LocalDate endingDate) {
HashMap<Taskgroup, HashMap<LocalDate, Integer>> taskgroupActivityInfos = new HashMap<>();
List<AbstractSchedule> startedSchedules = scheduleRepository.getAllFinishedSchedulesByUser(username);
for(AbstractSchedule schedule : startedSchedules) {
Taskgroup topTaskgroup = findTopTaskgroup(schedule.getTask().getTaskgroup());
HashMap<LocalDate, Integer> taskgroupActivity;
if(taskgroupActivityInfos.containsKey(topTaskgroup)) {
taskgroupActivity = taskgroupActivityInfos.get(topTaskgroup);
} else {
taskgroupActivity = new HashMap<>();
taskgroupActivityInfos.put(topTaskgroup, taskgroupActivity);
}
if(schedule.getStartTime().toLocalDate().isEqual(schedule.getStopTime().toLocalDate())) {
insertActivity(taskgroupActivity, schedule.getStartTime().toLocalDate(), schedule.getActiveTime());
} else {
//Starting Date
LocalDateTime startingDayEnd = schedule.getStartTime().toLocalDate().atTime(LocalTime.MAX);
Duration startingActivity = Duration.between(schedule.getStartTime(), startingDayEnd);
insertActivity(taskgroupActivity, schedule.getStartTime().toLocalDate(), (int) startingActivity.toMinutes());
//Ending Date
LocalDateTime endingDayStart = schedule.getStopTime().toLocalDate().atStartOfDay();
Duration endingActivity = Duration.between(endingDayStart, schedule.getStopTime());
insertActivity(taskgroupActivity, schedule.getStopTime().toLocalDate(), (int) endingActivity.toMinutes());
}
}
return taskgroupActivityInfos;
}
public static List<TaskgroupActivityInfo> convertInternActivityInfo(HashMap<Taskgroup, HashMap<LocalDate, Integer>> taskgroupActivity) {
List<TaskgroupActivityInfo> taskgroupActivityInfos = new ArrayList<>();
for(Map.Entry<Taskgroup, HashMap<LocalDate, Integer>> entry: taskgroupActivity.entrySet()) {
List<ActivityInfo> activityInfos = new ArrayList<>();
for(Map.Entry<LocalDate, Integer> dateActivity : entry.getValue().entrySet()) {
activityInfos.add(new ActivityInfo(dateActivity.getKey(), dateActivity.getValue()));
}
taskgroupActivityInfos.add(new TaskgroupActivityInfo(new TaskgroupEntityInfo(entry.getKey()), activityInfos));
}
return taskgroupActivityInfos;
}
private static void insertActivity(HashMap<LocalDate, Integer> activityInfo, LocalDate date, int deltaActivity) {
if(activityInfo.containsKey(date)) {
int activity = activityInfo.get(date);
activity += deltaActivity;
activityInfo.put(date, activity);
} else {
activityInfo.put(date, deltaActivity);
}
}
private Taskgroup findTopTaskgroup(Taskgroup taskgroup) {
Taskgroup currentTaskgroup = taskgroup;
while(currentTaskgroup.getParent() != null) {
currentTaskgroup = currentTaskgroup.getParent();
}
return currentTaskgroup;
}
}

View File

@ -2060,20 +2060,13 @@ paths:
schema: schema:
type: object type: object
$ref: "#/components/schemas/SimpleStatusResponse" $ref: "#/components/schemas/SimpleStatusResponse"
/statistics/taskgroup-activity/{taskgroupID}/{startingDate}/{endingDate}/{includeSubTaskgroups}: /statistics/{startingDate}/{endingDate}:
get: get:
security: security:
- API_TOKEN: [] - API_TOKEN: []
tags: tags:
- history - history
parameters: parameters:
- name: taskgroupID
in: path
description: internal id of taskgroup
required: true
schema:
type: number
example: 1
- name: startingDate - name: startingDate
in: path in: path
description: starting date description: starting date
@ -2088,13 +2081,6 @@ paths:
schema: schema:
type: string type: string
format: date format: date
- name: includeSubTaskgroups
in: path
description: determines whether to include subtaskgroups or not
required: true
schema:
type: boolean
example: false
responses: responses:
200: 200:
description: Operation successfull description: Operation successfull
@ -2948,7 +2934,7 @@ components:
rootTasktroup: rootTasktroup:
type: object type: object
$ref: '#/components/schemas/TaskgroupEntityInfo' $ref: '#/components/schemas/TaskgroupEntityInfo'
TaskgroupActivityInfo: ActivityInfo:
required: required:
- date - date
- activeMinutes - activeMinutes
@ -2961,6 +2947,19 @@ components:
type: number type: number
description: Number of minutes the task was active description: Number of minutes the task was active
example: 122 example: 122
TaskgroupActivityInfo:
required:
- taskgroup
- activityInfos
additionalProperties: false
properties:
taskgroup:
type: object
$ref: '#/components/schemas/TaskgroupEntityInfo'
activityInfos:
type: array
items:
$ref: '#/components/schemas/ActivityInfo'
ManualScheduleStopInfo: ManualScheduleStopInfo:
required: required:
- duration - duration