issue-53 #78
@ -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"))));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
78
openapi.yaml
78
openapi.yaml
@ -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
|
Loading…
Reference in New Issue
Block a user