From e53a1797fe324eb6152cb42e7386dd0b046b2f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ckelmann?= Date: Sun, 17 Mar 2024 13:28:49 +0100 Subject: [PATCH 1/6] Display only statistics until today --- .../simple-activity-diagram.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts index dc20fb4..6a07f82 100644 --- a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts +++ b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts @@ -39,7 +39,7 @@ export class SimpleActivityDiagramComponent { createDateRange(): Date[] { const dates: Date[] = []; - for (let i: number = 1; i <= 31; i++) { + for (let i: number = 1; i <= 30; i++) { dates.push(moment().subtract(30-i, 'd').toDate()); } -- 2.34.1 From 985aa5f83b820535e77eb85270dc7f99e21074fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ckelmann?= Date: Sun, 17 Mar 2024 16:01:48 +0100 Subject: [PATCH 2/6] Partial Correct heatMap implementation --- .../heatmap-activity.component.html | 13 +- .../heatmap-activity.component.ts | 288 +++++++++--------- .../taskgroup-activity.component.html | 2 +- .../taskgroup-activity.component.ts | 2 +- 4 files changed, 160 insertions(+), 145 deletions(-) diff --git a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.html b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.html index 9c020bf..94db31a 100644 --- a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.html +++ b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.html @@ -1,8 +1,11 @@ -
+
diff --git a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts index 768ebba..301e822 100644 --- a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts +++ b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts @@ -1,168 +1,180 @@ -import {Component, Input, OnInit, ViewChild} from '@angular/core'; -import {ApexAxisChartSeries, ChartComponent, ChartType} from "ng-apexcharts"; -import {ChartOptions} from "../taskgroup-activity.component"; +import {Component, Input, OnChanges, ViewChild} from '@angular/core'; + +import { + ApexAxisChartSeries, + ApexTitleSubtitle, + ApexDataLabels, + ApexChart, + ApexPlotOptions, ChartComponent, ApexXAxis, ApexYAxis, ApexTooltip +} from "ng-apexcharts"; +import {generate} from "rxjs"; import * as moment from "moment"; import {HistoryService, TaskgroupActivityInfo, TaskgroupPathInfo} from "../../../../api"; - -interface XYData { - x: any, - y: any -} +export type ChartOptions = { + series: ApexAxisChartSeries; + chart: ApexChart; + dataLabels: ApexDataLabels; + title: ApexTitleSubtitle; + plotOptions: ApexPlotOptions; + xAxis: ApexXAxis, + yAxis: ApexYAxis, + tooltip: ApexTooltip +}; @Component({ selector: 'app-heatmap-activity', templateUrl: './heatmap-activity.component.html', styleUrls: ['./heatmap-activity.component.css'] }) -export class HeatmapActivityComponent implements OnInit{ +export class HeatmapActivityComponent implements OnChanges{ - @ViewChild("chart") chart?: ChartComponent; - public chartOptions: Partial = this.generateChartOptions() + @ViewChild("chart") chart: ChartComponent | undefined; + @Input() selectedTaskgroupPath: TaskgroupPathInfo | undefined + public chartOptions: Partial | undefined; - @Input() selectedTaskgroupPath?: TaskgroupPathInfo + maxValue = 120 constructor(private historyService: HistoryService) { + } - ngOnInit() { - this.chartOptions = this.generateChartOptions(); - } - - generateChartOptions(): Partial { - return { - series: this.generateSeries(), - chart: { - height: 350, - type: 'heatmap', - }, - title: { - text: "" - }, - dataLabels: { - enabled: false - }, - colors: ["#008FFB"], - }; - } - - generateSeries() : ApexAxisChartSeries { - const series: ApexAxisChartSeries = [] - + ngOnChanges() { if(this.selectedTaskgroupPath != undefined) { - const startingDate = new Date(2023, 4, 9); - const endDate = new Date(2023, 11, 20); - let activityInfos: TaskgroupActivityInfo[]; this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( - this.selectedTaskgroupPath.rootTasktroup.taskgroupID, - moment().subtract(6, "months").format("YYYY-MM-DD"), - moment().add(2, 'days').format("YYYY-MM-DD"), - true + this.selectedTaskgroupPath!.rootTasktroup.taskgroupID, + moment().startOf('year').format("YYYY-MM-DD"), + moment().endOf("year").format("YYYY-MM-DD"), + false ).subscribe({ next: resp => { - activityInfos = resp; - const data: XYData[][] = []; - let currentDate: moment.Moment = moment(activityInfos[0].date).subtract(moment(activityInfos[0].date).day(), 'days'); - - //Offset until data starts - let index = currentDate.day(); - while(currentDate < moment(activityInfos[0].date)) { - if(data[currentDate.day()] == undefined) { - data[currentDate.day()] = []; + const taskgroupActivityInfo = resp; + console.log("Chat Options") + this.chartOptions = { + series: [ + { + name: "Monday", + data: this.generateData(moment().startOf('year'),taskgroupActivityInfo) + }, + { + name: "Tuesday", + data: this.generateData(moment().startOf('year').add(1, "days"),taskgroupActivityInfo) + }, + { + name: "Wednesday", + data: this.generateData(moment().startOf('year').add(2, "days"), taskgroupActivityInfo) + }, + { + name: "Thursday", + data: this.generateData(moment().startOf('year').add(3, "days"), taskgroupActivityInfo) + }, + { + name: "Friday", + data: this.generateData(moment().startOf('year').add(4, "days"), taskgroupActivityInfo) + }, + { + name: "Saturday", + data: this.generateData(moment().startOf('year').add(5, "days"), taskgroupActivityInfo) + }, + { + name: "Sunday", + data: this.generateData(moment().startOf('year').add(6, "days"), taskgroupActivityInfo) + } + ], + chart: { + height: 350, + type: "heatmap" + }, + xAxis: { + labels: { + show: false + } + }, + yAxis: { + labels: { + show: true + } + }, + plotOptions: { + heatmap: { + shadeIntensity: 0.5, + colorScale: { + ranges: [ + { + from: 0, + to: 5, + name: "low", + color: "#00A100" + }, + { + from: 6, + to: 20, + name: "medium", + color: "#128FD9" + }, + { + from: 21, + to: 45, + name: "high", + color: "#FFB200" + }, + { + from: 46, + to: this.maxValue, + name: "extreme", + color: "#FF0000" + } + ] + } + } + }, + dataLabels: { + enabled: true, + style: { + fontSize: "12px" + } + }, + title: { + text: "HeatMap Chart with Color Range" + }, + tooltip: { + enabled: true, + y: { + formatter: function (value, { seriesIndex, dataPointIndex, w }) { + return "Spent " + value + " Minutes on
" + w.config.series[seriesIndex].data[dataPointIndex].extraInfo.format("dddd, MMMM D, YYYY") + "
" + }, + title: { + formatter: seriesName => "" + } + } } - - - data[currentDate.day()][index] = { - x: moment(currentDate).toDate(), - y: 0 - } - currentDate.add(1, 'days'); - } - - //inside data - activityInfos.forEach(activityInfo => { - const momentDate: moment.Moment = moment(activityInfo.date); - const seriesIndex = momentDate.day(); - - if(data[seriesIndex] == undefined) { - data[seriesIndex] = []; - } - - data[seriesIndex][index] = { - x: activityInfo.date, - y: activityInfo.activeMinutes - } - - - if(seriesIndex == 6) { - index++; - } - }) - - currentDate = moment(activityInfos[activityInfos.length-1].date); - currentDate = currentDate.add(1, "days"); - //offset outside data - for(let i=moment(activityInfos[activityInfos.length-1].date).day(); i<7; i++) { - data[i][index] = { - x: moment(currentDate).toDate(), - y: 0 - } - console.log(currentDate) - currentDate = currentDate.add(1, "days"); - } - - - series.push({ - name: "Saturday", - data: data[6] - }); - series.push({ - name: "Friday", - data: data[5] - }); - series.push({ - name: "Thursday", - data: data[4] - }); - series.push({ - name: "Wednesday", - data: data[3] - }); - series.push({ - name: "Tuesday", - data: data[2] - }); - series.push({ - name: "Monday", - data: data[1] - }); - series.push({ - name: "Sunday", - data: data[0] - }); + }; } }) - return series; - } else { - return series; } - } - generateData(start: moment.Moment, end: moment.Moment) { - const data: TaskgroupActivityInfo[] = []; - let currentDate: moment.Moment = moment(start); - while(currentDate <= end) { - data.push({ - date: currentDate.format("YYYY-MM-DD"), - activeMinutes: Math.floor(Math.random() * (100 + 1)) + private generateData(startingDate: moment.Moment, data: TaskgroupActivityInfo[]) { + let currentDate = startingDate; + + let series = [] + let endingDate = startingDate.clone().endOf('year'); + + while(currentDate.isBefore(endingDate)) { + let x = "w" + currentDate.isoWeek(); + let y = this.findTaskgroupActivityInfoByDate(currentDate, data) + let extraInfo = currentDate.clone() + + series.push({ + x: x, + y: y, + extraInfo: extraInfo }) - currentDate = currentDate.add(1, 'days'); + currentDate = currentDate.add(7, "days"); } - return data; + return series; } - setSelectedTaskgroupPath(taskgroupPath: TaskgroupPathInfo) { - this.selectedTaskgroupPath = taskgroupPath; - this.chartOptions = this.generateChartOptions(); + private findTaskgroupActivityInfoByDate(date: moment.Moment, data: TaskgroupActivityInfo[]) { + return data.find(taskActivity => moment(taskActivity.date).isSame(date)) } } diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html index 59d25d6..3d73666 100644 --- a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html @@ -14,5 +14,5 @@ - +
diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts index 526fdf5..c8bd252 100644 --- a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts @@ -70,7 +70,7 @@ export class TaskgroupActivityComponent implements OnInit{ this.simpleActivityDiagram.setSelectedTaskgroupPath(this.selectedTaskgroupPath!); } - this.heatMap?.setSelectedTaskgroupPath(this.selectedTaskgroupPath!); + //this.heatMap?.setSelectedTaskgroupPath(this.selectedTaskgroupPath!); } onSelectChartType() { -- 2.34.1 From e1f69b340d07669e02f0db420979bb667f7e2967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ckelmann?= Date: Mon, 18 Mar 2024 09:42:56 +0100 Subject: [PATCH 3/6] Adapt Statistic Endpoint for Statistics independent of Taskgroup --- .../api/controller/StatisticController.java | 31 ++----- .../timemanager/history/ActivityInfo.java | 30 +++++++ .../history/TaskgroupActivityInfo.java | 22 +++-- .../core/entities/timemanager/Taskgroup.java | 49 ----------- .../timemanager/ScheduleRepository.java | 3 + .../java/core/services/StatisticService.java | 84 +++++++++++++++++++ openapi.yaml | 31 ++++--- 7 files changed, 151 insertions(+), 99 deletions(-) create mode 100644 backend/src/main/java/core/api/models/timemanager/history/ActivityInfo.java create mode 100644 backend/src/main/java/core/services/StatisticService.java diff --git a/backend/src/main/java/core/api/controller/StatisticController.java b/backend/src/main/java/core/api/controller/StatisticController.java index 36e2e0e..ee66710 100644 --- a/backend/src/main/java/core/api/controller/StatisticController.java +++ b/backend/src/main/java/core/api/controller/StatisticController.java @@ -5,10 +5,7 @@ import core.api.models.timemanager.history.TaskgroupActivityInfo; import core.api.models.timemanager.history.WorkingStatus; import core.entities.timemanager.AbstractSchedule; import core.entities.timemanager.Taskgroup; -import core.services.PermissionResult; -import core.services.ServiceExitCode; -import core.services.TaskScheduleService; -import core.services.TaskgroupService; +import core.services.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContextHolder; @@ -24,7 +21,7 @@ import java.util.*; public class StatisticController { @Autowired private TaskScheduleService taskScheduleService; - @Autowired private TaskgroupService taskgroupService; + @Autowired private StatisticService statisticService; @GetMapping("/history/workingStatus") public ResponseEntity getWorkingStatus() { @@ -42,24 +39,14 @@ public class StatisticController { return ResponseEntity.ok(new WorkingStatus(missedSchedules, activeTime)); } - @GetMapping("/statistics/taskgroup-activity/{taskgroupID}/{startingDate}/{endingDate}/{includeSubTaskgroups}") - public ResponseEntity getTaskgroupActivity(@PathVariable long taskgroupID, @PathVariable String startingDate, @PathVariable String endingDate, @PathVariable boolean includeSubTaskgroups){ - PermissionResult taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName()); - if(taskgroupPermissionResult.isNoPermissions()) { - return ResponseEntity.status(403).body(new SimpleStatusResponse("failed")); - } else if(taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) { - return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); - } + @GetMapping("/statistics/{startingDate}/{endingDate}/{includeSubTaskgroups}") + public ResponseEntity getTaskgroupActivity(@PathVariable String startingDate, @PathVariable String endingDate){ + LocalDate starting = LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + LocalDate ending = LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); - List activityInfos = taskgroupPermissionResult.getResult().calcActivityInfo(includeSubTaskgroups, - LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")), LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"))); - activityInfos.sort(new Comparator() { - @Override - public int compare(TaskgroupActivityInfo o1, TaskgroupActivityInfo o2) { - return o1.getDate().compareTo(o2.getDate()); - } - }); - return ResponseEntity.ok(activityInfos); + var taskgroupActivityInfos = statisticService.calcActivityByUser(SecurityContextHolder.getContext().getAuthentication().getName(), starting, ending); + List outgoingResult = StatisticService.convertInternActivityInfo(taskgroupActivityInfos); + return ResponseEntity.ok(outgoingResult); } @GetMapping("/history/schedules/{date}") diff --git a/backend/src/main/java/core/api/models/timemanager/history/ActivityInfo.java b/backend/src/main/java/core/api/models/timemanager/history/ActivityInfo.java new file mode 100644 index 0000000..44d0b5a --- /dev/null +++ b/backend/src/main/java/core/api/models/timemanager/history/ActivityInfo.java @@ -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; + } +} diff --git a/backend/src/main/java/core/api/models/timemanager/history/TaskgroupActivityInfo.java b/backend/src/main/java/core/api/models/timemanager/history/TaskgroupActivityInfo.java index 51a6d73..fd164df 100644 --- a/backend/src/main/java/core/api/models/timemanager/history/TaskgroupActivityInfo.java +++ b/backend/src/main/java/core/api/models/timemanager/history/TaskgroupActivityInfo.java @@ -4,25 +4,23 @@ import com.fasterxml.jackson.annotation.JsonProperty; import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo; import java.time.LocalDate; +import java.util.List; public class TaskgroupActivityInfo { - @JsonProperty - private LocalDate date; + private TaskgroupEntityInfo taskgroup; + private List activityInfos; - @JsonProperty - private int activeMinutes; - - public TaskgroupActivityInfo(int activeMinutes, LocalDate localDate) { - this.date = localDate; - this.activeMinutes = activeMinutes; + public TaskgroupActivityInfo(TaskgroupEntityInfo taskgroup, List activityInfos) { + this.taskgroup = taskgroup; + this.activityInfos = activityInfos; } - public LocalDate getDate() { - return date; + public TaskgroupEntityInfo getTaskgroup() { + return taskgroup; } - public int getActiveMinutes() { - return activeMinutes; + public List getActivityInfos() { + return activityInfos; } } diff --git a/backend/src/main/java/core/entities/timemanager/Taskgroup.java b/backend/src/main/java/core/entities/timemanager/Taskgroup.java index ffea384..26a6861 100644 --- a/backend/src/main/java/core/entities/timemanager/Taskgroup.java +++ b/backend/src/main/java/core/entities/timemanager/Taskgroup.java @@ -136,54 +136,5 @@ public class Taskgroup { return Objects.hash(taskgroupID); } - public List calcActivityInfo(boolean includeChildTasks, LocalDate start, LocalDate end) { - HashMap activityInfos = new HashMap<>(); - if(includeChildTasks) { - Queue 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 taskgroupActivityInfos = new ArrayList<>(); - for(Map.Entry entry : activityInfos.entrySet()) { - taskgroupActivityInfos.add(new TaskgroupActivityInfo(entry.getValue(), entry.getKey())); - } - return taskgroupActivityInfos; - } } diff --git a/backend/src/main/java/core/repositories/timemanager/ScheduleRepository.java b/backend/src/main/java/core/repositories/timemanager/ScheduleRepository.java index da5e43a..fe3ada9 100644 --- a/backend/src/main/java/core/repositories/timemanager/ScheduleRepository.java +++ b/backend/src/main/java/core/repositories/timemanager/ScheduleRepository.java @@ -27,4 +27,7 @@ public interface ScheduleRepository extends CrudRepository getAllFinishedSchedulesByUser(String username); } diff --git a/backend/src/main/java/core/services/StatisticService.java b/backend/src/main/java/core/services/StatisticService.java new file mode 100644 index 0000000..36f29f0 --- /dev/null +++ b/backend/src/main/java/core/services/StatisticService.java @@ -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> calcActivityByUser(String username, LocalDate startinDate, LocalDate endingDate) { + HashMap> taskgroupActivityInfos = new HashMap<>(); + List startedSchedules = scheduleRepository.getAllFinishedSchedulesByUser(username); + for(AbstractSchedule schedule : startedSchedules) { + Taskgroup topTaskgroup = findTopTaskgroup(schedule.getTask().getTaskgroup()); + HashMap 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 convertInternActivityInfo(HashMap> taskgroupActivity) { + List taskgroupActivityInfos = new ArrayList<>(); + for(Map.Entry> entry: taskgroupActivity.entrySet()) { + List activityInfos = new ArrayList<>(); + for(Map.Entry 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 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; + } +} diff --git a/openapi.yaml b/openapi.yaml index 4df888a..afe6415 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2060,20 +2060,13 @@ paths: schema: type: object $ref: "#/components/schemas/SimpleStatusResponse" - /statistics/taskgroup-activity/{taskgroupID}/{startingDate}/{endingDate}/{includeSubTaskgroups}: + /statistics/{startingDate}/{endingDate}: get: security: - API_TOKEN: [] tags: - history parameters: - - name: taskgroupID - in: path - description: internal id of taskgroup - required: true - schema: - type: number - example: 1 - name: startingDate in: path description: starting date @@ -2088,13 +2081,6 @@ paths: schema: type: string format: date - - name: includeSubTaskgroups - in: path - description: determines whether to include subtaskgroups or not - required: true - schema: - type: boolean - example: false responses: 200: description: Operation successfull @@ -2948,7 +2934,7 @@ components: rootTasktroup: type: object $ref: '#/components/schemas/TaskgroupEntityInfo' - TaskgroupActivityInfo: + ActivityInfo: required: - date - activeMinutes @@ -2961,6 +2947,19 @@ components: type: number description: Number of minutes the task was active 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: required: - duration -- 2.34.1 From 183030611b4f6c0fe8e95a7724f80106c948aac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ckelmann?= Date: Mon, 18 Mar 2024 14:45:25 +0100 Subject: [PATCH 4/6] Select top taskgroups by default in Activity Statistic --- .../api/controller/StatisticController.java | 2 +- .../timemanager/history/ActivityInfo.java | 12 +- frontend/src/api/.openapi-generator/FILES | 1 + frontend/src/api/api/history.service.ts | 22 +-- frontend/src/api/model/activityInfo.ts | 21 +++ frontend/src/api/model/models.ts | 1 + .../src/api/model/taskgroupActivityInfo.ts | 9 +- .../heatmap-activity.component.ts | 6 +- .../simple-activity-diagram.component.html | 2 +- .../simple-activity-diagram.component.ts | 126 +++++++----------- .../taskgroup-activity.component.html | 2 +- .../taskgroup-activity.component.ts | 8 -- 12 files changed, 93 insertions(+), 119 deletions(-) create mode 100644 frontend/src/api/model/activityInfo.ts diff --git a/backend/src/main/java/core/api/controller/StatisticController.java b/backend/src/main/java/core/api/controller/StatisticController.java index ee66710..54f6a53 100644 --- a/backend/src/main/java/core/api/controller/StatisticController.java +++ b/backend/src/main/java/core/api/controller/StatisticController.java @@ -39,7 +39,7 @@ public class StatisticController { return ResponseEntity.ok(new WorkingStatus(missedSchedules, activeTime)); } - @GetMapping("/statistics/{startingDate}/{endingDate}/{includeSubTaskgroups}") + @GetMapping("/statistics/{startingDate}/{endingDate}") public ResponseEntity getTaskgroupActivity(@PathVariable String startingDate, @PathVariable String endingDate){ LocalDate starting = LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); LocalDate ending = LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); diff --git a/backend/src/main/java/core/api/models/timemanager/history/ActivityInfo.java b/backend/src/main/java/core/api/models/timemanager/history/ActivityInfo.java index 44d0b5a..f74dc72 100644 --- a/backend/src/main/java/core/api/models/timemanager/history/ActivityInfo.java +++ b/backend/src/main/java/core/api/models/timemanager/history/ActivityInfo.java @@ -5,11 +5,11 @@ import java.time.LocalDate; public class ActivityInfo { private LocalDate scheduleDate; - private int workedMinutes; + private int activeMinutes; public ActivityInfo(LocalDate scheduleDate, int workedMinutes) { this.scheduleDate = scheduleDate; - this.workedMinutes = workedMinutes; + this.activeMinutes = workedMinutes; } public LocalDate getScheduleDate() { @@ -20,11 +20,11 @@ public class ActivityInfo { this.scheduleDate = scheduleDate; } - public int getWorkedMinutes() { - return workedMinutes; + public int getActiveMinutes() { + return activeMinutes; } - public void setWorkedMinutes(int workedMinutes) { - this.workedMinutes = workedMinutes; + public void setActiveMinutes(int activeMinutes) { + this.activeMinutes = activeMinutes; } } diff --git a/frontend/src/api/.openapi-generator/FILES b/frontend/src/api/.openapi-generator/FILES index 8171b54..9c3f8e8 100644 --- a/frontend/src/api/.openapi-generator/FILES +++ b/frontend/src/api/.openapi-generator/FILES @@ -17,6 +17,7 @@ encoder.ts git_push.sh index.ts model/accountDeleteRequest.ts +model/activityInfo.ts model/advancedScheduleFieldInfo.ts model/advancedScheduleInfo.ts model/advancedScheduleInfoAllOf.ts diff --git a/frontend/src/api/api/history.service.ts b/frontend/src/api/api/history.service.ts index 1e5ec3d..f567d47 100644 --- a/frontend/src/api/api/history.service.ts +++ b/frontend/src/api/api/history.service.ts @@ -203,28 +203,20 @@ export class HistoryService { } /** - * @param taskgroupID internal id of taskgroup * @param startingDate starting date * @param endingDate starting date - * @param includeSubTaskgroups determines whether to include subtaskgroups or not * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet(taskgroupID: number, startingDate: string, endingDate: string, includeSubTaskgroups: boolean, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; - public statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet(taskgroupID: number, startingDate: string, endingDate: string, includeSubTaskgroups: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; - public statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet(taskgroupID: number, startingDate: string, endingDate: string, includeSubTaskgroups: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; - public statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet(taskgroupID: number, startingDate: string, endingDate: string, includeSubTaskgroups: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { - if (taskgroupID === null || taskgroupID === undefined) { - throw new Error('Required parameter taskgroupID was null or undefined when calling statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet.'); - } + public statisticsStartingDateEndingDateGet(startingDate: string, endingDate: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public statisticsStartingDateEndingDateGet(startingDate: string, endingDate: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public statisticsStartingDateEndingDateGet(startingDate: string, endingDate: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public statisticsStartingDateEndingDateGet(startingDate: string, endingDate: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { if (startingDate === null || startingDate === undefined) { - throw new Error('Required parameter startingDate was null or undefined when calling statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet.'); + throw new Error('Required parameter startingDate was null or undefined when calling statisticsStartingDateEndingDateGet.'); } if (endingDate === null || endingDate === undefined) { - throw new Error('Required parameter endingDate was null or undefined when calling statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet.'); - } - if (includeSubTaskgroups === null || includeSubTaskgroups === undefined) { - throw new Error('Required parameter includeSubTaskgroups was null or undefined when calling statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet.'); + throw new Error('Required parameter endingDate was null or undefined when calling statisticsStartingDateEndingDateGet.'); } let localVarHeaders = this.defaultHeaders; @@ -259,7 +251,7 @@ export class HistoryService { responseType_ = 'text'; } - return this.httpClient.get>(`${this.configuration.basePath}/statistics/taskgroup-activity/${encodeURIComponent(String(taskgroupID))}/${encodeURIComponent(String(startingDate))}/${encodeURIComponent(String(endingDate))}/${encodeURIComponent(String(includeSubTaskgroups))}`, + return this.httpClient.get>(`${this.configuration.basePath}/statistics/${encodeURIComponent(String(startingDate))}/${encodeURIComponent(String(endingDate))}`, { context: localVarHttpContext, responseType: responseType_, diff --git a/frontend/src/api/model/activityInfo.ts b/frontend/src/api/model/activityInfo.ts new file mode 100644 index 0000000..80da109 --- /dev/null +++ b/frontend/src/api/model/activityInfo.ts @@ -0,0 +1,21 @@ +/** + * API Title + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ActivityInfo { + date: string; + /** + * Number of minutes the task was active + */ + activeMinutes: number; +} + diff --git a/frontend/src/api/model/models.ts b/frontend/src/api/model/models.ts index 12edcd1..ec5d33b 100644 --- a/frontend/src/api/model/models.ts +++ b/frontend/src/api/model/models.ts @@ -1,4 +1,5 @@ export * from './accountDeleteRequest'; +export * from './activityInfo'; export * from './advancedScheduleFieldInfo'; export * from './advancedScheduleInfo'; export * from './advancedScheduleInfoAllOf'; diff --git a/frontend/src/api/model/taskgroupActivityInfo.ts b/frontend/src/api/model/taskgroupActivityInfo.ts index fc6a973..0c4d052 100644 --- a/frontend/src/api/model/taskgroupActivityInfo.ts +++ b/frontend/src/api/model/taskgroupActivityInfo.ts @@ -9,13 +9,12 @@ * https://openapi-generator.tech * Do not edit the class manually. */ +import { ActivityInfo } from './activityInfo'; +import { TaskgroupEntityInfo } from './taskgroupEntityInfo'; export interface TaskgroupActivityInfo { - date: string; - /** - * Number of minutes the task was active - */ - activeMinutes: number; + taskgroup: TaskgroupEntityInfo; + activityInfos: Array; } diff --git a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts index 301e822..e2fb081 100644 --- a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts +++ b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts @@ -40,7 +40,7 @@ export class HeatmapActivityComponent implements OnChanges{ ngOnChanges() { if(this.selectedTaskgroupPath != undefined) { - this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( + /*this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( this.selectedTaskgroupPath!.rootTasktroup.taskgroupID, moment().startOf('year').format("YYYY-MM-DD"), moment().endOf("year").format("YYYY-MM-DD"), @@ -149,7 +149,7 @@ export class HeatmapActivityComponent implements OnChanges{ } }; } - }) + })*/ } } @@ -175,6 +175,6 @@ export class HeatmapActivityComponent implements OnChanges{ } private findTaskgroupActivityInfoByDate(date: moment.Moment, data: TaskgroupActivityInfo[]) { - return data.find(taskActivity => moment(taskActivity.date).isSame(date)) + //return data.find(taskActivity => moment(taskActivity.date).isSame(date)) } } diff --git a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.html b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.html index bda266d..46622b2 100644 --- a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.html +++ b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.html @@ -1,5 +1,5 @@
- = this.generateChartOptions() + + series: ApexAxisChartSeries = [] + chartOptions: Partial | undefined; constructor(private taskgroupService: TaskgroupService, private historyService: HistoryService) { } + ngOnChanges() { + this.series = []; + const startingDate = moment(this.dateRange[0]).format("yyyy-MM-DD"); + const endingDate = moment(this.dateRange[this.dateRange.length-1]).format("yyyy-MM-DD"); + this.historyService.statisticsStartingDateEndingDateGet(startingDate, endingDate).subscribe({ + next: resp => { + resp.forEach(taskgroupActivityInfo => { + const data: any[] = []; + this.dateRange.map(date => { + const selectedActivity = taskgroupActivityInfo.activityInfos.find(activity => moment(date).isSame(moment(activity.date), "day")) + if(selectedActivity != undefined) { + data.push(selectedActivity.activeMinutes) + } else { + data.push(0) + } + }) + + this.series.push({ + name: taskgroupActivityInfo.taskgroup.taskgroupName, + data: data + }) + }) + + this.chartOptions = { + series: this.series, + chart: { + height: 350, + type: this.selectedChartype as ChartType, + stacked: true + }, + title: { + text: "" + }, + xaxis: { + categories: this.selectedDateRange.map(date => date.toDateString()) + } + } + } + }) + } createDateRange(): Date[] { const dates: Date[] = []; @@ -63,84 +105,10 @@ export class SimpleActivityDiagramComponent { console.log("min " + moment(changeContext.value).format("YYYY-MM-DD")); console.log("max " + moment(changeContext.highValue!).format("YYYY-MM-DD")) this.selectedDateRange = this.createDateRangeBetween(moment(changeContext.value), moment(changeContext.highValue!)) - this.chartOptions = this.generateChartOptions() - } - - generateChartOptions(): Partial { - return { - series: this.generateSeries(), - chart: { - height: 350, - type: this.selectedChartype as ChartType, - stacked: true - }, - title: { - text: "" - }, - xaxis: { - categories: this.selectedDateRange.map(date => date.toDateString()) - } - }; - } - - generateSeries() : ApexAxisChartSeries { - const series: ApexAxisChartSeries = [] - - if(this.selectedTaskgroupPath != undefined) { - this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( - this.selectedTaskgroupPath!.rootTasktroup.taskgroupID, - moment(this.selectedDateRange[0]).format("YYYY-MM-DD"), - moment(this.selectedDateRange[this.selectedDateRange.length-1]).format("YYYY-MM-DD"), - false - ).subscribe({ - next: resp => { - series.push( - { - name: this.selectedTaskgroupPath!.rootTasktroup.taskgroupName, - data: resp.map(dailyActivityInfo => dailyActivityInfo.activeMinutes) - } - ); - } - }) - } - - - - - this.selectedTaskgroupPath?.directChildren.forEach(taskgroup => { - - this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( - taskgroup.taskgroupID, - moment(this.selectedDateRange[0]).format("YYYY-MM-DD"), - moment(this.selectedDateRange[this.selectedDateRange.length-1]).format("YYYY-MM-DD"), true - ).subscribe({ - next: resp => { - series.push( - { - name: taskgroup.taskgroupName, - data: resp.map(dailyActivityInfo => dailyActivityInfo.activeMinutes) - } - ) - } - }) - - }) - console.log(series); - return series; - - } - - updateSerieSelection() { - this.chartOptions = this.generateChartOptions() } setSelectedChartType(selectedChartype: string) { - this.selectedChartype = selectedChartype; - this.updateSerieSelection(); - } + //this.selectedChartype = selectedChartype; - setSelectedTaskgroupPath(selectedTaskgroupPath: TaskgroupPathInfo) { - this.selectedTaskgroupPath = selectedTaskgroupPath; - this.updateSerieSelection(); } } diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html index 3d73666..7ca0e64 100644 --- a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html @@ -2,7 +2,7 @@ Taskgroup - + {{topping.taskgroupPath}} diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts index c8bd252..28ee779 100644 --- a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts @@ -65,14 +65,6 @@ export class TaskgroupActivityComponent implements OnInit{ } - onSelectTaskgroupPath() { - if(this.simpleActivityDiagram != undefined) { - this.simpleActivityDiagram.setSelectedTaskgroupPath(this.selectedTaskgroupPath!); - } - - //this.heatMap?.setSelectedTaskgroupPath(this.selectedTaskgroupPath!); - } - onSelectChartType() { if(this.simpleActivityDiagram != undefined) { this.simpleActivityDiagram.setSelectedChartType(this.selectedChartype); -- 2.34.1 From bbdeaabd1e604593724e26e420eb515d5209c0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ckelmann?= Date: Mon, 18 Mar 2024 15:23:40 +0100 Subject: [PATCH 5/6] Activity of Taskgroups --- .../api/controller/StatisticController.java | 18 +++- .../java/core/services/StatisticService.java | 33 ++++++- frontend/src/api/api/history.service.ts | 65 +++++++++++++ .../simple-activity-diagram.component.ts | 96 ++++++++++++------- 4 files changed, 174 insertions(+), 38 deletions(-) diff --git a/backend/src/main/java/core/api/controller/StatisticController.java b/backend/src/main/java/core/api/controller/StatisticController.java index 54f6a53..8afa704 100644 --- a/backend/src/main/java/core/api/controller/StatisticController.java +++ b/backend/src/main/java/core/api/controller/StatisticController.java @@ -22,6 +22,7 @@ public class StatisticController { @Autowired private TaskScheduleService taskScheduleService; @Autowired private StatisticService statisticService; + @Autowired private TaskgroupService taskgroupService; @GetMapping("/history/workingStatus") public ResponseEntity getWorkingStatus() { @@ -44,7 +45,22 @@ public class StatisticController { LocalDate starting = LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); LocalDate ending = LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); - var taskgroupActivityInfos = statisticService.calcActivityByUser(SecurityContextHolder.getContext().getAuthentication().getName(), starting, ending); + var taskgroupActivityInfos = statisticService.calcActivityByUser(null, SecurityContextHolder.getContext().getAuthentication().getName(), starting, ending); + List outgoingResult = StatisticService.convertInternActivityInfo(taskgroupActivityInfos); + return ResponseEntity.ok(outgoingResult); + } + + @GetMapping("/statistics/{taskgroupID}/{startingDate}/{endingDate}") + public ResponseEntity getTaskgroupActivity(@PathVariable long taskgroupID, @PathVariable String startingDate, @PathVariable String endingDate){ + LocalDate starting = LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + LocalDate ending = LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + var permissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, username); + if(permissionResult.hasIssue()) { + return permissionResult.mapToResponseEntity(); + } + + var taskgroupActivityInfos = statisticService.calcActivityByUser(permissionResult.getResult(), username, starting, ending); List outgoingResult = StatisticService.convertInternActivityInfo(taskgroupActivityInfos); return ResponseEntity.ok(outgoingResult); } diff --git a/backend/src/main/java/core/services/StatisticService.java b/backend/src/main/java/core/services/StatisticService.java index 36f29f0..2a6fddc 100644 --- a/backend/src/main/java/core/services/StatisticService.java +++ b/backend/src/main/java/core/services/StatisticService.java @@ -21,11 +21,24 @@ public class StatisticService { @Autowired private ScheduleRepository scheduleRepository; - public HashMap> calcActivityByUser(String username, LocalDate startinDate, LocalDate endingDate) { + public HashMap> calcActivityByUser(Taskgroup taskgroup, String username, LocalDate startinDate, LocalDate endingDate) { HashMap> taskgroupActivityInfos = new HashMap<>(); List startedSchedules = scheduleRepository.getAllFinishedSchedulesByUser(username); for(AbstractSchedule schedule : startedSchedules) { - Taskgroup topTaskgroup = findTopTaskgroup(schedule.getTask().getTaskgroup()); + if(schedule.getStartTime().toLocalDate().isAfter(endingDate) || schedule.getStopTime().toLocalDate().isBefore(startinDate)) { + continue; + } + + Taskgroup topTaskgroup; + if(taskgroup == null) { + topTaskgroup = findTopTaskgroup(schedule.getTask().getTaskgroup()); + } else { + topTaskgroup = findTaskgroupOfLayer(taskgroup, schedule.getTask().getTaskgroup()); + if(topTaskgroup == null) { + continue; + } + } + HashMap taskgroupActivity; if(taskgroupActivityInfos.containsKey(topTaskgroup)) { taskgroupActivity = taskgroupActivityInfos.get(topTaskgroup); @@ -81,4 +94,20 @@ public class StatisticService { } return currentTaskgroup; } + + private Taskgroup findTaskgroupOfLayer(Taskgroup targetLayer, Taskgroup childTaskgroup) { + if(targetLayer.getTaskgroupID() == childTaskgroup.getTaskgroupID()) { + return childTaskgroup; + } + + Taskgroup currentTaskgroup = childTaskgroup; + while (currentTaskgroup.getParent() != null && currentTaskgroup.getParent().getTaskgroupID() != targetLayer.getTaskgroupID()) { + currentTaskgroup = currentTaskgroup.getParent(); + } + + if(currentTaskgroup.getParent() == null) { + return null; + } + return currentTaskgroup; + } } diff --git a/frontend/src/api/api/history.service.ts b/frontend/src/api/api/history.service.ts index f567d47..794a3ed 100644 --- a/frontend/src/api/api/history.service.ts +++ b/frontend/src/api/api/history.service.ts @@ -263,4 +263,69 @@ export class HistoryService { ); } + /** + * @param taskgroupID internal id of taskgroup + * @param startingDate starting date + * @param endingDate starting date + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public statisticsTaskgroupIDStartingDateEndingDateGet(taskgroupID: number, startingDate: string, endingDate: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>; + public statisticsTaskgroupIDStartingDateEndingDateGet(taskgroupID: number, startingDate: string, endingDate: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public statisticsTaskgroupIDStartingDateEndingDateGet(taskgroupID: number, startingDate: string, endingDate: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable>>; + public statisticsTaskgroupIDStartingDateEndingDateGet(taskgroupID: number, startingDate: string, endingDate: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable { + if (taskgroupID === null || taskgroupID === undefined) { + throw new Error('Required parameter taskgroupID was null or undefined when calling statisticsTaskgroupIDStartingDateEndingDateGet.'); + } + if (startingDate === null || startingDate === undefined) { + throw new Error('Required parameter startingDate was null or undefined when calling statisticsTaskgroupIDStartingDateEndingDateGet.'); + } + if (endingDate === null || endingDate === undefined) { + throw new Error('Required parameter endingDate was null or undefined when calling statisticsTaskgroupIDStartingDateEndingDateGet.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (API_TOKEN) required + localVarCredential = this.configuration.lookupCredential('API_TOKEN'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + + let responseType_: 'text' | 'json' = 'json'; + if(localVarHttpHeaderAcceptSelected && localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } + + return this.httpClient.get>(`${this.configuration.basePath}/statistics/${encodeURIComponent(String(taskgroupID))}/${encodeURIComponent(String(startingDate))}/${encodeURIComponent(String(endingDate))}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + reportProgress: reportProgress + } + ); + } + } diff --git a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts index 64ebb50..e912f58 100644 --- a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts +++ b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts @@ -2,7 +2,7 @@ import {Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild} from '@an import {FormControl} from "@angular/forms"; import {ChangeContext, LabelType, Options} from "ngx-slider-v2"; import {ApexAxisChartSeries, ChartComponent, ChartType} from "ng-apexcharts"; -import {HistoryService, TaskgroupPathInfo, TaskgroupService} from "../../../../api"; +import {HistoryService, TaskgroupActivityInfo, TaskgroupPathInfo, TaskgroupService} from "../../../../api"; import * as moment from "moment/moment"; import {ChartOptions} from "../taskgroup-activity.component"; @@ -32,51 +32,77 @@ export class SimpleActivityDiagramComponent implements OnChanges { @ViewChild("chart") chart?: ChartComponent; - series: ApexAxisChartSeries = [] chartOptions: Partial | undefined; constructor(private taskgroupService: TaskgroupService, private historyService: HistoryService) { } ngOnChanges() { - this.series = []; + this.fetchData() + } + + fetchData() { const startingDate = moment(this.dateRange[0]).format("yyyy-MM-DD"); const endingDate = moment(this.dateRange[this.dateRange.length-1]).format("yyyy-MM-DD"); - this.historyService.statisticsStartingDateEndingDateGet(startingDate, endingDate).subscribe({ - next: resp => { - resp.forEach(taskgroupActivityInfo => { - const data: any[] = []; - this.dateRange.map(date => { - const selectedActivity = taskgroupActivityInfo.activityInfos.find(activity => moment(date).isSame(moment(activity.date), "day")) - if(selectedActivity != undefined) { - data.push(selectedActivity.activeMinutes) - } else { - data.push(0) - } - }) - this.series.push({ - name: taskgroupActivityInfo.taskgroup.taskgroupName, - data: data - }) - }) - - this.chartOptions = { - series: this.series, - chart: { - height: 350, - type: this.selectedChartype as ChartType, - stacked: true - }, - title: { - text: "" - }, - xaxis: { - categories: this.selectedDateRange.map(date => date.toDateString()) - } + if(this.selectedTaskgroupPath == undefined) { + this.historyService.statisticsStartingDateEndingDateGet(startingDate, endingDate).subscribe({ + next: resp => { + const series = this.createChartSeries(resp); + this.chartOptions = this.createChartOptions(series); } - } + }) + } else { + this.historyService.statisticsTaskgroupIDStartingDateEndingDateGet( + this.selectedTaskgroupPath.rootTasktroup.taskgroupID, + startingDate, + endingDate + ).subscribe({ + next: resp => { + const series = this.createChartSeries(resp); + this.chartOptions = this.createChartOptions(series) + } + }) + } + } + + createChartSeries(data: TaskgroupActivityInfo[]) { + const series: ApexAxisChartSeries = []; + data.forEach(taskgroupActivityInfo => { + const data: any[] = []; + this.dateRange.map(date => { + const selectedActivity = taskgroupActivityInfo.activityInfos.find(activity => moment(date).isSame(moment(activity.date), "day")) + if(selectedActivity != undefined) { + data.push(selectedActivity.activeMinutes) + } else { + data.push(0) + } + }) + + series.push({ + name: taskgroupActivityInfo.taskgroup.taskgroupName, + data: data + }) }) + + return series; + } + + createChartOptions(series: any[] = []) { + return { + series: series, + chart: { + height: 350, + type: this.selectedChartype as ChartType, + stacked: true + }, + title: { + text: "" + }, + xaxis: { + categories: this.selectedDateRange.map(date => date.toDateString()) + } + } } createDateRange(): Date[] { -- 2.34.1 From 28f390f88a2e30f1c42081956a578aa01928f9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6ckelmann?= Date: Mon, 18 Mar 2024 15:24:10 +0100 Subject: [PATCH 6/6] Document additional endpoint to get taskgroup specific activity info --- openapi.yaml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index afe6415..627d7b4 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2102,6 +2102,55 @@ paths: application/json: schema: $ref: '#/components/schemas/SimpleStatusResponse' + /statistics/{taskgroupID}/{startingDate}/{endingDate}: + get: + security: + - API_TOKEN: [] + tags: + - history + parameters: + - name: taskgroupID + in: path + description: internal id of taskgroup + required: true + schema: + type: number + example: 1 + - name: startingDate + in: path + description: starting date + required: true + schema: + type: string + format: date + - name: endingDate + in: path + description: starting date + required: true + schema: + type: string + format: date + responses: + 200: + description: Operation successfull + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TaskgroupActivityInfo' + 403: + description: No permission + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' + 404: + description: Schedule not found + content: + application/json: + schema: + $ref: '#/components/schemas/SimpleStatusResponse' /history/schedules/{date}: get: security: -- 2.34.1