Skip to content

Commit

Permalink
Merge pull request #6 from etsauer/finish-cfr
Browse files Browse the repository at this point in the history
Enable chart and table for change failure rate
  • Loading branch information
etsauer authored Mar 14, 2024
2 parents 3976fa7 + 82fe01c commit 84c6a84
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 579 deletions.
28 changes: 28 additions & 0 deletions components/change-failure-rate/changeFailureRate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// components/deploymentFrequency.js
import { useState, useEffect } from 'react';
import { getDaysBetweenDates } from '@/components/date-range-selector'

export default function useChangeFailureRate(appName, dateRange) {
const [cfrData, setCfrData] = useState([]);
const [loading, setLoading] = useState(true); // Add loading state

useEffect(() => {
const fetchData = async () => {
try {
const req = `${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/change_failure_rate/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`;
const response = await fetch(req);
const data = await response.json();
const sortedData = data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0);
setCfrData(sortedData);
} catch (error) {
console.error('Error fetching deployment frequency data:', error);
} finally {
setLoading(false); // Set loading to false regardless of success or failure
}
};

fetchData();
}, [appName, dateRange]);

return { cfrData, loading }; // Return loading state along with cfrData
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import { format } from 'date-fns'
import { useTheme } from "next-themes"
import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ComposedChart, Area, Line, ReferenceLine } from 'recharts'
import { ChangeFailureRateTooltip } from './tooltip'

const dateFormatter = date => {
return format(new Date(date), "MMM d")
}
import { dateFormatter, dayFormatter } from '@/lib/date-funcs';
import useChangeFailureRate from './changeFailureRate'

const calculateMean = data => {
if (data.length < 1) {
Expand All @@ -17,7 +15,39 @@ const calculateMean = data => {
return data.reduce((prev, current) => prev + current) / data.length
}

export function IsolatedChangeFailureRateChart({ data }) {
function eventsPerDay(data) {
// Object to store the count of deployments per day
const countPerDay = {};

// Loop through each item in the data array
data.forEach(item => {
// Convert timestamp to Date object and extract the date
const date = new Date(item.timestamp * 1000);
// Format the date to YYYY-MM-DD
const formattedDate = date.toLocaleDateString().split('T')[0];

// Increment the count of deployments for the corresponding date
if (countPerDay[formattedDate]) {
countPerDay[formattedDate]++;
} else {
countPerDay[formattedDate] = 1;
}
});

// Convert the deployments count per day object into an array of objects
const result = Object.keys(countPerDay).map(date => ({
// Convert date back to epoch format and assign to day_epoch property
day_epoch: new Date(date).getTime() / 1000,
// Assign the count of deployments to the count property
count: countPerDay[date]
}));

// Return the result
return result;
}


export function ChangeFailureRateChart({ dateRange, appName }) {

const { resolvedTheme } = useTheme()
const animationDuration = 1000
Expand All @@ -36,27 +66,37 @@ export function IsolatedChangeFailureRateChart({ data }) {
const strokeRollingAverage = '#f43f5e' // Rose 500
const strokeGoal = '#f59e0b' // Amber 500

const { cfrData, loading } = useChangeFailureRate(appName, dateRange);
console.log('Chart cfrData: ', cfrData)

if (loading) {
return <div>Loading...</div>; // Render loading state while data is being fetched
}

var countPerDay = eventsPerDay(cfrData);
console.log('Count per day', countPerDay)

// Calculate the mean
const averages = data.map(element => {
const averages = cfrData.map(element => {
return element.rollingAverage
})

const chartMean = calculateMean(averages)

// Reports
const [reportChangeFailureRateData, setReportChangeFailureRateData] = useState(null)
const [showReportChangeFailureRateData, setShowReportChangeFailureRateData] = useState(false)
// // Reports
// const [reportChangeFailureRateData, setReportChangeFailureRateData] = useState(null)
// const [showReportChangeFailureRateData, setShowReportChangeFailureRateData] = useState(false)

function handleChartClick(event) {
setReportChangeFailureRateData(event)
setShowReportChangeFailureRateData(true)
}
// function handleChartClick(event) {
// setReportChangeFailureRateData(event)
// setShowReportChangeFailureRateData(true)
// }

// Anomaly detection
const showAnomalyWarning = data.some((day) => {
if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true }
return false
})
// // Anomaly detection
// const showAnomalyWarning = data.some((day) => {
// if (day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1]) { return true }
// return false
// })

const customAnomalyLabel = props => {
return (
Expand All @@ -70,20 +110,20 @@ export function IsolatedChangeFailureRateChart({ data }) {
return (
<>
<ResponsiveContainer width="100%" height="100%">
<ComposedChart data={data} margin={{ top: 0, left: 0, right: 4, bottom: 0 }} onClick={handleChartClick}>
<ComposedChart data={countPerDay} margin={{ top: 0, left: 0, right: 4, bottom: 0 }}>
<CartesianGrid vertical={false} stroke={resolvedTheme === 'dark' ? strokeGridDark : strokeGrid} />
<XAxis style={{ fontSize: '0.75rem' }} dataKey="date" axisLine={false} tickLine={false} tickFormatter={dateFormatter} />
<YAxis style={{ fontSize: '0.75rem' }} domain={[0, 100]} axisLine={false} tickLine={false} tickFormatter={tick => `${tick}%`} />
<XAxis style={{ fontSize: '0.75rem' }} dataKey="day_epoch" axisLine={false} tickLine={false} tickFormatter={dateFormatter} />
<YAxis style={{ fontSize: '0.75rem' }} axisLine={false} tickLine={false} />
<Tooltip content={<ChangeFailureRateTooltip />} cursor={{ stroke: strokeCursor }} />
<Area type="monotone" dataKey="expectedRange" activeDot={resolvedTheme === 'dark' ? { stroke: strokeActiveDotDark } : { stroke: strokeActiveDot }} fill={resolvedTheme === 'dark' ? fillRangeDark : fillRange} stroke={strokeRange} strokeWidth={0} strokeDasharray="4 4" animationDuration={animationDuration} />
<Line type="monotone" dataKey="rollingAverage" dot={false} activeDot={resolvedTheme === 'dark' ? { stroke: strokeActiveDotDark } : { stroke: strokeActiveDot }} stroke={strokeRollingAverage} strokeWidth={3} strokeLinecap="round" animationDuration={animationDuration} />
<Line type="monotone" dataKey="count" dot={false} activeDot={resolvedTheme === 'dark' ? { stroke: strokeActiveDotDark } : { stroke: strokeActiveDot }} stroke={strokeRollingAverage} strokeWidth={3} strokeLinecap="round" animationDuration={animationDuration} />
<Line type="monotone" dataKey="goal" dot={false} activeDot={resolvedTheme === 'dark' ? { stroke: strokeActiveDotDark } : { stroke: strokeActiveDot }} stroke={strokeGoal} strokeWidth={2} strokeDasharray="4 4" strokeLinecap="round" isAnimationActive={false} />

{data.map((day, index) => (
{/* {data.map((day, index) => (
day.rollingAverage < day.expectedRange[0] || day.rollingAverage > day.expectedRange[1] && (
<ReferenceLine className="anomaly-reference-line" key={index} x={day.date} stroke={strokeAnomaly} label={customAnomalyLabel} />
)
))}
))} */}
</ComposedChart>
</ResponsiveContainer>
{/* <ChangeFailureRateReport reportChangeFailureRateData={reportChangeFailureRateData} showReportChangeFailureRateData={showReportChangeFailureRateData} setShowReportChangeFailureRateData={setShowReportChangeFailureRateData} /> */}
Expand Down
Loading

0 comments on commit 84c6a84

Please sign in to comment.