Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/components/Analytics/AnalyticsDashboard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useState } from 'react';
import ReviewersRequirementChart from './ReviewersRequirementChart';

const durationOptions = [
{ label: 'Last Week', value: 'lastWeek' },
{ label: 'Last 2 weeks', value: 'last2weeks' },
{ label: 'Last Month', value: 'lastMonth' },
{ label: 'All Time', value: 'allTime' },
];

const AnalyticsDashboard = () => {
const [duration, setDuration] = useState('lastWeek');

return (
<div style={{ padding: '2rem' }}>
<div style={{ marginBottom: '2rem' }}>
<label htmlFor="duration">Duration: </label>

<select id="duration" value={duration} onChange={e => setDuration(e.target.value)}>
{durationOptions.map(opt => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>

<div style={{ marginBottom: '3rem' }}>
<h2 style={{ marginBottom: '1rem' }}>Reviewers Ranked by Requirement Satisfied</h2>
<ReviewersRequirementChart duration={duration} />
</div>
</div>
);
};

export default AnalyticsDashboard;
66 changes: 66 additions & 0 deletions src/components/Analytics/PopularPRChart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import { Bar, BarChart, LabelList, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';

const data = [
{ prNumber: 'PR1234', reviews: 10 },
{ prNumber: 'PR1235', reviews: 8 },
{ prNumber: 'PR1236', reviews: 15 },
{ prNumber: 'PR1237', reviews: 5 },
{ prNumber: 'PR1238', reviews: 12 },
{ prNumber: 'PR1231', reviews: 12 },
{ prNumber: 'PR1256', reviews: 17 },
{ prNumber: 'PR1255', reviews: 19 },
{ prNumber: 'PR1254', reviews: 1 },
{ prNumber: 'PR1253', reviews: 2 },
{ prNumber: 'PR1252', reviews: 7 },
{ prNumber: 'PR1251', reviews: 9 },
{ prNumber: 'PR1250', reviews: 11 },
{ prNumber: 'PR1249', reviews: 19 },
{ prNumber: 'PR1248', reviews: 0 },
{ prNumber: 'PR1242', reviews: 2 },
{ prNumber: 'PR1241', reviews: 50 },
];

const PopularPRChart = () => {
const filteredData = data;

return (
<div style={{ width: '100%', height: 500, overflowY: 'auto' }}>
<ResponsiveContainer width="100%" height={400}>
<BarChart
data={filteredData}
layout="vertical"
margin={{ top: 20, right: 30, left: 150, bottom: 40 }}
>
<XAxis
type="number"
label={{
value: 'Number of Reviews',
position: 'insideBottom',
offset: -5,
}}
/>

<YAxis
dataKey="prNumber"
type="category"
label={{
value: 'PR Numbers',
angle: -90,
position: 'insideLeft',
}}
width={100}
/>

<Tooltip />

<Bar dataKey="reviews" fill="#052C65">
<LabelList dataKey="reviews" position="right" />
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
);
};

export default PopularPRChart;
121 changes: 121 additions & 0 deletions src/components/Analytics/ReviewersRequirementChart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import {
Bar,
BarChart,
LabelList,
Legend,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import { ENDPOINTS } from '../../utils/URL';

const getCount = (counts, key) => counts?.[key] || 0;

const normalizeReviewCounts = item => {
const counts = item.counts || {};
const exceptional = getCount(counts, 'Exceptional');
const sufficient = getCount(counts, 'Sufficient');
const needsChanges = getCount(counts, 'Needs Changes');
const didNotReview = getCount(counts, 'Did Not Review');

return {
reviewer: item.reviewer,
exceptional,
sufficient,
needsChanges,
didNotReview,
total: exceptional + sufficient + needsChanges + didNotReview,
};
};

const ReviewersRequirementChart = ({ duration }) => {

Check warning on line 34 in src/components/Analytics/ReviewersRequirementChart.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'duration' is missing in props validation

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ7CH6a_EGbq8ukjt6bE&open=AZ7CH6a_EGbq8ukjt6bE&pullRequest=3861
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');

useEffect(() => {
const fetchAPIData = async () => {
setIsLoading(true);
setErrorMessage('');

try {
const token = window.localStorage.getItem('token');

Check warning on line 45 in src/components/Analytics/ReviewersRequirementChart.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ7CH6a_EGbq8ukjt6bF&open=AZ7CH6a_EGbq8ukjt6bF&pullRequest=3861
const response = await axios.get(ENDPOINTS.GITHUB_REVIEW_SUMMARY(duration), {
headers: {
Authorization: token,
'Content-Type': 'application/json',
},
});

setData(Array.isArray(response.data) ? response.data : []);
} catch (err) {
setData([]);
setErrorMessage('Unable to load GitHub review data for the selected duration.');
} finally {

Check warning on line 57 in src/components/Analytics/ReviewersRequirementChart.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ7CH6a_EGbq8ukjt6bG&open=AZ7CH6a_EGbq8ukjt6bG&pullRequest=3861
setIsLoading(false);
}
};

fetchAPIData();
}, [duration]);

const sortedData = data
.map(normalizeReviewCounts)
.filter(item => item.reviewer)
.sort((a, b) => b.total - a.total);

if (isLoading) {
return <p>Loading review data...</p>;
}

if (errorMessage) {
return <p>{errorMessage}</p>;
}

if (!sortedData.length) {
return <p>No review data found for this duration.</p>;
}

const rowHeight = 34;
const chartHeight = Math.max(sortedData.length * rowHeight, 360);

return (
<div style={{ width: '100%', height: chartHeight + 80 }}>
<ResponsiveContainer width="100%" height={chartHeight}>
<BarChart
layout="vertical"
data={sortedData}
margin={{ top: 20, right: 48, left: 20, bottom: 40 }}
barSize={22}
>
<XAxis type="number" allowDecimals={false} />
<YAxis
dataKey="reviewer"
type="category"
interval={0}
width={190}
tick={{ fontSize: 12 }}
/>
<Tooltip />
<Legend />

<Bar dataKey="exceptional" name="Exceptional" stackId="reviews" fill="#052C65" />
<Bar dataKey="sufficient" name="Sufficient" stackId="reviews" fill="#4682B4" />
<Bar dataKey="needsChanges" name="Needs Changes" stackId="reviews" fill="#FF8C00" />
<Bar dataKey="didNotReview" name="Did Not Review" stackId="reviews" fill="#A9A9A9">
<LabelList
dataKey="total"
position="right"
style={{ fill: 'black', fontSize: 12, fontWeight: 'bold' }}
/>
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
);
};

export default ReviewersRequirementChart;
12 changes: 12 additions & 0 deletions src/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ import RecipesLandingPage from './components/KitchenandInventory/Recipes';

import EPProtectedRoute from './components/common/EPDashboard/EPProtectedRoute';
import EPDashboard from './components/EductionPortal';
import PopularPRChart from './components/Analytics/PopularPRChart';
import InsightWidget from './components/EductionPortal/AnalyticsDashboard/InsightsWidget';
import ReportDownloadButton from './components/EductionPortal/AnalyticsDashboard/ReportDownloadButton';
import AnnouncementsPage from './components/EductionPortal/Announcements/AnnouncementsPage';
Expand All @@ -190,6 +191,7 @@ import OptStatusPieChart from './components/OptStatusPieChart/OptStatusPieChart'
import SupportDashboard from './components/SupportPortal/SupportDashboard';
import SupportLogin from './components/SupportPortal/SupportLogin';
import SupportLogViewer from './components/SupportPortal/SupportLogViewer';
import ReviewersRequirementChart from './components/Analytics/ReviewersRequirementChart';
import { UserRole } from './utils/enums';
import JobApplicationForm from './components/Collaboration/JobApplicationForm/JobApplicationForm';
import ReviewsInsight from './components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight';
Expand Down Expand Up @@ -785,6 +787,16 @@ export default (
fallback
allowedRoles={[UserRole.Owner]}
/>

<Route path="/analytics/popular-prs" exact component={PopularPRChart} fallback />

<ProtectedRoute
path="/analytics/review-summary"
exact
component={ReviewersRequirementChart}
fallback
/>
<Route path="/analytics/dashboard" exact component={AnalyticsDashboard} fallback />
<ProtectedRoute path="/job-application" exact component={JobApplicationForm} />
<ProtectedRoute path="/popularity" component={PopularityTimelineChart} fallback />
<ProtectedRoute
Expand Down
4 changes: 4 additions & 0 deletions src/utils/URL.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ export const ENDPOINTS = {
GET_TOTAL_COUNTRY_COUNT: () => `${APIEndpoint}/getTotalCountryCount`,

ANALYTICS_AVAILABLE_ROLES: () => `${APIEndpoint}/analytics/roles`,
GITHUB_REVIEW_SUMMARY: (duration, sort = 'desc') =>
`${APIEndpoint}/analytics/review-summary?duration=${encodeURIComponent(
duration,
)}&sort=${encodeURIComponent(sort)}`,

// Country Application Map Chart endpoints
COUNTRY_APPLICATION_DATA: (params = {}) => {
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2785,8 +2785,8 @@

"@parcel/watcher-darwin-arm64@2.5.1":
version "2.5.1"
resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz"
integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==
resolved "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz"
integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==

"@parcel/watcher@^2.4.1":
version "2.5.1"
Expand Down
Loading