added svcapi ui and camunda code
[it/otf.git] / otf-frontend / client / src / app / layout / components / stats / multi-line-chart / multi-line-chart.component.ts
diff --git a/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.ts b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.ts
new file mode 100644 (file)
index 0000000..e9782c4
--- /dev/null
@@ -0,0 +1,336 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
+#                                                                              #\r
+#   Licensed under the Apache License, Version 2.0 (the "License");            #\r
+#   you may not use this file except in compliance with the License.           #\r
+#   You may obtain a copy of the License at                                    #\r
+#                                                                              #\r
+#       http://www.apache.org/licenses/LICENSE-2.0                             #\r
+#                                                                              #\r
+#   Unless required by applicable law or agreed to in writing, software        #\r
+#   distributed under the License is distributed on an "AS IS" BASIS,          #\r
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
+#   See the License for the specific language governing permissions and        #\r
+#   limitations under the License.                                             #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';\r
+import { StatsService } from '../stats.service';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+import * as moment from 'moment';\r
+\r
+//am4core.useTheme(am4themes_animated);\r
+\r
+@Component({\r
+  selector: 'app-multi-line-chart',\r
+  templateUrl: './multi-line-chart.component.pug',\r
+  styleUrls: ['./multi-line-chart.component.scss']\r
+})\r
+export class MultiLineChartComponent implements OnInit {\r
+\r
+  @ViewChild('multilinechartdiv') MultiLineChartDiv: ElementRef;\r
+  @Input() height: string;\r
+\r
+  public chart: am4charts.XYChart;\r
+  public loadingIndicator;\r
+  public chartData;\r
+  protected stats: StatsService;\r
+  public dataIsEmpty = 0;\r
+  constructor(private statsService: StatsService) {\r
+    this.stats = statsService;\r
+  }\r
+\r
+  ngOnInit() {\r
+\r
+    this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    })\r
+\r
+    this.stats.onTIExecutionChangeStarted().subscribe(res => {\r
+      this.showLoadingIndicator();\r
+    })\r
+\r
+    this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+      this.renderChart();\r
+      this.hideLoadingIndicator();\r
+    })\r
+\r
+    this.stats.onTIExecutionChangeFinished().subscribe(res => {\r
+      this.renderChart();\r
+      this.hideLoadingIndicator();\r
+    })\r
+    this.renderChart();\r
+\r
+    //Resize if screen size changes.\r
+    // this.stats.checkWindow().subscribe(res=>{\r
+    //   this.renderChart();\r
+    // })\r
+  }\r
+\r
+\r
+  // Rearrange the data to match the format needed for amcharts. Need to group by date. Each object has a date and a list of the lines and its count.\r
+  reformatData() {\r
+    var newData = [];\r
+\r
+    //Names of the test instances. \r
+    var InstanceStrings = {};\r
+\r
+    //Go through the instances and add the names to the InstanceStrings Array. \r
+    for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {\r
+      var instanceName = this.chartData[numberInstances].testInstanceName;\r
+      InstanceStrings[instanceName] = 0;\r
+    }\r
+\r
+    // Iterate through the test instances. \r
+    for (var instanceLength = 0; instanceLength < this.chartData.length; instanceLength++) {\r
+\r
+      //For each instance go through the dates. \r
+      for (var numDates = 0; numDates < this.chartData[instanceLength].dateData.length; numDates++) {\r
+\r
+        //Check newData to see if date has been pushed. \r
+        var dateIndex = newData.findIndex((element) => {\r
+          return (\r
+            this.chartData[instanceLength].dateData[numDates].date.getFullYear() === element.date.getFullYear() &&\r
+            this.chartData[instanceLength].dateData[numDates].date.getMonth() === element.date.getMonth() &&\r
+            this.chartData[instanceLength].dateData[numDates].date.getDate() === element.date.getDate()\r
+          )\r
+        });\r
+\r
+        //If date is not present push the new date and the count for the test instance. \r
+        if (newData[dateIndex] == undefined) {\r
+          //date is not present\r
+          var newDate = {\r
+            date: new Date(this.chartData[instanceLength].dateData[numDates].date.getFullYear(),\r
+              this.chartData[instanceLength].dateData[numDates].date.getMonth(),\r
+              this.chartData[instanceLength].dateData[numDates].date.getDate())\r
+          };\r
+          newDate = Object.assign(newDate, InstanceStrings);\r
+          newDate[this.chartData[instanceLength].testInstanceName] = this.chartData[instanceLength].dateData[numDates].count;\r
+          newData.push(newDate);\r
+        } else {\r
+\r
+          //If the date is present update the count for that test instance. \r
+          newData[dateIndex][this.chartData[instanceLength].testInstanceName] += this.chartData[instanceLength].dateData[numDates].count;\r
+        }\r
+      }\r
+    }\r
+    return newData;\r
+  }\r
+\r
+  //fill in dates that have no data. If a specific date is not present, we need to fill in the date and set a count for 0. \r
+  async setupPoints(rawData): Promise<any> {\r
+    let formattedData = [];\r
+\r
+    //If the chart is supposed to be empty push in a line with a count of 0.\r
+    if (rawData.length == 0) {\r
+      return new Promise((resolve, reject) => {\r
+\r
+        let days = this.daysDuration(this.stats.filters.startDate, this.stats.filters.endDate);\r
+\r
+        if (!days) {\r
+          days = 62;\r
+          this.stats.filters.startDate = (moment().subtract(2, "months").toDate());\r
+        }\r
+\r
+        // Go through 62 days and push a count of 0/\r
+        for (let day = 0; day < days; day++) {\r
+          let newDate = this.addDaysToDate(this.stats.filters.startDate, day);\r
+          formattedData.push({\r
+            date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+            count: 0,\r
+            testInstancename: "empty"\r
+          })\r
+        }\r
+        resolve(formattedData);\r
+      })\r
+\r
+\r
+\r
+\r
+\r
+      //Data is not empty. push in empty days for each instance. Some instances might not have executions for each day. \r
+    } else return new Promise((resolve, reject) => {\r
+      //get list of test instances. \r
+      var InstanceStrings = {};\r
+      for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {\r
+        var instanceName = this.chartData[numberInstances].testInstanceName;\r
+        InstanceStrings[instanceName] = 0;\r
+      }\r
+\r
+\r
+      //Go through the data\r
+      for (let i = 0; i < rawData.length; i++) {\r
+\r
+        //for the first iteration, \r
+        if (i == 0) {\r
+          formattedData.push(rawData[0]);\r
+\r
+          // if the date is before the startDate specified by the filter or two months be default. \r
+          if (formattedData[0].date > this.stats.filters.startDate) {\r
+\r
+            // Go through the difference in days and push the date and count of 0. \r
+            let days = this.daysDuration(this.stats.filters.startDate, formattedData[0].date)\r
+            for (let k = days - 1; k >= 0; k--) {\r
+              let newDate = this.addDaysToDate(this.stats.filters.startDate, k);\r
+              var objectToAdd = {\r
+                date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+              };\r
+              //push the new date and all the strings for the test instances.\r
+              objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+\r
+              //add date to the beginning of the array. \r
+              formattedData.unshift(objectToAdd)\r
+\r
+            }\r
+          }\r
+\r
+          //for all other iterations\r
+        } else {\r
+\r
+          //get the difference in days.\r
+          let days = this.daysDuration(rawData[i].date, rawData[i - 1].date);\r
+          if (days > 1) {\r
+            //push the new dates. \r
+            for (let j = 1; j < days; j++) {\r
+              let newDate = this.addDaysToDate(rawData[i - 1].date, j);\r
+              var objectToAdd = {\r
+                date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+              };\r
+              //push the new date and all the strings for the test instances.\r
+              objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+              formattedData.push(objectToAdd);\r
+            }\r
+          }\r
+          formattedData.push(rawData[i]);\r
+        }\r
+      }\r
+\r
+      if (rawData.length >= 1) {\r
+        var days = this.daysDuration(rawData[rawData.length - 1].date, this.stats.filters.endDate);\r
+        if (days >= 1) {\r
+          for (let j = 1; j < days; j++) {\r
+            let newDate = this.addDaysToDate(rawData[rawData.length - 1].date, j);\r
+            var objectToAdd = {\r
+              date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+            };\r
+            objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+            formattedData.push(objectToAdd);\r
+          }\r
+        }\r
+      }\r
+\r
+\r
+      resolve(formattedData);\r
+    })\r
+  }\r
+\r
+  daysDuration(date1, date2) {\r
+    return Math.ceil(Math.abs((date1 - date2) / (60 * 60 * 24 * 1000)));\r
+  }\r
+\r
+  addDaysToDate(date, days) {\r
+    let newDate = new Date(date);\r
+    newDate.setDate(date.getDate() + days);\r
+    return newDate;\r
+  }\r
+\r
+  //initialize loading indicator\r
+  showLoadingIndicator() {\r
+\r
+    this.height = "380px";\r
+    this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+    this.loadingIndicator.background.fill = am4core.color("#fff");\r
+    this.loadingIndicator.background.fillOpacity = 0.8;\r
+    this.loadingIndicator.width = am4core.percent(100);\r
+    this.loadingIndicator.height = am4core.percent(100);\r
+\r
+    let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+    indicatorLabel.text = "Loading..";\r
+    indicatorLabel.align = "center";\r
+    indicatorLabel.valign = "middle";\r
+    indicatorLabel.fontSize = 18;\r
+    indicatorLabel.fontWeight= "bold";\r
+    indicatorLabel.dy = 50;\r
+\r
+    let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+    //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+    loadingImage.href = "https://loading.io/spinners/equalizer/lg.equalizer-bars-loader.gif";\r
+    //loadingImage.dataSource = "/loading-pies.svg"\r
+    loadingImage.align = "center";\r
+    loadingImage.valign = "middle";\r
+    loadingImage.horizontalCenter = "middle";\r
+    loadingImage.verticalCenter = "middle";\r
+    loadingImage.scale = 3.0;\r
+\r
+  }\r
+\r
+  hideLoadingIndicator() {\r
+    this.loadingIndicator.hide();\r
+  }\r
+\r
+  async renderChart() {\r
+    //console.log("here")\r
+\r
+    am4core.options.minPolylineStep = 5;\r
+\r
+    this.chart = am4core.create(this.MultiLineChartDiv.nativeElement, am4charts.XYChart);\r
+    this.chart.hiddenState.properties.opacity = 0; // this creates initial fade-in\r
+\r
+    this.chart.paddingRight = 20;\r
+    this.chartData = this.stats.getData("multiLineData");\r
+\r
+    //reformat the data to match the format needed for amcharts. \r
+    var formattedData = this.reformatData();\r
+\r
+    //sort the data.\r
+    formattedData.sort((a, b) => a.date - b.date);\r
+\r
+    //fill in gaps in the data\r
+    await this.setupPoints(formattedData).then(res => {\r
+      formattedData = res;\r
+    }, err => console.log(err));\r
+\r
+    this.chart.data = formattedData;\r
+\r
+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());\r
+    dateAxis.title.text = "Date";\r
+\r
+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());\r
+    valueAxis.title.text = "Executions";\r
+\r
+    this.chart.cursor = new am4charts.XYCursor();\r
+    this.chart.cursor.xAxis = dateAxis;\r
+\r
+    //if the data is empty, push in a line and set the count to 0. \r
+    if (this.chartData.length == 0) {\r
+      this.chartData.push({ testInstanceName: "empty" })\r
+      let newSeries = this.chart.series.push(new am4charts.LineSeries());\r
+      newSeries.name = "empty";\r
+      newSeries.dataFields.dateX = "date";\r
+      newSeries.dataFields.valueY = "count";\r
+      newSeries.tooltipText = "{valueY.value}";\r
+      newSeries.sequencedInterpolation = true;\r
+      newSeries.defaultState.transitionDuration = 1000;\r
+      newSeries.strokeWidth = 3;\r
+      newSeries.tensionX = 0.8;\r
+    } else {\r
+\r
+      //initialize the lines for the series\r
+      for (let index = 0; index < this.chartData.length; index++) {\r
+        let newSeries = this.chart.series.push(new am4charts.LineSeries());\r
+        newSeries.name = this.chartData[index].testInstanceName;\r
+        newSeries.dataFields.dateX = "date";\r
+        newSeries.dataFields.valueY = (this.chartData[index].testInstanceName).toString();\r
+        newSeries.tooltipText = "{valueY.value}";\r
+        newSeries.sequencedInterpolation = true;\r
+        newSeries.defaultState.transitionDuration = 1000;\r
+        newSeries.strokeWidth = 3;\r
+        newSeries.tensionX = 0.8;\r
+      }\r
+      this.chart.legend = new am4charts.Legend();\r
+      this.chart.legend.labels.template.text = "[bold {color}]{name}";\r
+    }\r
+  }\r
+\r
+}
\ No newline at end of file