diff --git a/examples/example.js b/examples/example.js index abf64cd..5f29901 100644 --- a/examples/example.js +++ b/examples/example.js @@ -9,7 +9,7 @@ import { processServiceData, eventBus } from "../dist/graphs-renderer.js"; -import {initializeForm,toggleRightSidebar} from "./sidebars.js" +import {initializeForm, toggleRightSidebar} from "./sidebars.js" let removedTicketTypes = ["task"]; let removedRepos = ["wizard-lambda"]; @@ -33,7 +33,8 @@ async function renderGraphs(data, serviceId) { const cfdGraphElementSelector = "#cfd-area-div"; const cfdBrushElementSelector = "#cfd-brush-div"; //Create a CFDGraph - const cfdGraph = new CFDGraph(data); + const states = ['analysis_active', 'analysis_done', 'in_progress', 'dev_complete', 'verif_start', 'delivered']; + const cfdGraph = new CFDGraph(data, states); //Compute the dataset for a cfd graph const cfdGraphDataSet = cfdGraph.computeDataSet(); //Create a CFDRenderer diff --git a/src/graphs/cfd/CFDGraph.js b/src/graphs/cfd/CFDGraph.js index 3aaa80a..08e01dc 100644 --- a/src/graphs/cfd/CFDGraph.js +++ b/src/graphs/cfd/CFDGraph.js @@ -1,4 +1,5 @@ import * as d3 from 'd3'; + /** * Class representing a Cumulative Flow Diagram (CFD) Graph Data */ @@ -36,8 +37,9 @@ class CFDGraph { * } * ]; */ - constructor(data) { + constructor(data, states) { this.data = data; + this.states = states; } /** @@ -69,20 +71,20 @@ class CFDGraph { */ computeDataSet() { const dataSet = []; - //Get the min and max dates from the dataSet set - const minDate = d3.min(this.data, (d) => d.delivered); - const maxDate = d3.max(this.data, (d) => d.delivered); - for (let i = new Date(minDate * 1000); i < new Date(maxDate * 1000); i.setDate(i.getDate() + 1)) { - const currentDate = new Date(i); - currentDate.setHours(0, 0, 0, 0); + const minDate = new Date(d3.min(this.data, (d) => d.delivered) * 1000); + const maxDate = new Date(d3.max(this.data, (d) => d.delivered) * 1000); + minDate.setHours(0, 0, 0, 0); + maxDate.setHours(0, 0, 0, 0); + for (let date = minDate; date < maxDate; date.setDate(date.getDate() + 1)) { + const currentDate = date.getTime() / 1000; dataSet.push({ date: currentDate, - delivered: this.#getNoOfTicketsInDeliveredState(currentDate.getTime() / 1000), - verif_start: this.#getNoOfTicketsInVerificationStartState(currentDate.getTime() / 1000), - dev_complete: this.#getNoOfTicketsInDevCompleteState(currentDate.getTime() / 1000), - in_progress: this.#getNoOfTicketsInProgressState(currentDate.getTime() / 1000), - analysis_done: this.#getNoOfTicketsInAnalysisDoneState(currentDate.getTime() / 1000), - analysis_active: this.#getNoOfTicketsInAnalysisActiveState(currentDate.getTime() / 1000), + delivered: this.#getNoOfTicketsInState(this.states[5], currentDate), + verif_start: this.#getNoOfTicketsInState(this.states[4], currentDate), + dev_complete: this.#getNoOfTicketsInState(this.states[3], currentDate), + in_progress: this.#getNoOfTicketsInState(this.states[2], currentDate), + analysis_done: this.#getNoOfTicketsInState(this.states[1], currentDate), + analysis_active: this.#getNoOfTicketsInState(this.states[0], currentDate), }); } if (dataSet.length > 0) { @@ -93,73 +95,22 @@ class CFDGraph { return dataSet; } - #getNoOfTicketsInAnalysisActiveState(ticketTimestamp) { - return this.data.filter((d) => { - if (!d.analysis_active) { - return false; - } - if (!d.analysis_done && ticketTimestamp >= d.analysis_active) { - return true; - } - return ticketTimestamp >= d.analysis_active && ticketTimestamp < d.analysis_done; - }).length; - } - - #getNoOfTicketsInAnalysisDoneState(ticketTimestamp) { - return this.data.filter((d) => { - if (!d.analysis_done) { - return false; - } - if (!d.in_progress && ticketTimestamp >= d.analysis_done) { - return true; - } - return ticketTimestamp >= d.analysis_done && ticketTimestamp < d.in_progress; - }).length; - } - - #getNoOfTicketsInProgressState(ticketTimestamp) { - return this.data.filter((d) => { - if (!d.in_progress) { - return false; - } - if (!d.dev_complete && ticketTimestamp >= d.in_progress) { - return true; - } - return ticketTimestamp >= d.in_progress && ticketTimestamp < d.dev_complete; - }).length; - } - - #getNoOfTicketsInDevCompleteState(ticketTimestamp) { + #getNoOfTicketsInState(state, timestamp) { return this.data.filter((d) => { - if (!d.dev_complete) { + if (!d[state]) { return false; } - if (!d.verification_start && ticketTimestamp >= d.dev_complete) { - return true; + const nextState = this.#getNextState(state); + if (!nextState) { + return d[state] <= timestamp; } - return ticketTimestamp >= d.dev_complete && ticketTimestamp < d.verification_start; + return d[state] <= timestamp && d[nextState] > timestamp; }).length; } - #getNoOfTicketsInVerificationStartState(ticketTimestamp) { - return this.data.filter((d) => { - if (!d.verification_start) { - return false; - } - if (!d.delivered && ticketTimestamp >= d.verification_start) { - return true; - } - return ticketTimestamp >= d.verification_start && ticketTimestamp < d.delivered; - }).length; - } - - #getNoOfTicketsInDeliveredState(ticketTimestamp) { - return this.data.filter((d) => { - if (!d.delivered) { - return false; - } - return ticketTimestamp >= d.delivered; - }).length; + #getNextState(state) { + const index = this.states.indexOf(state); + return index >= 0 && index < this.states.length - 1 ? this.states[index + 1] : null; } } diff --git a/src/graphs/cfd/CFDRenderer.js b/src/graphs/cfd/CFDRenderer.js index b295f19..04ddf96 100644 --- a/src/graphs/cfd/CFDRenderer.js +++ b/src/graphs/cfd/CFDRenderer.js @@ -1,4 +1,4 @@ -import { addDaysToDate, getNoOfDaysBetweenDates, areDatesEqual, formatDateToLocalString } from '../../utils/utils.js'; +import { addDaysToDate, calculateDaysBetweenDates, areDatesEqual, formatDateToLocalString } from '../../utils/utils.js'; import UIControlsRenderer from '../UIControlsRenderer.js'; import styles from '../tooltipStyles.module.css'; import * as d3 from 'd3'; @@ -117,13 +117,13 @@ class CFDRenderer extends UIControlsRenderer { if (this.currentSelectionDomain) { endDate = new Date(this.currentSelectionDomain[1]); startDate = new Date(this.currentSelectionDomain[0]); - const diffDays = Number(noOfDays) - getNoOfDaysBetweenDates(startDate, endDate); + const diffDays = Number(noOfDays) - calculateDaysBetweenDates(startDate, endDate); if (diffDays < 0) { startDate = addDaysToDate(startDate, -Number(diffDays)); } else { endDate = addDaysToDate(endDate, Number(diffDays)); if (endDate > finalDate) { - const diffEndDays = getNoOfDaysBetweenDates(finalDate, endDate); + const diffEndDays = calculateDaysBetweenDates(finalDate, endDate); endDate = finalDate; startDate = addDaysToDate(startDate, -Number(diffEndDays)); } @@ -277,8 +277,9 @@ class CFDRenderer extends UIControlsRenderer { } } const averageCycleTime = - cycleTimeDateBefore && currentDate ? Math.floor(getNoOfDaysBetweenDates(cycleTimeDateBefore, currentDate)) : null; - const averageLeadTime = leadTimeDateBefore && currentDate ? Math.floor(getNoOfDaysBetweenDates(leadTimeDateBefore, currentDate)) : null; + cycleTimeDateBefore && currentDate ? Math.floor(calculateDaysBetweenDates(cycleTimeDateBefore, currentDate)) : null; + const averageLeadTime = + leadTimeDateBefore && currentDate ? Math.floor(calculateDaysBetweenDates(leadTimeDateBefore, currentDate)) : null; let throughput = 0; if (averageLeadTime) { const diff = (this.#getNoOfItems(currentDataEntry, this.#keys[this.#keys.length - 1]) - currentDeliveredItems) / averageLeadTime; @@ -296,7 +297,7 @@ class CFDRenderer extends UIControlsRenderer { updateChart(domain) { const maxY = d3.max(this.#stackedData[this.#stackedData.length - 1], (d) => (d.data.date <= domain[1] ? d[1] : -1)); - this.setReportingRangeDays(getNoOfDaysBetweenDates(domain[0], domain[1])); + this.setReportingRangeDays(calculateDaysBetweenDates(domain[0], domain[1])); this.currentXScale = this.x.copy().domain(domain); this.currentYScale = this.y.copy().domain([0, maxY]).nice(); this.drawXAxis(this.gx, this.currentXScale, this.rangeIncrementUnits, this.height); diff --git a/src/graphs/scatterplot/ScatterplotGraph.js b/src/graphs/scatterplot/ScatterplotGraph.js index eb1f673..d4cd92c 100644 --- a/src/graphs/scatterplot/ScatterplotGraph.js +++ b/src/graphs/scatterplot/ScatterplotGraph.js @@ -1,3 +1,5 @@ +import { calculateDaysBetweenDates } from '../../utils/utils.js'; + /** * Class representing a Scatterplot Graph Data */ @@ -59,31 +61,20 @@ class ScatterplotGraph { * ]; */ computeDataSet() { - const dataSet = []; - this.data.forEach((ticket) => { - if (ticket.delivered) { + return this.data + .filter((ticket) => ticket.delivered) + .map((ticket) => { const deliveredDate = new Date(ticket.delivered * 1000); + const startDate = ticket.analysis_active || ticket.analysis_done; + const noOfDays = startDate ? calculateDaysBetweenDates(startDate, ticket.delivered) : 0; deliveredDate.setHours(0, 0, 0, 0); - const scatterplotTicket = { + return { delivered: deliveredDate, - noOfDays: 0, + noOfDays: noOfDays, ticketId: ticket.work_id, }; - if (ticket.analysis_active || ticket.analysis_done) { - scatterplotTicket.noOfDays = this.#getNoOfDeliveryDays(ticket.analysis_active || ticket.analysis_done, ticket.delivered); - } - dataSet.push(scatterplotTicket); - } - }); - dataSet.sort((t1, t2) => t1.delivered - t2.delivered); - return dataSet; - } - - #getNoOfDeliveryDays(startTimestamp, deliveredTimestamp) { - const oneDayInSeconds = 60 * 60 * 24; - const diffTimeInSeconds = deliveredTimestamp - startTimestamp; - const noOfDays = Math.floor(diffTimeInSeconds / oneDayInSeconds); - return noOfDays; + }) + .sort((t1, t2) => t1.delivered - t2.delivered); } } diff --git a/src/graphs/scatterplot/ScatterplotRenderer.js b/src/graphs/scatterplot/ScatterplotRenderer.js index dad977c..deed66f 100644 --- a/src/graphs/scatterplot/ScatterplotRenderer.js +++ b/src/graphs/scatterplot/ScatterplotRenderer.js @@ -1,4 +1,4 @@ -import { getNoOfDaysBetweenDates, addDaysToDate } from '../../utils/utils.js'; +import { calculateDaysBetweenDates, addDaysToDate } from '../../utils/utils.js'; import UIControlsRenderer from '../UIControlsRenderer.js'; import styles from '../tooltipStyles.module.css'; @@ -101,13 +101,13 @@ class ScatterplotRenderer extends UIControlsRenderer { if (this.currentSelectionDomain) { endDate = new Date(this.currentSelectionDomain[1]); startDate = new Date(this.currentSelectionDomain[0]); - const diffDays = Number(noOfDays) - getNoOfDaysBetweenDates(startDate, endDate); + const diffDays = Number(noOfDays) - calculateDaysBetweenDates(startDate, endDate); if (diffDays < 0) { startDate = addDaysToDate(startDate, -Number(diffDays)); } else { endDate = addDaysToDate(endDate, Number(diffDays)); if (endDate > finalDate) { - const diffEndDays = getNoOfDaysBetweenDates(finalDate, endDate); + const diffEndDays = calculateDaysBetweenDates(finalDate, endDate); endDate = finalDate; startDate = addDaysToDate(startDate, -Number(diffEndDays)); } @@ -193,7 +193,7 @@ class ScatterplotRenderer extends UIControlsRenderer { updateChart(domain) { const maxY = d3.max(this.data, (d) => (d.delivered <= domain[1] && d.delivered >= domain[0] ? d.noOfDays : -1)); - this.setReportingRangeDays(getNoOfDaysBetweenDates(domain[0], domain[1])); + this.setReportingRangeDays(calculateDaysBetweenDates(domain[0], domain[1])); this.currentXScale = this.x.copy().domain(domain); this.currentYScale = this.y.copy().domain([0, maxY]).nice(); const focusData = this.data.filter((d) => d.delivered <= domain[1] && d.delivered >= domain[0]); diff --git a/src/utils/utils.js b/src/utils/utils.js index e78f5ff..ddc409e 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -23,8 +23,11 @@ export function addDaysToDate(date, noOfDays) { return new Date(date.getTime() + noOfDays * (1000 * 3600 * 24)); } -export function getNoOfDaysBetweenDates(startDate, finalDate) { - return (finalDate.getTime() - startDate.getTime()) / (1000 * 3600 * 24); +export function calculateDaysBetweenDates(startDate, endDate, roundDown = true) { + const startMillis = startDate instanceof Date ? startDate.getTime() : startDate * 1000; + const endMillis = endDate instanceof Date ? endDate.getTime() : endDate * 1000; + const diffDays = (endMillis - startMillis) / (1000 * 3600 * 24); + return roundDown ? Math.floor(diffDays) : diffDays; } export function areDatesEqual(date1, date2) {