Merged branch comments/dashbaord
This commit is contained in:
		| @@ -1,119 +1,124 @@ | ||||
| <div class="container-fluid"> | ||||
|   <div class="content"> | ||||
|     <h3>Dashboard</h3> | ||||
|  | ||||
|     <div> | ||||
|       <div class="row px-3 py-2"> | ||||
|         <ng-container *ngIf="selectedSprint === undefined"> | ||||
|           <h3 class="mr-3 text-primary">Alle Userstories</h3> | ||||
|           <a [routerLink]="['sprints']" | ||||
|             >Lege einen Sprint an, um Userstories zu organisieren.</a | ||||
|           > | ||||
|         </ng-container> | ||||
|  | ||||
|         <ng-container *ngIf="selectedSprint !== undefined"> | ||||
|           <h3 | ||||
|             class="mr-3 text-primary" | ||||
|             *ngIf="selectedSprint === currentSprint" | ||||
|           > | ||||
|             Aktueller Sprint: | ||||
|           </h3> | ||||
|           <h3 | ||||
|             class="mr-3 text-primary" | ||||
|             *ngIf="selectedSprint !== currentSprint" | ||||
|           > | ||||
|             Sprint: | ||||
|           </h3> | ||||
|           <h3 class="mr-3 custom-text-secondary">{{ selectedSprint.title }}</h3> | ||||
|           <h3 class="mr-3"> | ||||
|             {{ toDateString(selectedSprint.startDate) }} - | ||||
|             {{ toDateString(selectedSprint.endDate) }} | ||||
|           </h3> | ||||
|  | ||||
|           <label class="mr-3"> | ||||
|             <select | ||||
|               class="select custom-select custom-text-secondary" | ||||
|               [(ngModel)]="selectedSprint" | ||||
|             > | ||||
|               <option class="bg-secondary text-dark" [ngValue]="currentSprint"> | ||||
|                 {{ currentSprint.title }} (aktuell) | ||||
|               </option> | ||||
|               <option value="" disabled="disabled" | ||||
|                 >─────────────────────────</option | ||||
|               > | ||||
|               <option | ||||
|                 class="text-dark" | ||||
|                 *ngFor="let sprint of sprints" | ||||
|                 [ngValue]="sprint" | ||||
|               > | ||||
|                 <ng-container *ngIf="sprint === currentSprint"> | ||||
|                   {{ sprint.title }} (aktuell) | ||||
|                 </ng-container> | ||||
|                 <ng-container *ngIf="sprint !== currentSprint"> | ||||
|                   {{ sprint.title }} ({{ toDateString(sprint.startDate) }} - | ||||
|                   {{ toDateString(sprint.endDate) }}) | ||||
|                 </ng-container> | ||||
|               </option> | ||||
|             </select> | ||||
|           </label> | ||||
|  | ||||
|           <span class="mr-5"></span> | ||||
|           <h3 | ||||
|             *ngIf="selectedSprint === currentSprint" | ||||
|             class="mr-3 custom-text-secondary" | ||||
|             [class.text-success]="getSprintUrgency() === 2" | ||||
|             [class.text-warning]="getSprintUrgency() === 1" | ||||
|             [class.text-danger]="getSprintUrgency() === 0" | ||||
|           > | ||||
|             Verbleibende Tage: {{ getRemainingDaysInSprint() }} | ||||
|           </h3> | ||||
|         </ng-container> | ||||
|       </div> | ||||
|  | ||||
|       <div class="row"> | ||||
|         <div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12"> | ||||
|           <div class="card"> | ||||
|             <div class="card-header"> | ||||
|               <span class="text-large"> | ||||
|                 Userstories | ||||
|               </span> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|               <div | ||||
|                 *ngIf="selectedSprint !== undefined && usedStatus.length === 0" | ||||
|               > | ||||
|                 Zum Sprint "{{ selectedSprint.title }}" sind aktuell keine | ||||
|                 Userstories vorhanden. | ||||
|               </div> | ||||
|               <canvas id="done-stories-chart"></canvas> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="card-deck"> | ||||
|             <div class="card text-center" *ngFor="let status of usedStatus"> | ||||
|               <div class="card-header"> | ||||
|                 <span class="text-large"> | ||||
|                   {{ status.title }} | ||||
|                 </span> | ||||
|               </div> | ||||
|               <div class="card-body"> | ||||
|                 <span class="text-very-large"> | ||||
|                   <b>{{ getNumberOfUserstoriesByStatus(status) }}</b> | ||||
|                 </span> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12"> | ||||
|           <div class="card"> | ||||
|             <div class="card-body"> | ||||
|               <app-userstory-inner-table | ||||
|                 [items]="selectedSprintUserstories" | ||||
|               ></app-userstory-inner-table> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| <div class="container-fluid"> | ||||
|   <div class="content"> | ||||
|     <h3>Dashboard</h3> | ||||
|  | ||||
|     <div> | ||||
|       <div class="row px-3 py-2"> | ||||
|  | ||||
|         <!-- Show a message if no sprints exist yet, with a link to the page where a new one can be created --> | ||||
|         <ng-container *ngIf="selectedSprint === undefined"> | ||||
|           <h3 class="mr-3 text-primary">Alle Userstories</h3> | ||||
|           <a [routerLink]="['sprints']" | ||||
|             >Lege einen Sprint an, um Userstories zu organisieren.</a | ||||
|           > | ||||
|         </ng-container> | ||||
|  | ||||
|         <ng-container *ngIf="selectedSprint !== undefined"> | ||||
|           <h3 | ||||
|             class="mr-3 text-primary" | ||||
|             *ngIf="selectedSprint === currentSprint" | ||||
|           > | ||||
|             Aktueller Sprint: | ||||
|           </h3> | ||||
|           <h3 | ||||
|             class="mr-3 text-primary" | ||||
|             *ngIf="selectedSprint !== currentSprint" | ||||
|           > | ||||
|             Sprint: | ||||
|           </h3> | ||||
|           <h3 class="mr-3 custom-text-secondary">{{ selectedSprint.title }}</h3> | ||||
|           <h3 class="mr-3"> | ||||
|             {{ toDateString(selectedSprint.startDate) }} - | ||||
|             {{ toDateString(selectedSprint.endDate) }} | ||||
|           </h3> | ||||
|  | ||||
|           <label class="mr-3"> | ||||
|             <select | ||||
|               class="select custom-select custom-text-secondary" | ||||
|               [(ngModel)]="selectedSprint" | ||||
|             > | ||||
|               <option class="bg-secondary text-dark" [ngValue]="currentSprint"> | ||||
|                 {{ currentSprint.title }} (aktuell) | ||||
|               </option> | ||||
|               <option value="" disabled="disabled" | ||||
|                 >─────────────────────────</option | ||||
|               > | ||||
|               <option | ||||
|                 class="text-dark" | ||||
|                 *ngFor="let sprint of sprints" | ||||
|                 [ngValue]="sprint" | ||||
|               > | ||||
|                 <ng-container *ngIf="sprint === currentSprint"> | ||||
|                   {{ sprint.title }} (aktuell) | ||||
|                 </ng-container> | ||||
|                 <ng-container *ngIf="sprint !== currentSprint"> | ||||
|                   {{ sprint.title }} ({{ toDateString(sprint.startDate) }} - | ||||
|                   {{ toDateString(sprint.endDate) }}) | ||||
|                 </ng-container> | ||||
|               </option> | ||||
|             </select> | ||||
|           </label> | ||||
|  | ||||
|           <!-- If the active sprint is selected: Show the number of remaining days + urgency --> | ||||
|           <span class="mr-5"></span> | ||||
|           <h3 | ||||
|             *ngIf="selectedSprint === currentSprint" | ||||
|             class="mr-3 custom-text-secondary" | ||||
|             [class.text-success]="getSprintUrgency() === 2" | ||||
|             [class.text-warning]="getSprintUrgency() === 1" | ||||
|             [class.text-danger]="getSprintUrgency() === 0" | ||||
|           > | ||||
|             Verbleibende Tage: {{ getRemainingDaysInSprint() }} | ||||
|           </h3> | ||||
|         </ng-container> | ||||
|       </div> | ||||
|  | ||||
|       <div class="row"> | ||||
|  | ||||
|         <!-- Info on the userstories in the selected sprint --> | ||||
|         <div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12"> | ||||
|           <div class="card"> | ||||
|             <div class="card-header"> | ||||
|               <span class="text-large"> | ||||
|                 Userstories | ||||
|               </span> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|               <div | ||||
|                 *ngIf="selectedSprint !== undefined && usedStatus.length === 0" | ||||
|               > | ||||
|                 Zum Sprint "{{ selectedSprint.title }}" sind aktuell keine | ||||
|                 Userstories vorhanden. | ||||
|               </div> | ||||
|               <canvas id="done-stories-chart"></canvas> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="card-deck"> | ||||
|             <div class="card text-center" *ngFor="let status of usedStatus"> | ||||
|               <div class="card-header"> | ||||
|                 <span class="text-large"> | ||||
|                   {{ status.title }} | ||||
|                 </span> | ||||
|               </div> | ||||
|               <div class="card-body"> | ||||
|                 <span class="text-very-large"> | ||||
|                   <b>{{ getNumberOfUserstoriesByStatus(status) }}</b> | ||||
|                 </span> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="p-3 col-12 col-xl-6 col-lg-6 col-md-12 col-sm-12"> | ||||
|           <div class="card"> | ||||
|             <div class="card-body"> | ||||
|               <app-userstory-inner-table | ||||
|                 [items]="selectedSprintUserstories" | ||||
|               ></app-userstory-inner-table> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
|   | ||||
| @@ -1,187 +1,215 @@ | ||||
| import { Component, OnChanges } from '@angular/core'; | ||||
| import { forkJoin } from 'rxjs'; | ||||
| import Chart from 'chart.js'; | ||||
| import { | ||||
|   BackendService, | ||||
|   ScrumSprint, | ||||
|   ScrumStatus, | ||||
|   ScrumUserstory, | ||||
| } from '../../services/backend.service'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-dashboard', | ||||
|   templateUrl: 'dashboard.component.html', | ||||
|   styleUrls: ['./dashboard.component.css'], | ||||
| }) | ||||
| export class DashboardComponent { | ||||
|   /** | ||||
|    * Returns the status that are used by at least one userstory. | ||||
|    */ | ||||
|   public get usedStatus(): ScrumStatus[] { | ||||
|     return this.status.filter( | ||||
|       (s) => | ||||
|         this.selectedSprintUserstories.find((us) => us.statusid === s.id) !== | ||||
|         undefined | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   public get selectedSprintUserstories(): ScrumUserstory[] { | ||||
|     if (this.selectedSprint === undefined) { | ||||
|       return this.userstories; | ||||
|     } | ||||
|     return this.userstories.filter( | ||||
|       (us) => us.sprintid === this.selectedSprint.id | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   public get currentSprint(): ScrumSprint { | ||||
|     const now = Date.now(); | ||||
|     return this.sprints.find( | ||||
|       (s) => Date.parse(s.startDate) < now && Date.parse(s.endDate) > now | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   private _selectedSprint: ScrumSprint; | ||||
|   public get selectedSprint(): ScrumSprint { | ||||
|     if (this._selectedSprint === undefined) { | ||||
|       if (this.currentSprint === undefined) { | ||||
|         return this.sprints[0]; | ||||
|       } | ||||
|       return this.currentSprint; | ||||
|     } | ||||
|     return this._selectedSprint; | ||||
|   } | ||||
|  | ||||
|   public set selectedSprint(value) { | ||||
|     this._selectedSprint = value; | ||||
|     this.createChart(); | ||||
|   } | ||||
|  | ||||
|   public status: ScrumStatus[] = []; | ||||
|   public userstories: ScrumUserstory[] = []; | ||||
|   public sprints: ScrumSprint[] = []; | ||||
|  | ||||
|   public chart: Chart; | ||||
|  | ||||
|   constructor(private backendService: BackendService) { | ||||
|     // download userstories, status and sprints and update | ||||
|     // the chart whenever a new response is there | ||||
|     forkJoin([ | ||||
|       backendService.getUserstories(), | ||||
|       backendService.getAllStatus(), | ||||
|       backendService.getSprints(), | ||||
|     ]).subscribe((results) => { | ||||
|       const [userstoryResponse, statusResponse, sprintResponse] = results; | ||||
|       if ( | ||||
|         userstoryResponse.status > 399 || | ||||
|         statusResponse.status > 399 || | ||||
|         sprintResponse.status > 399 | ||||
|       ) { | ||||
|         alert('Fehler'); | ||||
|       } else { | ||||
|         this.userstories.push(...userstoryResponse.body); | ||||
|         this.status.push(...statusResponse.body); | ||||
|         this.sprints.push(...sprintResponse.body); | ||||
|         this.createChart(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the date in the following format: 1.7.2020 | ||||
|    */ | ||||
|   public toDateString(isoFormatString) { | ||||
|     const date = new Date(isoFormatString); | ||||
|     return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`; | ||||
|   } | ||||
|  | ||||
|   private createChart() { | ||||
|     // @ts-ignore | ||||
|     const context = document.getElementById('done-stories-chart').getContext('2d'); | ||||
|  | ||||
|     if (this.usedStatus.length === 0) { | ||||
|       this.chart.destroy(); | ||||
|     } else { | ||||
|       this.chart = new Chart(context, { | ||||
|         type: 'pie', | ||||
|         data: { | ||||
|           labels: this.usedStatus.map((s) => s.title), | ||||
|           datasets: [ | ||||
|             { | ||||
|               data: this.usedStatus.map((s) => | ||||
|                 this.getNumberOfUserstoriesByStatus(s) | ||||
|               ), | ||||
|               backgroundColor: this.getBackgroundColors(), | ||||
|               options: { | ||||
|                 legend: { | ||||
|                   display: true, | ||||
|                   position: 'right', | ||||
|                   labels: { | ||||
|                     fontColor: 'rgba(255, 255, 255, 0.8)', | ||||
|                   }, | ||||
|                 }, | ||||
|               }, | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       }); | ||||
|       this.chart.update(); | ||||
|       this.chart.render(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private getBackgroundColors(): string[] { | ||||
|     const baseColors = [ | ||||
|       'rgb(255, 153, 102)', | ||||
|       'rgb(255, 102, 102)', | ||||
|       'rgb(153, 204, 255)', | ||||
|       'rgb(102, 153, 102)', | ||||
|       'rgb(204, 204, 153)', | ||||
|       'rgb(153, 102, 204)', | ||||
|       'rgb(204, 102, 102)', | ||||
|       'rgb(255, 204, 153)', | ||||
|       'rgb(153, 102, 255)', | ||||
|       'rgb(204, 204, 204)', | ||||
|       'rgb(102, 255, 204)', | ||||
|       'rgb(102, 153, 255)', | ||||
|       'rgb(153, 102, 153)', | ||||
|       'rgb(204, 204, 255)', | ||||
|     ]; | ||||
|     const colors = []; | ||||
|     while (colors.length < this.usedStatus.length) { | ||||
|       colors.push(...baseColors); | ||||
|     } | ||||
|     return colors; | ||||
|   } | ||||
|  | ||||
|   public getNumberOfUserstoriesByStatus(status: ScrumStatus): number { | ||||
|     return this.selectedSprintUserstories.filter( | ||||
|       (us) => us.statusid === status.id | ||||
|     ).length; | ||||
|   } | ||||
|  | ||||
|   public getRemainingDaysInSprint(): number { | ||||
|     if (this.selectedSprint === undefined) { | ||||
|       return undefined; | ||||
|     } | ||||
|     return Math.floor( | ||||
|       (Date.parse(this.selectedSprint.endDate) - Date.now()) / 86400000 | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the "urgency" of the current sprint (ie what percentage of the sprint is remaining) | ||||
|    * as an integer from 0 (most urgent) to 2 (least urgent). | ||||
|    */ | ||||
|   public getSprintUrgency(): number { | ||||
|     const now = Date.now(); | ||||
|     const sprint = this.selectedSprint; | ||||
|     if (sprint === undefined) { | ||||
|       return undefined; | ||||
|     } | ||||
|     const deltaFromNow = Date.parse(sprint.endDate) - now; | ||||
|     const deltaFromStart = | ||||
|       Date.parse(sprint.endDate) - Date.parse(sprint.startDate); | ||||
|     return Math.floor((3 * deltaFromNow) / deltaFromStart); | ||||
|   } | ||||
| } | ||||
| import { Component } from '@angular/core'; | ||||
| import { forkJoin } from 'rxjs'; | ||||
| import Chart from 'chart.js'; | ||||
| import { BackendService, ScrumSprint, ScrumStatus, ScrumUserstory } from '../../services/backend.service'; | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-dashboard', | ||||
|   templateUrl: 'dashboard.component.html', | ||||
|   styleUrls: ['./dashboard.component.css'], | ||||
| }) | ||||
| export class DashboardComponent { | ||||
|   /** | ||||
|    * Returns the status that are used by at least one userstory. | ||||
|    */ | ||||
|   public get usedStatus(): ScrumStatus[] { | ||||
|     return this.status.filter( | ||||
|       (s) => | ||||
|         this.selectedSprintUserstories.find((us) => us.statusid === s.id) !== | ||||
|         undefined | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns all userstories in the selected sprint. | ||||
|    */ | ||||
|   public get selectedSprintUserstories(): ScrumUserstory[] { | ||||
|     if (this.selectedSprint === undefined) { | ||||
|       return this.userstories; | ||||
|     } | ||||
|     return this.userstories.filter( | ||||
|       (us) => us.sprintid === this.selectedSprint.id | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the currently active sprint. | ||||
|    * If multiple sprints are active at the current time, any one of them may be returned. | ||||
|    */ | ||||
|   public get currentSprint(): ScrumSprint { | ||||
|     const now = Date.now(); | ||||
|     return this.sprints.find( | ||||
|       (s) => Date.parse(s.startDate) < now && Date.parse(s.endDate) > now | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   private _selectedSprint: ScrumSprint; | ||||
|  | ||||
|   /** | ||||
|    * Returns the sprint selected by the user. | ||||
|    */ | ||||
|   public get selectedSprint(): ScrumSprint { | ||||
|     if (this._selectedSprint === undefined) { | ||||
|       if (this.currentSprint === undefined) { | ||||
|         return this.sprints[0]; | ||||
|       } | ||||
|       return this.currentSprint; | ||||
|     } | ||||
|     return this._selectedSprint; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Sets the sprint selected by the user. | ||||
|    */ | ||||
|   public set selectedSprint(value) { | ||||
|     this._selectedSprint = value; | ||||
|     this.createChart(); | ||||
|   } | ||||
|  | ||||
|   /** All status. */ | ||||
|   public status: ScrumStatus[] = []; | ||||
|  | ||||
|   /** All userstories. */ | ||||
|   public userstories: ScrumUserstory[] = []; | ||||
|  | ||||
|   /** All sprints. */ | ||||
|   public sprints: ScrumSprint[] = []; | ||||
|  | ||||
|   /** | ||||
|    * The pie chart showing the userstories in the selected sprint. | ||||
|    */ | ||||
|   public chart: Chart; | ||||
|  | ||||
|   constructor(private backendService: BackendService) { | ||||
|     // download userstories, status and sprints and update | ||||
|     // the chart whenever a new response is there | ||||
|     forkJoin([ | ||||
|       backendService.getUserstories(), | ||||
|       backendService.getAllStatus(), | ||||
|       backendService.getSprints(), | ||||
|     ]).subscribe((results) => { | ||||
|       const [userstoryResponse, statusResponse, sprintResponse] = results; | ||||
|       if ( | ||||
|         userstoryResponse.status > 399 || | ||||
|         statusResponse.status > 399 || | ||||
|         sprintResponse.status > 399 | ||||
|       ) { | ||||
|         alert('Fehler'); | ||||
|       } else { | ||||
|         this.userstories.push(...userstoryResponse.body); | ||||
|         this.status.push(...statusResponse.body); | ||||
|         this.sprints.push(...sprintResponse.body); | ||||
|         this.createChart(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the date in the following format: 1.7.2020 | ||||
|    * @param dateValue an integer representing the number of milliseconds since the | ||||
|    * ECMAScript epoch -or- a string in a format recognized by the Date.parse() method. | ||||
|    */ | ||||
|   public toDateString(dateValue) { | ||||
|     const date = new Date(dateValue); | ||||
|     return `${date.getDate()}.${date.getMonth() + 1}.${date.getFullYear()}`; | ||||
|   } | ||||
|  | ||||
|   private createChart() { | ||||
|     // @ts-ignore | ||||
|     const context = document.getElementById('done-stories-chart').getContext('2d'); | ||||
|  | ||||
|     if (this.usedStatus.length === 0) { | ||||
|       this.chart.destroy(); | ||||
|     } else { | ||||
|       this.chart = new Chart(context, { | ||||
|         type: 'pie', | ||||
|         data: { | ||||
|           labels: this.usedStatus.map((s) => s.title), | ||||
|           datasets: [ | ||||
|             { | ||||
|               data: this.usedStatus.map((s) => | ||||
|                 this.getNumberOfUserstoriesByStatus(s) | ||||
|               ), | ||||
|               backgroundColor: this.getBackgroundColors(), | ||||
|               options: { | ||||
|                 legend: { | ||||
|                   display: true, | ||||
|                   position: 'right', | ||||
|                   labels: { | ||||
|                     fontColor: 'rgba(255, 255, 255, 0.8)', | ||||
|                   }, | ||||
|                 }, | ||||
|               }, | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       }); | ||||
|       this.chart.update(); | ||||
|       this.chart.render(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns a sufficiently large array of background colors to be used in the chart. | ||||
|    */ | ||||
|   private getBackgroundColors(): string[] { | ||||
|     const baseColors = [ | ||||
|       'rgb(255, 153, 102)', | ||||
|       'rgb(255, 102, 102)', | ||||
|       'rgb(153, 204, 255)', | ||||
|       'rgb(102, 153, 102)', | ||||
|       'rgb(204, 204, 153)', | ||||
|       'rgb(153, 102, 204)', | ||||
|       'rgb(204, 102, 102)', | ||||
|       'rgb(255, 204, 153)', | ||||
|       'rgb(153, 102, 255)', | ||||
|       'rgb(204, 204, 204)', | ||||
|       'rgb(102, 255, 204)', | ||||
|       'rgb(102, 153, 255)', | ||||
|       'rgb(153, 102, 153)', | ||||
|       'rgb(204, 204, 255)', | ||||
|     ]; | ||||
|     const colors = []; | ||||
|     while (colors.length < this.usedStatus.length) { | ||||
|       colors.push(...baseColors); | ||||
|     } | ||||
|     return colors; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the number of userstories in the selected sprint that have the given status. | ||||
|    */ | ||||
|   public getNumberOfUserstoriesByStatus(status: ScrumStatus): number { | ||||
|     return this.selectedSprintUserstories.filter( | ||||
|       (us) => us.statusid === status.id | ||||
|     ).length; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the days remaining until the end date of the selected sprint. | ||||
|    */ | ||||
|   public getRemainingDaysInSprint(): number { | ||||
|     if (this.selectedSprint === undefined) { | ||||
|       return undefined; | ||||
|     } | ||||
|     return Math.floor( | ||||
|       (Date.parse(this.selectedSprint.endDate) - Date.now()) / 86400000 | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the "urgency" of the current sprint (ie what percentage of the sprint is remaining) | ||||
|    * as an integer from 0 (most urgent) to 2 (least urgent). | ||||
|    */ | ||||
|   public getSprintUrgency(): number { | ||||
|     const now = Date.now(); | ||||
|     const sprint = this.selectedSprint; | ||||
|     if (sprint === undefined) { | ||||
|       return undefined; | ||||
|     } | ||||
|     const deltaFromNow = Date.parse(sprint.endDate) - now; | ||||
|     const deltaFromStart = | ||||
|       Date.parse(sprint.endDate) - Date.parse(sprint.startDate); | ||||
|     return Math.floor((3 * deltaFromNow) / deltaFromStart); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user