issue-53 #78

Merged
sebastian merged 5 commits from issue-53 into master 2023-11-19 15:37:23 +01:00
11 changed files with 240 additions and 47 deletions
Showing only changes of commit 882169840d - Show all commits

View File

@ -1,21 +1,30 @@
package core.api.controller;
import core.api.models.auth.SimpleStatusResponse;
import core.api.models.timemanager.history.TaskgroupActivityInfo;
import core.api.models.timemanager.history.WorkingStatus;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import core.entities.timemanager.AbstractSchedule;
import core.entities.timemanager.Task;
import core.entities.timemanager.Taskgroup;
import core.services.PermissionResult;
import core.services.ServiceExitCode;
import core.services.TaskScheduleService;
import core.services.TaskgroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.core.parameters.P;
import org.springframework.web.bind.annotation.*;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@ -23,6 +32,7 @@ import java.util.List;
public class StatisticController {
@Autowired private TaskScheduleService taskScheduleService;
@Autowired private TaskgroupService taskgroupService;
@GetMapping("/history/workingStatus")
public ResponseEntity<?> getWorkingStatus() {
@ -39,4 +49,17 @@ 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<Taskgroup> taskgroupPermissionResult = taskgroupService.getTaskgroupByIDAndUsername(taskgroupID, SecurityContextHolder.getContext().getAuthentication().getName());
if(!taskgroupPermissionResult.isHasPermissions()) {
return ResponseEntity.status(403).body(new SimpleStatusResponse("failed"));
} else if(taskgroupPermissionResult.getExitCode() == ServiceExitCode.MISSING_ENTITY) {
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
}
return ResponseEntity.ok(taskgroupPermissionResult.getResult().calcActivityInfo(includeSubTaskgroups,
LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")), LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"))));
}
}

View File

@ -0,0 +1,20 @@
package core.api.models.timemanager.history;
import com.fasterxml.jackson.annotation.JsonProperty;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import java.time.LocalDate;
public class TaskgroupActivityInfo {
@JsonProperty
private LocalDate date;
@JsonProperty
private int activeMinutes;
public TaskgroupActivityInfo(int activeMinutes, LocalDate localDate) {
this.date = localDate;
this.activeMinutes = activeMinutes;
}
}

View File

@ -9,13 +9,18 @@ import java.util.List;
public class TaskgroupPathInfo {
@JsonProperty
private List<String> taskgroupPathNames;
private String taskgroupPath;
@JsonProperty
private List<TaskgroupEntityInfo> directChildren;
public TaskgroupPathInfo(Taskgroup taskgroup) {
taskgroupPathNames = new ArrayList<>();
List<Taskgroup> taskgroupPath = Taskgroup.getAncestorList(taskgroup);
for(Taskgroup cT : taskgroupPath) {
taskgroupPathNames.add(cT.getTaskgroupName());
}
StringBuilder stringBuilder = new StringBuilder();
for(Taskgroup taskgroupPathEntity : taskgroupPath) {
stringBuilder.append(taskgroupPathEntity.getTaskgroupName());
stringBuilder.append("/");
}
this.taskgroupPath = stringBuilder.substring(0, stringBuilder.length()-1);
directChildren = taskgroup.getChildren().stream().map(TaskgroupEntityInfo::new).toList();
}
}

View File

@ -93,6 +93,10 @@ public abstract class AbstractSchedule {
return startTime != null && stopTime == null;
}
public boolean isCompleted() {
return startTime != null && stopTime != null;
}
public int getActiveTime() {
if(startTime == null) {
return 0;

View File

@ -188,4 +188,14 @@ public class Task {
public void setFinishable(boolean finishable) {
this.finishable = finishable;
}
public int calcOverallActivityInfo(LocalDate date) {
int activeMinutes = 0;
for(AbstractSchedule schedule : getBasicTaskSchedules()) {
if(schedule.isCompleted() && schedule.getStartTime().toLocalDate().isEqual(date)) {
activeMinutes += schedule.calcActiveMinutes();
}
}
return activeMinutes;
}
}

View File

@ -1,9 +1,12 @@
package core.entities.timemanager;
import core.api.models.timemanager.history.TaskgroupActivityInfo;
import core.api.models.timemanager.taskgroup.TaskgroupEntityInfo;
import core.entities.User;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.util.*;
@Entity
@ -132,4 +135,55 @@ public class Taskgroup {
public int hashCode() {
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

@ -1,10 +1,13 @@
package core.repositories.timemanager;
import core.entities.timemanager.AbstractSchedule;
import core.entities.timemanager.Taskgroup;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

View File

@ -9,9 +9,14 @@
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { TaskgroupEntityInfo } from './taskgroupEntityInfo';
export interface TaskgroupPathInfo {
taskgroupPathNames: Array<string>;
/**
* TaskgroupPath
*/
taskgroupPath: string;
directChildren: Array<TaskgroupEntityInfo>;
}

View File

@ -2,8 +2,8 @@
<app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
<mat-form-field style="width: 100%">
<mat-label>Toppings</mat-label>
<mat-select [(ngModel)]="selectedSeries" multiple (ngModelChange)="updateSerieSelection()">
<mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
<mat-select [(ngModel)]="selectedTaskgroupPath" (ngModelChange)="updateSerieSelection()">
<mat-option *ngFor="let topping of taskgroupPaths" [value]="topping">{{topping.taskgroupPath}}</mat-option>
</mat-select>
</mat-form-field>

View File

@ -11,7 +11,7 @@ import {
} from "ng-apexcharts";
import {timeInterval} from "rxjs";
import {FormControl} from "@angular/forms";
import {TaskgroupService} from "../../../api";
import {HistoryService, TaskgroupPathInfo, TaskgroupService} from "../../../api";
export type ChartOptions = {
series: ApexAxisChartSeries;
@ -41,9 +41,9 @@ export class TaskgroupActivityComponent implements OnInit{
];
toppings = new FormControl();
toppingList: string[] = [];
selectedSeries: string[] = []
selectedTaskgroupPath: TaskgroupPathInfo | undefined
taskgroupPaths: TaskgroupPathInfo[] = []
sliderControl: FormControl = new FormControl()
dateRange: Date[] = this.createDateRange();
selectedDateRange: Date[] = this.dateRange;
@ -62,17 +62,14 @@ export class TaskgroupActivityComponent implements OnInit{
@ViewChild("chart") chart?: ChartComponent;
public chartOptions: Partial<ChartOptions> = this.generateChartOptions()
constructor(private taskgroupService: TaskgroupService) {
constructor(private taskgroupService: TaskgroupService,
private historyService: HistoryService) {
}
ngOnInit() {
this.taskgroupService.taskgroupsPathGet().subscribe({
next: resp => {
resp.forEach(taskgroupPath => {
let taskgroupPathName = "";
taskgroupPath.taskgroupPathNames.forEach(name => taskgroupPathName += (name + "/"))
this.toppingList.push(taskgroupPathName)
})
this.taskgroupPaths = resp;
}
})
}
@ -113,7 +110,7 @@ export class TaskgroupActivityComponent implements OnInit{
generateChartOptions(): Partial<ChartOptions> {
return {
series: this.selectedSeries.map(serie => this.generateSeries(serie)!),
series: this.generateSeries(),
chart: {
height: 350,
type: "bar",
@ -128,28 +125,26 @@ export class TaskgroupActivityComponent implements OnInit{
};
}
generateSeries(serie: string) {
if(serie == "KIT/") {
return {
name: "Marine Sprite",
data: [44, 55, 41, 37, 22, 43, 21]
}
} else if(serie == "Striking Calf") {
return {
name: "Striking Calf",
data: [53, 32, 33, 52, 13, 43, 32]
}
} else if(serie == "Tank Picture") {
return {
name: "Tank Picture",
data: [12, 17, 11, 9, 15, 11, 20]
}
} else {
return {
name: "",
data: []
generateSeries() : ApexAxisChartSeries {
const series: ApexAxisChartSeries = []
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)
}
)
}
})
})
return series;
}

View File

@ -1905,6 +1905,63 @@ paths:
schema:
type: object
$ref: "#/components/schemas/SimpleStatusResponse"
/statistics/taskgroup-activity/{taskgroupID}/{startingDate}/{endingDate}/{includeSubTaskgroups}:
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
- name: includeSubTaskgroups
in: path
description: determines whether to include subtaskgroups or not
required: true
schema:
type: boolean
example: false
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'
components:
securitySchemes:
@ -2549,10 +2606,27 @@ components:
format: date
TaskgroupPathInfo:
required:
- taskgroupPathNames
- taskgroupPath
- directChildren
additionalProperties: false
properties:
taskgroupPathNames:
taskgroupPath:
type: string
description: TaskgroupPath
directChildren:
type: array
items:
$ref: '#/components/schemas/TaskgroupEntityInfo'
TaskgroupActivityInfo:
required:
- date
- activeMinutes
additionalProperties: false
properties:
date:
type: string
format: date
activeMinutes:
type: number
description: Number of minutes the task was active
example: 122