Skip to content

Commit 77b123a

Browse files
authored
Merge pull request #188 from oss-slu/task/coreCI/CD-122
Created CI/CD Pipeline
2 parents 76c3671 + 04255b7 commit 77b123a

12 files changed

Lines changed: 149 additions & 124 deletions

.github/workflows/CICDPipeline.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: CI/CD Pipeline
2+
3+
on:
4+
push:
5+
branches: main
6+
pull_request:
7+
branches: main
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
permissions:
14+
contents: read
15+
pages: write
16+
id-token: write
17+
18+
jobs:
19+
backend-tests:
20+
name: Python Backend Tests
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v6
24+
- uses: actions/setup-python@v5
25+
with:
26+
python-version: '3.11'
27+
cache: 'pip'
28+
- run: |
29+
python -m pip install --upgrade pip
30+
pip install -r docs/requirements.txt
31+
pip install pytest responses
32+
- run: pytest tests/
33+
env:
34+
GIT_TOKEN: "dummy_token_for_testing"
35+
36+
frontend-checks:
37+
name: React Lint & Test
38+
runs-on: ubuntu-latest
39+
needs: backend-tests
40+
defaults:
41+
run:
42+
working-directory: ./Frontend
43+
steps:
44+
- uses: actions/checkout@v6
45+
- uses: actions/setup-node@v6
46+
with:
47+
node-version: '20'
48+
cache: 'npm'
49+
cache-dependency-path: ./Frontend/package-lock.json
50+
- run: npm ci
51+
- run: npm run lint
52+
- run: npm run test -- --run
53+
54+
build:
55+
name: Build React App
56+
runs-on: ubuntu-latest
57+
needs: frontend-checks
58+
defaults:
59+
run:
60+
working-directory: ./Frontend
61+
steps:
62+
- uses: actions/checkout@v6
63+
- uses: actions/setup-node@v6
64+
with:
65+
node-version: '20'
66+
cache: 'npm'
67+
cache-dependency-path: ./Frontend/package-lock.json
68+
- run: npm ci
69+
- run: npm run build -- --base=/oss_dev_analytics/
70+
71+
- name: Upload Pages artifact
72+
uses: actions/upload-pages-artifact@v3
73+
with:
74+
path: './Frontend/dist'
75+
76+
deploy:
77+
name: Deploy to GitHub Pages
78+
runs-on: ubuntu-latest
79+
needs: build
80+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
81+
environment:
82+
name: github-pages
83+
url: ${{ steps.deployment.outputs.page_url }}
84+
steps:
85+
- name: Deploy to GitHub Pages
86+
id: deployment
87+
uses: actions/deploy-pages@v4

Frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"scripts": {
77
"dev": "vite",
88
"test": "vitest --environment jsdom",
9-
"build": "vite build"
9+
"build": "vite build",
10+
"lint": "eslint ."
1011
},
1112
"vitest": {
1213
"globals": true,

Frontend/src/components/TopContributorsRepos.jsx

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,52 +10,14 @@
1010
*/
1111

1212
import { getTopContributorsAndRepos } from "../utils/getTopContributorsAndRepos";
13-
import testData from "../test_data.json";
13+
import lifetimeData from "../../../lifetimeData.json";
1414

1515

1616
const TOP_N = 5;
1717

18-
const TopContributorsRepos = ({ events }) => {
19-
18+
const TopContributorsRepos = () => {
2019
// Converting repo JSON structure into event-like objects
21-
const eventData = [];
22-
23-
Object.entries(testData)
24-
.filter(([repoName]) => !repoName.includes("sprint"))
25-
.forEach(([repoName, repo]) => {
26-
27-
const issues = repo?.issues || {};
28-
const prs = repo?.pull_requests || {};
29-
const commits = repo?.commits || {};
30-
31-
Object.entries(issues).forEach(([user, stats]) => {
32-
const count = Number(stats.total_issues_opened) || 0;
33-
for (let i = 0; i < count; i++) {
34-
eventData.push({ author: user, repo: repoName });
35-
}
36-
});
37-
38-
Object.entries(prs).forEach(([user, stats]) => {
39-
const count = Number(stats.total_prs_opened) || 0;
40-
for (let i = 0; i < count; i++) {
41-
eventData.push({ author: user, repo: repoName });
42-
}
43-
});
44-
45-
Object.entries(commits).forEach(([user, stats]) => {
46-
const count = Number(stats.total_commits) || 0;
47-
for (let i = 0; i < count; i++) {
48-
eventData.push({ author: user, repo: repoName });
49-
}
50-
});
51-
52-
});
53-
54-
console.log("eventData length:", eventData.length);
55-
console.log(eventData.slice(0,5));
56-
57-
const { topContributors, topRepos } =
58-
getTopContributorsAndRepos(eventData, TOP_N);
20+
const {topContributors, topRepos} = getTopContributorsAndRepos(lifetimeData, TOP_N);
5921

6022
return (
6123
<div style={{ display: "flex", gap: "30px" }}>

Frontend/src/components/charts/TimeBased.jsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,32 @@ import {
1616
* @param {Array} data - Array of objects [{ label: string, value: number }]
1717
* @param {string} repos - Repository name or "All" (default "All")
1818
* @param {string|null} user - Optional user filter
19+
* @param {string} title - Optional custom title for the chart
1920
*/
20-
export default function TimeBased({ data, repos = "All", user = null }) {
21+
export default function TimeBased({ data, repos = "All", user = null, titleCustom = null}) {
2122

2223
// Loading state (same style as VolumeCharts)
2324
if (!data) {
2425
return <div className="p-4 text-center">Loading Chart Data...</div>;
2526
}
26-
27-
// Title logic (mirrors VolumeCharts structure)
2827
let title;
29-
30-
if (repos === "All") {
31-
title = "Time-Based Data";
28+
// Title logic (mirrors VolumeCharts structure)
29+
if (titleCustom) {
30+
title = titleCustom;
31+
}
32+
else{
33+
if (repos === "All") {
34+
title = "Organization Level Time-Based Data";
3235
} else if (user) {
33-
title = `User Level Time-Based Data: ${repos} for ${user}`;
36+
title = `User Level Time-Based Data: ${repos} for ${user}`;
3437
} else {
35-
title = `Repository Level Time-Based Data: ${repos}`;
38+
title = `Repository Level Time-Based Data: ${repos}`;
39+
}
3640
}
3741

42+
3843
return (
39-
<div className="chart-container"/*className="chart-container bg-white p-4 rounded-lg shadow"*/>
44+
<div className="chart-container">
4045
<h3 className="text-center font-semibold mb-4">{title}</h3>
4146

4247
<ResponsiveContainer width="100%" height={300}>

Frontend/src/components/charts/VolumeBased.jsx

Lines changed: 19 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,27 @@ ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)
1414
* VolumeCharts Component
1515
* @param {Object} data - The processed data for the specific view (User, Repo, or Org)
1616
* @param {string} repos - The repository name or "All" for all repositories (Default "All")
17+
* @param {string|null} user - The username for user-level data (Default null)
18+
* @param {string} title - Optional custom title for the chart
1719
*/
18-
export default function VolumeCharts({ data, repos = "All", user = null }) {
20+
export default function VolumeCharts({ data, repos = "All", user = null, titleCustom = null}) {
1921
//If no data is available yet, return a loading state or null
2022
if (!data) return <div className="p-4 text-center">Loading Chart Data...</div>;
21-
22-
let title;
23+
let title;
24+
25+
if (titleCustom) {
26+
title = titleCustom;
27+
}
28+
else{
29+
if (repos === "All") {
30+
title = "Organization Level Volume Data";
31+
} else if (user) {
32+
title = `User Level Volume Data: ${repos} for ${user}`;
33+
} else {
34+
title = `Repository Level Volume Data: ${repos}`;
35+
}
36+
}
2337

24-
if (repos === "All"){
25-
/*var*/ title = "Volume-Based Data";
26-
}
27-
else if(user != null){
28-
/*var*/ title = `User Level Volume Data: ${repos} for ${user}`
29-
}
30-
else {
31-
/*var*/ title = `Repository Level Volume Data: ${repos}`;
32-
}
3338
const chartOptions = {
3439
responsive: true,
3540
maintainAspectRatio: false,
@@ -44,19 +49,6 @@ export default function VolumeCharts({ data, repos = "All", user = null }) {
4449
title: {
4550
display: false,
4651
},
47-
/*title: {
48-
display: true,
49-
text: title,
50-
color: "#ffffff",
51-
align: "start",
52-
font: {
53-
size: 18,
54-
weight: "600"
55-
},
56-
padding: {
57-
bottom: 16
58-
}
59-
},*/
6052
},
6153
scales: {
6254
x: {
@@ -92,28 +84,6 @@ export default function VolumeCharts({ data, repos = "All", user = null }) {
9284
</div>
9385
</div>
9486
);
95-
/* <div
96-
className="chart-container"
97-
style={{
98-
height: "100%",
99-
width: "100%",
100-
display: "flex",
101-
flexDirection: "column",
102-
paddingTop: "16ps",
103-
paddingLeft: "16px",
104-
paddingRight: "16px",
105-
}}
106-
>
107-
<div style={{ flex: 1, minHeight: 0, position: "relative" }}>
108-
<Bar options={chartOptions} data={chartData} />
109-
</div>
110-
</div>*/
87+
11188
}
112-
113-
/*return (
114-
<div className="chart-container p-4 rounded-lg shadow h-full w-full flex flex-col">
115-
<div style={{ flex: 1, minHeight: 0 }}>
116-
<Bar options={chartOptions} data={chartData} />
117-
</div>
118-
</div>
119-
);*/
89+

Frontend/src/components/charts/__tests__/TimeBased.test.jsx renamed to Frontend/src/components/charts/__tests__/test_TimeBased.test.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { render, screen } from "@testing-library/react";
2+
import { describe, test, expect } from "vitest"; // to allow lint to run
3+
import "@testing-library/jest-dom";
24
import TimeBased from "../TimeBased";
35

46
describe("TimeBased chart", () => {
57
test("renders chart title", () => {
68
render(
79
<TimeBased
8-
title="Org Average Time to Close"
10+
title="Organization Level Time-Based Data"
911
data={[{ label: "Organization", value: 200 }]}
1012
/>
1113
);
1214

1315
expect(
14-
screen.getByText("Org Average Time to Close")
16+
screen.getByText("Organization Level Time-Based Data")
1517
).toBeInTheDocument();
1618
});
1719
});

Frontend/src/components/charts/__tests__/VolumeChart.test.jsx renamed to Frontend/src/components/charts/__tests__/test_VolumeChart.test.jsx

File renamed without changes.

Frontend/src/components/charts/__tests__/getTopContributorsAndRepos.test.js renamed to Frontend/src/components/charts/__tests__/test_getTopContributorsAndRepos.test.js

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,38 @@
77
and easy to maintain.
88
*/
99
import { getTopContributorsAndRepos } from "../../../utils/getTopContributorsAndRepos";
10+
import { describe, it, expect } from "vitest";
1011

1112
describe("getTopContributorsAndRepos", () => {
13+
// mock JSON data so that Lint passes
14+
const mockJSON = {
15+
"repo1": {
16+
issues: { "alice": { total_issues_opened: 2 }, "bob": { total_issues_opened: 1 } },
17+
},
18+
"repo2": {
19+
pull_requests: { "charlie": { total_prs_opened: 4 } }
20+
}
21+
};
22+
23+
1224
it("counts contributor activity and sorts correctly", () => {
13-
const events = [
14-
{ author: "alice", repo: "repo1" },
15-
{ author: "bob", repo: "repo1" },
16-
{ author: "alice", repo: "repo2" },
17-
];
18-
19-
const { topContributors } =
20-
getTopContributorsAndRepos(events, 5);
21-
22-
// alice should rank first since she appears twice
25+
const {topContributors} = getTopContributorsAndRepos(mockJSON, 5);
26+
// charlie: 4, alicce: 2, bob: 1
2327
expect(topContributors).toEqual([
28+
{ name: "charlie", count: 4 },
2429
{ name: "alice", count: 2 },
2530
{ name: "bob", count: 1 },
2631
]);
2732
});
2833

2934
it("counts repository activity and sorts correctly", () => {
30-
const events = [
31-
{ author: "alice", repo: "repo1" },
32-
{ author: "bob", repo: "repo1" },
33-
{ author: "charlie", repo: "repo2" },
34-
];
35-
3635
const { topRepos } =
37-
getTopContributorsAndRepos(events, 5);
36+
getTopContributorsAndRepos(mockJSON, 5);
3837

39-
// repo1 should rank higher since it appears more frequently
38+
// repo1: 3 (2 from alice + 1 from bob), repo2: 4 (from charlie)
4039
expect(topRepos).toEqual([
41-
{ name: "repo1", count: 2 },
42-
{ name: "repo2", count: 1 },
40+
{ name: "repo2", count: 4 },
41+
{ name: "repo1", count: 3},
4342
]);
4443
});
4544

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Health Score = (80×0.25 + 60×0.20) / (0.25 + 0.20)
4444
Currently, metric selection is simulated using a temporary in-memory configuration (fake database).
4545
This will later be replaced with database/Okta-based user configurations.
4646

47-
## 🤝 Getting Started
47+
## Getting Started
4848
Are you interested in contributing to our organization-wide analytics?
4949
1. Check out our [Onboarding Document](./Onboarding.md).
5050
2. Join the conversation in the **#oss-dev-analytics** Slack channel.

docs/requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ PyGithub
44
# Data Processing and Analysis
55
pandas
66

7-
# Environment Variable Management (Recommended for GIT_TOKEN)
87
python-dotenv

0 commit comments

Comments
 (0)