Skip to content

Commit

Permalink
[#1899] Add graphic for commit diffstat (#2010)
Browse files Browse the repository at this point in the history
A diffstat graphic is useful in providing a visual representation
of the changes within and between commits. 

Let's add a diffstat graphic emulating the diffstat used by cgit, 
to help users have a high-level overview of the impact of a
commit.
  • Loading branch information
nseah21 committed Jul 28, 2023
1 parent ae805c8 commit a9f102d
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
describe('contribution bar', () => {
it('same length when breakdown selected', () => {
let expectedWidthSum = 0;
cy.get('.summary-chart__contrib--bar').then((ele) => {
cy.get('.stacked-bar__contrib--bar').then((ele) => {
let i;
for (i = 0; i < ele.length; i += 1) {
expectedWidthSum += parseFloat(ele[i].style.width.split('%')[0]);
Expand All @@ -12,7 +12,7 @@ describe('contribution bar', () => {
.check();

let actualWidthSum = 0;
cy.get('.summary-chart__contrib--bar').then((ele) => {
cy.get('.stacked-bar__contrib--bar').then((ele) => {
let i;
for (i = 0; i < ele.length; i += 1) {
actualWidthSum += parseFloat(ele[i].style.width.split('%')[0]);
Expand All @@ -35,7 +35,7 @@ describe('contribution bar', () => {
let expectedWidthSum = 0;
cy.get('#summary-wrapper label').contains('breakdown by file type').siblings().filter('input')
.check();
cy.get('.summary-chart__contrib--bar').then((ele) => {
cy.get('.stacked-bar__contrib--bar').then((ele) => {
expectedWidthSum += parseFloat(ele[0].style.width.split('%')[0]);
});

Expand All @@ -46,7 +46,7 @@ describe('contribution bar', () => {
.check();

let actualWidthSum = 0;
cy.get('.summary-chart__contrib--bar').then((ele) => {
cy.get('.stacked-bar__contrib--bar').then((ele) => {
let i;
for (i = 0; i < ele.length; i += 1) {
actualWidthSum += parseFloat(ele[i].style.width.split('%')[0]);
Expand Down
6 changes: 3 additions & 3 deletions frontend/cypress/tests/chartView/chartView_mergeGroup.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('merge group', () => {
.should('be.checked');

// get the three chart bars and assert they have the correct initial widths
cy.get('.summary-chart__contrib--bar')
cy.get('.stacked-bar__contrib--bar')
.should('have.length', 3)
.then(($bars) => {
// calculate the percentage of the width relative to the parent container
Expand All @@ -88,7 +88,7 @@ describe('merge group', () => {
const initialWidths = [];

// Store the initial widths of the contribution bars
cy.get('.summary-chart__contrib--bar')
cy.get('.stacked-bar__contrib--bar')
.each(($bar) => {
const width = window.getComputedStyle($bar[0]).width;
initialWidths.push(width);
Expand All @@ -99,7 +99,7 @@ describe('merge group', () => {
cy.get('.overlay-loader').should('not.be.visible');

// Get the contribution bars again and compare their widths with the initial widths
cy.get('.summary-chart__contrib--bar')
cy.get('.stacked-bar__contrib--bar')
.should('have.length', initialWidths.length)
.each(($bar, index) => {
const width = window.getComputedStyle($bar[0]).width;
Expand Down
93 changes: 93 additions & 0 deletions frontend/cypress/tests/zoomView/zoomView_diffstat.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
describe('diffstat', () => {
it('should render container for contribution bars', () => {
cy.get('.icon-button.fa-list-ul')
.should('be.visible')
.first()
.click();

cy.get('#tab-zoom .commit-message .stacked-bar-container')
.should('be.visible');
});

// Assumptions: The commit selected here is @eugenepeh's
// `README: Fix grammatical error` with 1 insertion and 1 deletion.
it('should render non-empty contribution bars for commits with changes', () => {
cy.get('.icon-button.fa-list-ul')
.should('be.visible')
.first()
.click();

cy.get('#tab-zoom .commit-message')
.first()
.within(() => {
cy.get('.stacked-bar__contrib--bar')
.then((element) => {
expect(element.length).to.be.equal(2);
expect(element[0].style['background-color']).to.be.equal('limegreen');
expect(element[0].style.width).to.be.equal('0.1%');
expect(element[1].style['background-color']).to.be.equal('red');
expect(element[1].style.width).to.be.equal('0.1%');
});
});
});

// Assumptions: The commit selected here is @eugenepeh's
// `Merge branch 'new-branch` into cypress` with 0 insertions and 0 deletions.
it('should render empty contribution bars for commits with no changes', () => {
cy.get('.icon-button.fa-list-ul')
.should('be.visible')
.first()
.click();

cy.get('#tab-zoom .commit-message')
.eq(1)
.within(() => {
cy.get('.stacked-bar__contrib--bar')
.then((element) => {
expect(element.length).to.be.equal(2);
expect(element[0].style['background-color']).to.be.equal('limegreen');
expect(element[0].style.width).to.be.equal('0%');
expect(element[1].style['background-color']).to.be.equal('red');
expect(element[1].style.width).to.be.equal('0%');
});
});
});

it('should render contribution bars in proportion', () => {
cy.get('.icon-button.fa-list-ul')
.should('be.visible')
.first()
.click();

let insertionWidthSum = 0;
let deletionWidthSum = 0;
let widthProportion = 0;
cy.get('#tab-zoom .commit-message .stacked-bar__contrib--bar')
.then((element) => {
for (let i = 0; i < element.length; i += 1) {
const val = parseFloat(element[i].style.width.split('%')[0]);
if (element[i].style['background-color'] === 'limegreen') {
insertionWidthSum += val;
} else {
deletionWidthSum += val;
}
}
widthProportion = insertionWidthSum / deletionWidthSum;
});

let insertions = 0;
let deletions = 0;
let actualProportion = 0;
cy.get('[data-cy="changes"]')
.invoke('text')
.then((text) => {
const temp = text.split('lines');
for (let i = 0; i < temp.length - 1; i += 1) {
insertions += parseFloat(temp[i].split('-')[0].split('+')[1].trim());
deletions += parseFloat(temp[i].split('-')[1].trim());
}
actualProportion = insertions / deletions;
expect(widthProportion.toFixed(3)).to.be.equal(actualProportion.toFixed(3));
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('hide all commit messages ', () => {
.should('have.length', 1);

cy.get('#tab-zoom .toolbar--multiline > a')
.should('have.text', 'hide all commit messages');
.should('have.text', 'hide all commit details');
});

it('should only display show all commit messages when all are hidden', () => {
Expand All @@ -93,7 +93,7 @@ describe('hide all commit messages ', () => {
.should('have.length', 1);

cy.get('#tab-zoom .toolbar--multiline > a')
.should('have.text', 'show all commit messages');
.should('have.text', 'show all commit details');
});

it('should display both show and hide all commit messages when some are hidden', () => {
Expand All @@ -119,11 +119,11 @@ describe('hide all commit messages ', () => {

cy.get('#tab-zoom .toolbar--multiline > a')
.eq(0)
.should('have.text', 'show all commit messages');
.should('have.text', 'show all commit details');

cy.get('#tab-zoom .toolbar--multiline > a')
.eq(1)
.should('have.text', 'hide all commit messages');
.should('have.text', 'hide all commit details');
});

it('check show all and hide all commit messages only toggle current commits', () => {
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('hide all commit messages ', () => {
.should('have.length', 1);

cy.get('#tab-zoom .toolbar--multiline > a')
.should('have.text', 'show all commit messages');
.should('have.text', 'show all commit details');

// check java file type
cy.get('#tab-zoom .fileTypes input[value="java"]')
Expand Down Expand Up @@ -182,11 +182,11 @@ describe('hide all commit messages ', () => {

cy.get('#tab-zoom .toolbar--multiline > a')
.eq(0)
.should('have.text', 'show all commit messages');
.should('have.text', 'show all commit details');

cy.get('#tab-zoom .toolbar--multiline > a')
.eq(1)
.should('have.text', 'hide all commit messages');
.should('have.text', 'hide all commit details');
});

it('check hidden commit message persists after sort', () => {
Expand Down
39 changes: 39 additions & 0 deletions frontend/src/components/c-stacked-bar-chart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template lang="pug">
.stacked-bar-container
.stacked-bar__contrib--bar(
v-for="bar in bars",
v-bind:title="bar.tooltipText",
v-bind:style="{ width: `${bar.width}%`,\
'background-color': bar.color }"
)
</template>

<script lang="ts">
import { PropType, defineComponent } from 'vue';
import { Bar } from '../types/types';
export default defineComponent({
props: {
bars: {
type: Array as PropType<Bar[]>,
required: true,
},
},
});
</script>

<style scoped lang="scss">
@import '../styles/_colors.scss';
.stacked-bar-container {
display: flex;
flex-wrap: wrap;
}
.stacked-bar__contrib--bar {
background-color: mui-color('blue');
height: 4px;
margin-top: 2px;
}
</style>
63 changes: 42 additions & 21 deletions frontend/src/components/c-summary-charts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -220,25 +220,17 @@

.summary-chart__contribution
template(v-if="filterBreakdown")
.summary-chart__contrib(
v-for="(widths, fileType) in getFileTypeContributionBars(user.fileTypeContribution)"
)
.summary-chart__contrib--bar(
v-for="width in widths",
v-bind:style="{ width: `${width}%`,\
'background-color': fileTypeColors[fileType] }",
v-bind:title="`${fileType}: ${user.fileTypeContribution[fileType]} lines, \
total: ${user.checkedFileTypeContribution} lines (contribution from ${minDate} to \
${maxDate})`"
.summary-chart__contrib
c-stacked-bar-chart(
v-bind:bars="getFileTypeContributionBars(user.fileTypeContribution, user.checkedFileTypeContribution)"
)
template(v-else)
.summary-chart__contrib(
v-bind:title="`Total contribution from ${minDate} to ${maxDate}: \
${user.checkedFileTypeContribution} lines`"
)
.summary-chart__contrib--bar(
v-for="width in getContributionBars(user.checkedFileTypeContribution)",
v-bind:style="{ width: `${width}%` }"
c-stacked-bar-chart(
v-bind:bars="getContributionBars(user.checkedFileTypeContribution)"
)
</template>

Expand All @@ -248,7 +240,8 @@ import { mapState } from 'vuex';
import brokenLinkDisabler from '../mixin/brokenLinkMixin';
import cRamp from './c-ramp.vue';
import { Repo, User } from '../types/types';
import cStackedBarChart from './c-stacked-bar-chart.vue';
import { Bar, Repo, User } from '../types/types';
import { FilterGroupSelection, FilterTimeFrame, SortGroupSelection } from '../types/summary';
import { StoreState, ZoomInfo } from '../types/vuex.d';
import { AuthorFileTypeContributions } from '../types/zod/commits-type';
Expand All @@ -257,6 +250,7 @@ export default defineComponent({
name: 'c-summary-charts',
components: {
cRamp,
cStackedBarChart,
},
mixins: [brokenLinkDisabler],
props: {
Expand Down Expand Up @@ -376,12 +370,15 @@ export default defineComponent({
this.retrieveSelectedTabHash();
},
methods: {
getFileTypeContributionBars(fileTypeContribution: AuthorFileTypeContributions): { [key: string]: number[] } {
getFileTypeContributionBars(
fileTypeContribution: AuthorFileTypeContributions,
checkedFileTypeContribution: number | undefined,
): Bar[] {
let currentBarWidth = 0;
const fullBarWidth = 100;
const contributionPerFullBar = (this.avgContributionSize * 2);
const allFileTypesContributionBars: { [key: string]: number[] } = {};
const allFileTypesContributionBars: Bar[] = [];
if (contributionPerFullBar === 0) {
return allFileTypesContributionBars;
}
Expand Down Expand Up @@ -413,7 +410,15 @@ export default defineComponent({
currentBarWidth = remainingBarWidth;
}
allFileTypesContributionBars[fileType] = contributionBars;
contributionBars.forEach((width) => {
allFileTypesContributionBars.push({
width,
color: this.fileTypeColors[fileType],
tooltipText: `${fileType}: ${fileTypeContribution[fileType]} lines, \
total: ${checkedFileTypeContribution} lines (contribution from ${this.minDate} to
${this.maxDate})`,
});
});
});
return allFileTypesContributionBars;
Expand All @@ -435,20 +440,20 @@ export default defineComponent({
return group.reduce((acc, ele) => acc + (ele.checkedFileTypeContribution ?? 0), 0);
},
getContributionBars(totalContribution: number): number[] {
const res: number[] = [];
getContributionBars(totalContribution: number): Bar[] {
const res: Bar[] = [];
const contributionLimit = (this.avgContributionSize * 2);
if (contributionLimit === 0) {
return res;
}
const cnt = Math.floor(totalContribution / contributionLimit);
for (let cntId = 0; cntId < cnt; cntId += 1) {
res.push(100);
res.push({ width: 100 });
}
const last = (totalContribution % contributionLimit) / contributionLimit;
if (last !== 0) {
res.push(last * 100);
res.push({ width: last * 100 });
}
return res;
Expand Down Expand Up @@ -559,6 +564,7 @@ export default defineComponent({
zFileTypeColors: this.fileTypeColors,
zFromRamp: false,
zFilterSearch: filterSearch,
zAvgContributionSize: this.getAvgContributionSize(),
};
this.addSelectedTab(user.name, user.repoName, 'zoom', isMerged);
this.$store.commit('updateTabZoomInfo', info);
Expand Down Expand Up @@ -794,6 +800,21 @@ export default defineComponent({
return explanation;
},
getAvgContributionSize(): number {
let totalContribution = 0;
let totalCommits = 0;
this.filteredRepos.forEach((repo) => {
repo.forEach((user) => {
user.commits?.forEach((commit) => {
totalCommits += 1;
totalContribution += (commit.insertions + commit.deletions);
});
});
});
return totalContribution / totalCommits;
},
},
});
</script>
Loading

0 comments on commit a9f102d

Please sign in to comment.