-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
774 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,5 +53,8 @@ | |
"prettier --write" | ||
] | ||
}, | ||
"packageManager": "[email protected]" | ||
"packageManager": "[email protected]", | ||
"dependencies": { | ||
"recharts": "^2.15.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
backstage-plugins/plugins/devex-survey-plugin/src/components/charts/ChallengesBreakdown.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { InfoCard } from '@backstage/core-components'; | ||
import React from 'react'; | ||
import { | ||
Cell, | ||
Legend, | ||
Pie, | ||
PieChart, | ||
ResponsiveContainer, | ||
Tooltip, | ||
} from 'recharts'; | ||
import { SurveyResult } from '../../pages/admin'; | ||
|
||
type Props = { | ||
results: SurveyResult[]; | ||
}; | ||
|
||
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042']; | ||
|
||
const renderCustomizedLabel = ({ | ||
cx, | ||
cy, | ||
midAngle, | ||
innerRadius, | ||
outerRadius, | ||
value, | ||
}: any) => { | ||
const RADIAN = Math.PI / 180; | ||
const radius = innerRadius + (outerRadius - innerRadius) * 0.5; | ||
const x = cx + radius * Math.cos(-midAngle * RADIAN); | ||
const y = cy + radius * Math.sin(-midAngle * RADIAN); | ||
|
||
return ( | ||
<text | ||
x={x} | ||
y={y} | ||
fill="white" | ||
textAnchor="middle" | ||
dominantBaseline="central" | ||
> | ||
{value} | ||
</text> | ||
); | ||
}; | ||
|
||
export const ChallengesBreakdown = ({ results }: Props) => { | ||
const data = React.useMemo(() => { | ||
const counts = results.reduce((acc, curr) => { | ||
acc[curr.primary_blocker] = (acc[curr.primary_blocker] || 0) + 1; | ||
return acc; | ||
}, {} as Record<string, number>); | ||
|
||
return Object.entries(counts).map(([key, value]) => ({ | ||
name: key.replace(/_/g, ' '), | ||
value, | ||
})); | ||
}, [results]); | ||
|
||
return ( | ||
<InfoCard title="All Challenges Distribution"> | ||
<div style={{ width: '100%', height: 300 }}> | ||
<ResponsiveContainer> | ||
<PieChart> | ||
<Pie | ||
data={data} | ||
cx="50%" | ||
cy="50%" | ||
labelLine={false} | ||
outerRadius={80} | ||
fill="#8884d8" | ||
dataKey="value" | ||
label={renderCustomizedLabel} | ||
> | ||
{data.map((_, index) => ( | ||
<Cell | ||
key={`cell-${index}`} | ||
fill={COLORS[index % COLORS.length]} | ||
/> | ||
))} | ||
</Pie> | ||
<Tooltip /> | ||
<Legend /> | ||
</PieChart> | ||
</ResponsiveContainer> | ||
</div> | ||
</InfoCard> | ||
); | ||
}; |
163 changes: 163 additions & 0 deletions
163
backstage-plugins/plugins/devex-survey-plugin/src/components/charts/ChallengesDetails.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
import { InfoCard } from '@backstage/core-components'; | ||
import { Grid } from '@material-ui/core'; | ||
import React from 'react'; | ||
import { | ||
Cell, | ||
Legend, | ||
Pie, | ||
PieChart, | ||
ResponsiveContainer, | ||
Tooltip, | ||
} from 'recharts'; | ||
import { SurveyResult } from '../../pages/admin'; | ||
|
||
type Props = { | ||
results: SurveyResult[]; | ||
}; | ||
|
||
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#FF99CC']; | ||
|
||
const renderCustomizedLabel = ({ | ||
cx, | ||
cy, | ||
midAngle, | ||
innerRadius, | ||
outerRadius, | ||
value, | ||
}: any) => { | ||
const RADIAN = Math.PI / 180; | ||
const radius = innerRadius + (outerRadius - innerRadius) * 0.5; | ||
const x = cx + radius * Math.cos(-midAngle * RADIAN); | ||
const y = cy + radius * Math.sin(-midAngle * RADIAN); | ||
|
||
return ( | ||
<text | ||
x={x} | ||
y={y} | ||
fill="white" | ||
textAnchor="middle" | ||
dominantBaseline="central" | ||
> | ||
{value} | ||
</text> | ||
); | ||
}; | ||
|
||
const ChallengePieChart = ({ | ||
title, | ||
data, | ||
}: { | ||
title: string; | ||
data: { name: string; value: number }[]; | ||
}) => ( | ||
<InfoCard title={title}> | ||
<div style={{ width: '100%', height: 300 }}> | ||
<ResponsiveContainer> | ||
<PieChart> | ||
<Pie | ||
data={data} | ||
cx="50%" | ||
cy="50%" | ||
labelLine={false} | ||
outerRadius={80} | ||
fill="#8884d8" | ||
dataKey="value" | ||
label={renderCustomizedLabel} | ||
> | ||
{data.map((_, index) => ( | ||
<Cell | ||
key={`cell-${index}`} | ||
fill={COLORS[index % COLORS.length]} | ||
/> | ||
))} | ||
</Pie> | ||
<Tooltip /> | ||
<Legend /> | ||
</PieChart> | ||
</ResponsiveContainer> | ||
</div> | ||
</InfoCard> | ||
); | ||
|
||
export const ChallengesDetails = ({ results }: Props) => { | ||
const workPlanningData = React.useMemo(() => { | ||
const counts = results.reduce((acc, curr) => { | ||
acc[curr.work_planning_challenges] = | ||
(acc[curr.work_planning_challenges] || 0) + 1; | ||
return acc; | ||
}, {} as Record<string, number>); | ||
|
||
return Object.entries(counts).map(([key, value]) => ({ | ||
name: key.replace(/_/g, ' '), | ||
value, | ||
})); | ||
}, [results]); | ||
|
||
const developmentProcessData = React.useMemo(() => { | ||
const counts = results.reduce((acc, curr) => { | ||
acc[curr.development_process_challenges] = | ||
(acc[curr.development_process_challenges] || 0) + 1; | ||
return acc; | ||
}, {} as Record<string, number>); | ||
|
||
return Object.entries(counts).map(([key, value]) => ({ | ||
name: key.replace(/_/g, ' '), | ||
value, | ||
})); | ||
}, [results]); | ||
|
||
const shippingFeaturesData = React.useMemo(() => { | ||
const counts = results.reduce((acc, curr) => { | ||
acc[curr.shipping_features_challenges] = | ||
(acc[curr.shipping_features_challenges] || 0) + 1; | ||
return acc; | ||
}, {} as Record<string, number>); | ||
|
||
return Object.entries(counts).map(([key, value]) => ({ | ||
name: key.replace(/_/g, ' '), | ||
value, | ||
})); | ||
}, [results]); | ||
|
||
const productionChallengesData = React.useMemo(() => { | ||
const counts = results.reduce((acc, curr) => { | ||
acc[curr.managing_production_challenges] = | ||
(acc[curr.managing_production_challenges] || 0) + 1; | ||
return acc; | ||
}, {} as Record<string, number>); | ||
|
||
return Object.entries(counts).map(([key, value]) => ({ | ||
name: key.replace(/_/g, ' '), | ||
value, | ||
})); | ||
}, [results]); | ||
|
||
return ( | ||
<Grid container spacing={3}> | ||
<Grid item md={6} xs={12}> | ||
<ChallengePieChart | ||
title="Work Planning Challenges" | ||
data={workPlanningData} | ||
/> | ||
</Grid> | ||
<Grid item md={6} xs={12}> | ||
<ChallengePieChart | ||
title="Development Process Challenges" | ||
data={developmentProcessData} | ||
/> | ||
</Grid> | ||
<Grid item md={6} xs={12}> | ||
<ChallengePieChart | ||
title="Shipping Features Challenges" | ||
data={shippingFeaturesData} | ||
/> | ||
</Grid> | ||
<Grid item md={6} xs={12}> | ||
<ChallengePieChart | ||
title="Production Management Challenges" | ||
data={productionChallengesData} | ||
/> | ||
</Grid> | ||
</Grid> | ||
); | ||
}; |
72 changes: 72 additions & 0 deletions
72
backstage-plugins/plugins/devex-survey-plugin/src/components/charts/ResponsesOverTime.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { InfoCard } from '@backstage/core-components'; | ||
import React from 'react'; | ||
import { | ||
CartesianGrid, | ||
Legend, | ||
Line, | ||
LineChart, | ||
ResponsiveContainer, | ||
Tooltip, | ||
XAxis, | ||
YAxis, | ||
} from 'recharts'; | ||
import { SurveyResult } from '../../pages/admin'; | ||
|
||
type Props = { | ||
results: SurveyResult[]; | ||
}; | ||
|
||
export const ResponsesOverTime = ({ results }: Props) => { | ||
const data = React.useMemo(() => { | ||
// Group responses by date | ||
const responsesByDate = results.reduce((acc, curr) => { | ||
const date = curr.last_updated.toLocaleDateString(); | ||
acc[date] = (acc[date] || 0) + 1; | ||
return acc; | ||
}, {} as Record<string, number>); | ||
|
||
// Convert to array and sort by date | ||
const sortedData = Object.entries(responsesByDate) | ||
.map(([date, count]) => ({ | ||
date, | ||
responses: count, | ||
total: 0, // Will be calculated next | ||
})) | ||
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); | ||
|
||
// Calculate running total | ||
let runningTotal = 0; | ||
return sortedData.map(item => ({ | ||
...item, | ||
total: (runningTotal += item.responses), | ||
})); | ||
}, [results]); | ||
|
||
return ( | ||
<InfoCard title="Responses Over Time"> | ||
<div style={{ width: '100%', height: 300 }}> | ||
<ResponsiveContainer> | ||
<LineChart data={data}> | ||
<CartesianGrid strokeDasharray="3 3" /> | ||
<XAxis dataKey="date" /> | ||
<YAxis /> | ||
<Tooltip /> | ||
<Legend /> | ||
<Line | ||
type="monotone" | ||
dataKey="responses" | ||
stroke="#8884d8" | ||
name="Daily Responses" | ||
/> | ||
<Line | ||
type="monotone" | ||
dataKey="total" | ||
stroke="#82ca9d" | ||
name="Total Responses" | ||
/> | ||
</LineChart> | ||
</ResponsiveContainer> | ||
</div> | ||
</InfoCard> | ||
); | ||
}; |
35 changes: 35 additions & 0 deletions
35
backstage-plugins/plugins/devex-survey-plugin/src/components/charts/SurveyStats.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { InfoCard } from '@backstage/core-components'; | ||
import { Grid, Typography } from '@material-ui/core'; | ||
import React from 'react'; | ||
import { SurveyResult } from '../../pages/admin'; | ||
|
||
type Props = { | ||
results: SurveyResult[]; | ||
}; | ||
|
||
export const SurveyStats = ({ results }: Props) => { | ||
const totalResponses = results.length; | ||
const lastResponseDate = results.length | ||
? new Date( | ||
Math.max(...results.map(r => new Date(r.last_updated).getTime())), | ||
) | ||
: null; | ||
|
||
return ( | ||
<InfoCard title="Survey Statistics"> | ||
<Grid container spacing={3}> | ||
<Grid item xs={5}> | ||
<Typography variant="h4">{totalResponses}</Typography> | ||
<Typography variant="subtitle1">Total Respondents</Typography> | ||
</Grid> | ||
<Grid item xs={5}> | ||
<Typography variant="h4"> | ||
{lastResponseDate?.toLocaleDateString()}{' '} | ||
{lastResponseDate?.toLocaleTimeString()} | ||
</Typography> | ||
<Typography variant="subtitle1">Last Response</Typography> | ||
</Grid> | ||
</Grid> | ||
</InfoCard> | ||
); | ||
}; |
Oops, something went wrong.