What am I looking at?
It's a package that graphs your conventional commits into a Emoji Chart based on the number of types of commits. I built this package while working on a side project to see where I was spending my time. What's get measured get's managed don't you know. You can install the package here https://www.npmjs.com/package/react-conventional-commits-graph however it may have bugs so please open an issue since it's my first package. Now let's understand some prerequisites...
Oh and if you want to see the graph live on a page here it is
https://app.stripeappkit.com/api/commits
Understanding Conventional Commits
Conventional commits are a specification for adding human and machine-readable meaning to commit messages. They follow a structured format to make the history of a repository more accessible and easier to navigate. Here's a brief overview of their key elements and importance:
Structured Format: Conventional commits require a specific format for commit messages, typically starting with a type (e.g.,
feat
,fix
), optionally followed by a scope, and a description. For example:feat(database): add new indexing feature
.Types of Commits: Common types include
feat
(new feature),fix
(bug fix),docs
(documentation changes),style
(style changes that do not affect meaning),refactor
(code changes that neither fix a bug nor add a feature),test
(adding missing tests), andchore
(maintenance tasks).Scope: This is an optional part that describes the part of the codebase affected by the changes (e.g.,
feat(auth)
).Description: A concise description of the changes made.
Importance:
- Improved Readability: Makes it easier to understand the purpose of each commit at a glance.
- Automated Tooling: Enables automated tools to process commit messages and generate changelogs, version bumps, and other outputs based on the type of commits.
- Team Collaboration: Helps team members quickly understand the nature of changes, improving collaboration and review processes.
- Project Management: Assists in tracking the progress of features, fixes, and other types of changes in a project.
In summary, conventional commits provide a standardized way to write commit messages, enhancing readability, supporting tool automation, and improving collaborative development workflows.
Building the NPM Package
We are going to be using mircobundle to package up the npm package for distribution as a react component.
make sure to globally install microbundle
npm i -g microbundle
Steps
- create a folder with project name mine was called commit-graph
- Copy this into a new package.json file in the folder
{
"name": "package-name",
"description": "package description",
"version": "1.2.8",
"type": "module",
"source": "src/index.js",
"main": "dist/index.esm.cjs",
"scripts": {
"build": "microbundle --jsx 'React.createElement' --jsxImportSource react --globals react/jsx-runtime=jsx --format es,cjs",
"dev": "microbundle watch --jsxFragment React.Fragment"
},
"dependencies": {
"chart.js": "^4.4.0",
"react-chartjs-2": "^5.2.0",
"simple-git": "^3.21.0"
},
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@babel/cli": "^7.23.4",
"@babel/core": "^7.23.5",
"@babel/preset-env": "^7.23.5",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"microbundle": "^0.15.1",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"bin": {
"extract-commits": "server/processData.js"
}
}
- Add a server folder at the root and a file called
processData.js
with the following code
#!/usr/bin/env node
import simpleGit from 'simple-git';
import fs from "fs"
const localPath = "./";
// Check if the local repository exists
if (fs.existsSync(localPath)) {
// If it exists, use the existing repository
processData(localPath);
} else {
console.error('Local repository does not exist.');
}
function processData(path) {
simpleGit(path).log()
.then(log => {
const messages = log.all.map(commit => commit.message);
// Write commit messages to a JSON file
fs.writeFileSync('./public/commitMessages.json', JSON.stringify(messages, null, 2));
console.log('Commit messages have been written to commitMessages.json');
})
.catch(err => console.error('Error in analyzing repository:', err));
}
- lastly create a
src
folder with a components folder andindex.js
file in the root ofsrc
and aCommitChart.js
file in the root ofcomponents
index.js
export { default as CommitChart } from './components/CommitChart';
CommitChart.js
import { useEffect, useRef, useState } from "react";
import { Bar } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from "chart.js";
const emojiMap = {
bugs: "๐",
features: "โจ",
chores: "๐งน", // Example emoji for chores
fixes: "๐ง", // Example emoji for fixes
// others: "๐ฆ", // Example emoji for others
docs: "๐",
refactor: "โป๏ธ",
};
const emojiPlugin = {
id: "emojiPlugin",
afterDraw: (chart) => {
const ctx = chart.ctx;
chart.data.datasets.forEach((dataset, datasetIndex) => {
const meta = chart.getDatasetMeta(datasetIndex);
meta.data.forEach((bar, index) => {
const emoji = emojiMap[chart.data.labels[index]];
const { x, y, base, width, height } = bar;
const dataValue = dataset.data[index]; // The number of emojis we want to show
// Calculate the number of rows and columns of emojis based on the data value
const numRows = Math.ceil(Math.sqrt(dataValue));
const numCols = Math.ceil(dataValue / numRows);
// Calculate the size of each emoji to fit the bar's dimensions
const emojiWidth = width / numCols;
const emojiHeight = height / numRows;
const emojiSize = Math.min(emojiWidth, emojiHeight); // Use the smallest to fit both dimensions
ctx.font = `${emojiSize}px Arial`;
// Draw the emojis in a grid pattern
// for (let row = 0; row < numRows; row++) {
// for (let col = 0; col < numCols; col++) {
// if (row * numCols + col < dataValue) { // Ensure we don't draw more emojis than the data value
// const emojiX = x - width / 2 + col * emojiWidth;
// const emojiY = base - (numRows - row) * emojiHeight; // Start from the bottom of the bar
// ctx.fillText(emoji, emojiX, emojiY);
// }
// }
// }
// ... rest of your plugin code
// Draw the emojis in a grid pattern
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) {
if (row * numCols + col < dataValue) {
// Ensure we don't draw more emojis than the data value
const emojiX = x - width / 2 + col * emojiWidth;
const emojiY = base - emojiHeight - row * emojiHeight; // Adjusted to start from the bottom
// Save the context and rotate the canvas around the emoji's center
ctx.save();
ctx.translate(emojiX + emojiSize / 2, emojiY + emojiSize / 2);
ctx.rotate(Math.PI); // Rotate 180 degrees
ctx.fillText(emoji, -emojiSize / 2, -emojiSize / 2);
ctx.restore(); // Restore the context to the original state for the next emoji
}
}
}
// ... rest of your plugin code
});
});
},
};
// Register the necessary components for Chart.js
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
);
ChartJS.register(emojiPlugin); // Register the custom plugin
const CommitChart = () => {
const [commitData, setCommitData] = useState({
labels: [],
datasets: [],
});
const chartRef = useRef(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("/commitMessages.json");
if (!response.ok) {
throw new Error("Network response was not ok");
}
const commitMessages = await response.json();
// Process your commit messages to get the data for the chart
// Assuming you process the data to the format Chart.js expects
// Example: { labels: ['Label1', 'Label2'], datasets: [{ data: [1, 2] }] }
const processedData = processData(commitMessages);
// If the chart already exists, update it
if (chartRef.current) {
chartRef.current.data = processedData;
chartRef.current.update();
} else {
setCommitData(processedData); // Set data for initial chart rendering
}
} catch (error) {
console.error("Failed to fetch commit messages:", error);
}
};
fetchData();
}, []); // Empty dependency array means this effect runs once after the first render
const options = {
scales: {
x: {
beginAtZero: true,
},
y: {
// Additional options for the Y-axis can be set here
},
},
maintainAspectRatio: true,
responsive: true,
plugins: {
// ... other plugin configurations
emojiPlugin: {}, // This is just to enable the plugin
},
};
return (
<div>
<h2>Commit Chart</h2>
<Bar ref={chartRef} data={commitData} options={options} />
</div>
);
};
export default CommitChart;
// Helper function to process the raw data
function processData(commitMessages) {
const counts = {
bugs: 0,
features: 0,
chores: 0,
fixes: 0,
// others: 0,
docs: 0,
refactor:0,
};
commitMessages.forEach((msg) => {
if (msg.includes("bug:") || msg.includes("๐")) {
counts.bugs++;
} else if (msg.includes("feat:") || msg.includes("โจ")) {
counts.features++;
} else if (msg.includes("chore:")) {
counts.chores++;
} else if (msg.includes("fix:")) {
counts.fixes++;
} else if (msg.includes("docs:")) {
counts.docs++;
} else if (msg.includes("refactor:")) {
counts.refactor++;
}
// else {
// counts.others++; // Count all other commits as 'others'
// }
});
return {
labels: Object.keys(counts),
datasets: [
{
label: "Number of Commits",
data: Object.values(counts),
backgroundColor: [
"rgba(255, 99, 132, 0.2)", // color for bugs
"rgba(54, 162, 235, 0.2)", // color for features
"rgba(255, 206, 86, 0.2)", // color for chores
"rgba(75, 192, 192, 0.2)", // color for fixes
"rgba(153, 102, 255, 0.2)", // color for others
],
borderColor: [
"rgba(255, 99, 132, 1)",
"rgba(54, 162, 235, 1)",
"rgba(255, 206, 86, 1)",
"rgba(75, 192, 192, 1)",
"rgba(153, 102, 255, 1)",
],
borderWidth: 1,
},
],
};
}
- Finally install yalc globaly
npm i -g yalc
With yalc you can run yalc publish
and it will create a local link that you can add to your project like running npm i pakcage-name
open up your React App, and for now I think it works best in a next JS app running React 17 will be fixing this for all versions. Naviagte there and run yalc add
your-package-name` and now you can import the component and add the script for extract-commits to your package.json
- The flow works like this
add script
`
"scripts": {
"extract-commits": "extract-commits"
}
`
run npm run extract-commits
should see a message that it a file was added to public folder
Got to a page an import the component
import {CommitGraph} from "your-package-name"
use it
`
return (
)
`
That's it ๐
Conclusion
In conclusion, the use of this NPM package could help you know where your time is spent.
the approach of integrating an emoji-based commit chart offers an engaging and visually intuitive way to track changes and progress. This feature not only adds an element of fun but also enhances the clarity and comprehensibility of project tracking.
I would say if you have something you built internally publish it to NPM, a great way to advertise your other work.
Kindly,
Anders from Wispy Company
Top comments (0)