fix-statistics #110

Merged
sebastian merged 6 commits from fix-statistics into master 2024-03-18 15:25:20 +01:00
18 changed files with 573 additions and 347 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,6 +21,7 @@ import java.util.*;
public class StatisticController { public class StatisticController {
@Autowired private TaskScheduleService taskScheduleService; @Autowired private TaskScheduleService taskScheduleService;
@Autowired private StatisticService statisticService;
@Autowired private TaskgroupService taskgroupService; @Autowired private TaskgroupService taskgroupService;
@GetMapping("/history/workingStatus") @GetMapping("/history/workingStatus")
@ -42,24 +40,29 @@ 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}")
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) { var taskgroupActivityInfos = statisticService.calcActivityByUser(null, SecurityContextHolder.getContext().getAuthentication().getName(), starting, ending);
return ResponseEntity.status(404).body(new SimpleStatusResponse("failed")); List<TaskgroupActivityInfo> 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();
} }
List<TaskgroupActivityInfo> activityInfos = taskgroupPermissionResult.getResult().calcActivityInfo(includeSubTaskgroups, var taskgroupActivityInfos = statisticService.calcActivityByUser(permissionResult.getResult(), username, 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 activeMinutes;
public ActivityInfo(LocalDate scheduleDate, int workedMinutes) {
this.scheduleDate = scheduleDate;
this.activeMinutes = workedMinutes;
}
public LocalDate getScheduleDate() {
return scheduleDate;
}
public void setScheduleDate(LocalDate scheduleDate) {
this.scheduleDate = scheduleDate;
}
public int getActiveMinutes() {
return activeMinutes;
}
public void setActiveMinutes(int activeMinutes) {
this.activeMinutes = activeMinutes;
}
}

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,113 @@
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(Taskgroup taskgroup, String username, LocalDate startinDate, LocalDate endingDate) {
HashMap<Taskgroup, HashMap<LocalDate, Integer>> taskgroupActivityInfos = new HashMap<>();
List<AbstractSchedule> startedSchedules = scheduleRepository.getAllFinishedSchedulesByUser(username);
for(AbstractSchedule schedule : startedSchedules) {
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<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;
}
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;
}
}

View File

@ -17,6 +17,7 @@ encoder.ts
git_push.sh git_push.sh
index.ts index.ts
model/accountDeleteRequest.ts model/accountDeleteRequest.ts
model/activityInfo.ts
model/advancedScheduleFieldInfo.ts model/advancedScheduleFieldInfo.ts
model/advancedScheduleInfo.ts model/advancedScheduleInfo.ts
model/advancedScheduleInfoAllOf.ts model/advancedScheduleInfoAllOf.ts

View File

@ -203,28 +203,20 @@ export class HistoryService {
} }
/** /**
* @param taskgroupID internal id of taskgroup
* @param startingDate starting date * @param startingDate starting date
* @param endingDate 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 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. * @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<Array<TaskgroupActivityInfo>>; public statisticsStartingDateEndingDateGet(startingDate: string, endingDate: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<Array<TaskgroupActivityInfo>>;
public statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet(taskgroupID: number, startingDate: string, endingDate: string, includeSubTaskgroups: boolean, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskgroupActivityInfo>>>; public statisticsStartingDateEndingDateGet(startingDate: string, endingDate: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskgroupActivityInfo>>>;
public statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet(taskgroupID: number, startingDate: string, endingDate: string, includeSubTaskgroups: boolean, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskgroupActivityInfo>>>; public statisticsStartingDateEndingDateGet(startingDate: string, endingDate: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskgroupActivityInfo>>>;
public statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet(taskgroupID: number, startingDate: string, endingDate: string, includeSubTaskgroups: boolean, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> { public statisticsStartingDateEndingDateGet(startingDate: string, endingDate: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
if (taskgroupID === null || taskgroupID === undefined) {
throw new Error('Required parameter taskgroupID was null or undefined when calling statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet.');
}
if (startingDate === null || startingDate === undefined) { 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) { if (endingDate === null || endingDate === undefined) {
throw new Error('Required parameter endingDate was null or undefined when calling statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet.'); throw new Error('Required parameter endingDate was null or undefined when calling statisticsStartingDateEndingDateGet.');
}
if (includeSubTaskgroups === null || includeSubTaskgroups === undefined) {
throw new Error('Required parameter includeSubTaskgroups was null or undefined when calling statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet.');
} }
let localVarHeaders = this.defaultHeaders; let localVarHeaders = this.defaultHeaders;
@ -259,7 +251,72 @@ export class HistoryService {
responseType_ = 'text'; responseType_ = 'text';
} }
return this.httpClient.get<Array<TaskgroupActivityInfo>>(`${this.configuration.basePath}/statistics/taskgroup-activity/${encodeURIComponent(String(taskgroupID))}/${encodeURIComponent(String(startingDate))}/${encodeURIComponent(String(endingDate))}/${encodeURIComponent(String(includeSubTaskgroups))}`, return this.httpClient.get<Array<TaskgroupActivityInfo>>(`${this.configuration.basePath}/statistics/${encodeURIComponent(String(startingDate))}/${encodeURIComponent(String(endingDate))}`,
{
context: localVarHttpContext,
responseType: <any>responseType_,
withCredentials: this.configuration.withCredentials,
headers: localVarHeaders,
observe: observe,
reportProgress: reportProgress
}
);
}
/**
* @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<Array<TaskgroupActivityInfo>>;
public statisticsTaskgroupIDStartingDateEndingDateGet(taskgroupID: number, startingDate: string, endingDate: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpResponse<Array<TaskgroupActivityInfo>>>;
public statisticsTaskgroupIDStartingDateEndingDateGet(taskgroupID: number, startingDate: string, endingDate: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<HttpEvent<Array<TaskgroupActivityInfo>>>;
public statisticsTaskgroupIDStartingDateEndingDateGet(taskgroupID: number, startingDate: string, endingDate: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext}): Observable<any> {
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<Array<TaskgroupActivityInfo>>(`${this.configuration.basePath}/statistics/${encodeURIComponent(String(taskgroupID))}/${encodeURIComponent(String(startingDate))}/${encodeURIComponent(String(endingDate))}`,
{ {
context: localVarHttpContext, context: localVarHttpContext,
responseType: <any>responseType_, responseType: <any>responseType_,

View File

@ -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;
}

View File

@ -1,4 +1,5 @@
export * from './accountDeleteRequest'; export * from './accountDeleteRequest';
export * from './activityInfo';
export * from './advancedScheduleFieldInfo'; export * from './advancedScheduleFieldInfo';
export * from './advancedScheduleInfo'; export * from './advancedScheduleInfo';
export * from './advancedScheduleInfoAllOf'; export * from './advancedScheduleInfoAllOf';

View File

@ -9,13 +9,12 @@
* https://openapi-generator.tech * https://openapi-generator.tech
* Do not edit the class manually. * Do not edit the class manually.
*/ */
import { ActivityInfo } from './activityInfo';
import { TaskgroupEntityInfo } from './taskgroupEntityInfo';
export interface TaskgroupActivityInfo { export interface TaskgroupActivityInfo {
date: string; taskgroup: TaskgroupEntityInfo;
/** activityInfos: Array<ActivityInfo>;
* Number of minutes the task was active
*/
activeMinutes: number;
} }

View File

@ -1,8 +1,11 @@
<div style="text-align:center"> <div style="text-align:center" *ngIf="selectedTaskgroupPath != undefined">
<apx-chart <apx-chart
[series]="chartOptions.series!" [series]="chartOptions!.series!"
[chart]="chartOptions.chart!" [chart]="chartOptions!.chart!"
[xaxis]="chartOptions.xaxis!" [title]="chartOptions!.title!"
[title]="chartOptions.title!" [plotOptions]="chartOptions!.plotOptions!"
[xaxis]="chartOptions!.xAxis!"
[yaxis]="chartOptions!.yAxis!"
[tooltip]="chartOptions!.tooltip!"
></apx-chart> ></apx-chart>
</div> </div>

View File

@ -1,168 +1,180 @@
import {Component, Input, OnInit, ViewChild} from '@angular/core'; import {Component, Input, OnChanges, ViewChild} from '@angular/core';
import {ApexAxisChartSeries, ChartComponent, ChartType} from "ng-apexcharts";
import {ChartOptions} from "../taskgroup-activity.component"; import {
ApexAxisChartSeries,
ApexTitleSubtitle,
ApexDataLabels,
ApexChart,
ApexPlotOptions, ChartComponent, ApexXAxis, ApexYAxis, ApexTooltip
} from "ng-apexcharts";
import {generate} from "rxjs";
import * as moment from "moment"; import * as moment from "moment";
import {HistoryService, TaskgroupActivityInfo, TaskgroupPathInfo} from "../../../../api"; import {HistoryService, TaskgroupActivityInfo, TaskgroupPathInfo} from "../../../../api";
export type ChartOptions = {
interface XYData { series: ApexAxisChartSeries;
x: any, chart: ApexChart;
y: any dataLabels: ApexDataLabels;
} title: ApexTitleSubtitle;
plotOptions: ApexPlotOptions;
xAxis: ApexXAxis,
yAxis: ApexYAxis,
tooltip: ApexTooltip
};
@Component({ @Component({
selector: 'app-heatmap-activity', selector: 'app-heatmap-activity',
templateUrl: './heatmap-activity.component.html', templateUrl: './heatmap-activity.component.html',
styleUrls: ['./heatmap-activity.component.css'] styleUrls: ['./heatmap-activity.component.css']
}) })
export class HeatmapActivityComponent implements OnInit{ export class HeatmapActivityComponent implements OnChanges{
@ViewChild("chart") chart?: ChartComponent; @ViewChild("chart") chart: ChartComponent | undefined;
public chartOptions: Partial<ChartOptions> = this.generateChartOptions() @Input() selectedTaskgroupPath: TaskgroupPathInfo | undefined
public chartOptions: Partial<ChartOptions> | undefined;
@Input() selectedTaskgroupPath?: TaskgroupPathInfo maxValue = 120
constructor(private historyService: HistoryService) { constructor(private historyService: HistoryService) {
} }
ngOnInit() { ngOnChanges() {
this.chartOptions = this.generateChartOptions();
}
generateChartOptions(): Partial<ChartOptions> {
return {
series: this.generateSeries(),
chart: {
height: 350,
type: 'heatmap',
},
title: {
text: ""
},
dataLabels: {
enabled: false
},
colors: ["#008FFB"],
};
}
generateSeries() : ApexAxisChartSeries {
const series: ApexAxisChartSeries = []
if(this.selectedTaskgroupPath != undefined) { if(this.selectedTaskgroupPath != undefined) {
const startingDate = new Date(2023, 4, 9); /*this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet(
const endDate = new Date(2023, 11, 20); this.selectedTaskgroupPath!.rootTasktroup.taskgroupID,
let activityInfos: TaskgroupActivityInfo[]; moment().startOf('year').format("YYYY-MM-DD"),
this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( moment().endOf("year").format("YYYY-MM-DD"),
this.selectedTaskgroupPath.rootTasktroup.taskgroupID, false
moment().subtract(6, "months").format("YYYY-MM-DD"),
moment().add(2, 'days').format("YYYY-MM-DD"),
true
).subscribe({ ).subscribe({
next: resp => { next: resp => {
activityInfos = resp; const taskgroupActivityInfo = resp;
const data: XYData[][] = []; console.log("Chat Options")
let currentDate: moment.Moment = moment(activityInfos[0].date).subtract(moment(activityInfos[0].date).day(), 'days'); this.chartOptions = {
series: [
//Offset until data starts {
let index = currentDate.day(); name: "Monday",
while(currentDate < moment(activityInfos[0].date)) { data: this.generateData(moment().startOf('year'),taskgroupActivityInfo)
if(data[currentDate.day()] == undefined) { },
data[currentDate.day()] = []; {
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 "<span style='font-weight: normal'>Spent </span>" + value + " <span style='font-weight: normal'>Minutes on <br>" + w.config.series[seriesIndex].data[dataPointIndex].extraInfo.format("dddd, MMMM D, YYYY") + "</span>"
},
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) { private generateData(startingDate: moment.Moment, data: TaskgroupActivityInfo[]) {
const data: TaskgroupActivityInfo[] = []; let currentDate = startingDate;
let currentDate: moment.Moment = moment(start);
while(currentDate <= end) { let series = []
data.push({ let endingDate = startingDate.clone().endOf('year');
date: currentDate.format("YYYY-MM-DD"),
activeMinutes: Math.floor(Math.random() * (100 + 1)) 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) { private findTaskgroupActivityInfoByDate(date: moment.Moment, data: TaskgroupActivityInfo[]) {
this.selectedTaskgroupPath = taskgroupPath; //return data.find(taskActivity => moment(taskActivity.date).isSame(date))
this.chartOptions = this.generateChartOptions();
} }
} }

View File

@ -1,5 +1,5 @@
<div style="text-align:center"> <div style="text-align:center">
<apx-chart <apx-chart #chart *ngIf="chartOptions != undefined"
[series]="chartOptions.series!" [series]="chartOptions.series!"
[chart]="chartOptions.chart!" [chart]="chartOptions.chart!"
[xaxis]="chartOptions.xaxis!" [xaxis]="chartOptions.xaxis!"

View File

@ -1,8 +1,8 @@
import {Component, Input, ViewChild} from '@angular/core'; import {Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild} from '@angular/core';
import {FormControl} from "@angular/forms"; import {FormControl} from "@angular/forms";
import {ChangeContext, LabelType, Options} from "ngx-slider-v2"; import {ChangeContext, LabelType, Options} from "ngx-slider-v2";
import {ApexAxisChartSeries, ChartComponent, ChartType} from "ng-apexcharts"; 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 * as moment from "moment/moment";
import {ChartOptions} from "../taskgroup-activity.component"; import {ChartOptions} from "../taskgroup-activity.component";
@ -11,8 +11,8 @@ import {ChartOptions} from "../taskgroup-activity.component";
templateUrl: './simple-activity-diagram.component.html', templateUrl: './simple-activity-diagram.component.html',
styleUrls: ['./simple-activity-diagram.component.css'] styleUrls: ['./simple-activity-diagram.component.css']
}) })
export class SimpleActivityDiagramComponent { export class SimpleActivityDiagramComponent implements OnChanges {
@Input('selectedChartype') selectedChartype: string = "bar"; @Input('selectedChartype') selectedChartype: string | undefined
@Input() selectedTaskgroupPath: TaskgroupPathInfo | undefined; @Input() selectedTaskgroupPath: TaskgroupPathInfo | undefined;
sliderControl: FormControl = new FormControl() sliderControl: FormControl = new FormControl()
@ -31,15 +31,83 @@ export class SimpleActivityDiagramComponent {
}; };
@ViewChild("chart") chart?: ChartComponent; @ViewChild("chart") chart?: ChartComponent;
public chartOptions: Partial<ChartOptions> = this.generateChartOptions()
chartOptions: Partial<ChartOptions> | undefined;
constructor(private taskgroupService: TaskgroupService, constructor(private taskgroupService: TaskgroupService,
private historyService: HistoryService) { private historyService: HistoryService) {
} }
ngOnChanges() {
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");
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[] { createDateRange(): Date[] {
const dates: 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()); dates.push(moment().subtract(30-i, 'd').toDate());
} }
@ -63,84 +131,10 @@ export class SimpleActivityDiagramComponent {
console.log("min " + moment(changeContext.value).format("YYYY-MM-DD")); console.log("min " + moment(changeContext.value).format("YYYY-MM-DD"));
console.log("max " + moment(changeContext.highValue!).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.selectedDateRange = this.createDateRangeBetween(moment(changeContext.value), moment(changeContext.highValue!))
this.chartOptions = this.generateChartOptions()
}
generateChartOptions(): Partial<ChartOptions> {
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) { setSelectedChartType(selectedChartype: string) {
this.selectedChartype = selectedChartype; //this.selectedChartype = selectedChartype;
this.updateSerieSelection();
}
setSelectedTaskgroupPath(selectedTaskgroupPath: TaskgroupPathInfo) {
this.selectedTaskgroupPath = selectedTaskgroupPath;
this.updateSerieSelection();
} }
} }

View File

@ -2,7 +2,7 @@
<app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list> <app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
<mat-form-field style="width: 90%"> <mat-form-field style="width: 90%">
<mat-label>Taskgroup</mat-label> <mat-label>Taskgroup</mat-label>
<mat-select [(ngModel)]="selectedTaskgroupPath" (ngModelChange)="onSelectTaskgroupPath()"> <mat-select [(ngModel)]="selectedTaskgroupPath">
<mat-option *ngFor="let topping of taskgroupPaths" [value]="topping">{{topping.taskgroupPath}}</mat-option> <mat-option *ngFor="let topping of taskgroupPaths" [value]="topping">{{topping.taskgroupPath}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
@ -14,5 +14,5 @@
</mat-form-field> </mat-form-field>
<app-simple-activity-diagram *ngIf="selectedChartype !== 'heatmap'" #simpleActivityDiagram [selectedChartype]="selectedChartype" [selectedTaskgroupPath]="selectedTaskgroupPath"></app-simple-activity-diagram> <app-simple-activity-diagram *ngIf="selectedChartype !== 'heatmap'" #simpleActivityDiagram [selectedChartype]="selectedChartype" [selectedTaskgroupPath]="selectedTaskgroupPath"></app-simple-activity-diagram>
<app-heatmap-activity *ngIf="selectedChartype === 'heatmap'" #heatMap></app-heatmap-activity> <app-heatmap-activity *ngIf="selectedChartype === 'heatmap'" #heatMap [selectedTaskgroupPath]="selectedTaskgroupPath!"></app-heatmap-activity>
</div> </div>

View File

@ -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() { onSelectChartType() {
if(this.simpleActivityDiagram != undefined) { if(this.simpleActivityDiagram != undefined) {
this.simpleActivityDiagram.setSelectedChartType(this.selectedChartype); this.simpleActivityDiagram.setSelectedChartType(this.selectedChartype);

View File

@ -2060,7 +2060,49 @@ 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:
security:
- API_TOKEN: []
tags:
- history
parameters:
- 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'
/statistics/{taskgroupID}/{startingDate}/{endingDate}:
get: get:
security: security:
- API_TOKEN: [] - API_TOKEN: []
@ -2088,13 +2130,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 +2983,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 +2996,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