From 2457ebbeaa69befc824fa609a4e12f4c55bb4c88 Mon Sep 17 00:00:00 2001
From: jonasongg <120372506+jonasongg@users.noreply.github.com>
Date: Tue, 7 May 2024 03:23:44 +0800
Subject: [PATCH] Add optimise timeline feature (#2180)
Add optimise timeline feature
Currently, the ramps have a lot of empty space if the since and until
dates are specified far apart.
Let's add a checkbox that can eliminate this empty space for each ramp.
This is especially useful if RepoSense is to be used to set up a report
of an individual's past OSS contributions.
---
docs/ug/usingReports.md | 1 +
.../chartView_optimiseTimeline.cy.js | 151 ++++++++++++++++++
.../codeView/codeView_renderFilterHash.cy.js | 14 ++
frontend/src/components/c-ramp.vue | 83 +++++++---
frontend/src/components/c-summary-charts.vue | 54 ++++++-
frontend/src/views/c-summary.vue | 20 ++-
6 files changed, 293 insertions(+), 30 deletions(-)
create mode 100644 frontend/cypress/tests/chartView/chartView_optimiseTimeline.cy.js
diff --git a/docs/ug/usingReports.md b/docs/ug/usingReports.md
index 6aa5ebae6c..d0ebbd13c6 100644
--- a/docs/ug/usingReports.md
+++ b/docs/ug/usingReports.md
@@ -119,6 +119,7 @@ The `Tool Bar` at the top of the Chart panel provides a set of configuration opt
* `Merge group`: merges all the ramp charts of each group into a single ramp chart; aggregates the contribution of each group.
* viewing of authored code of the group as a whole is available when `group by repos`.
* `Show tags`: shows the tags of all the repos under a group
+* `Trim timeline`: trims the starting and ending portion of each ramp where no commits were made; only the part of each ramp where commits were made will be shown.
Notes:
[1] **`Sort groups by`**: each main group has its own index and percentile according to its ranking position after sorting (e.g., if the groups are sorted by contribution in descending order, a 25% percentile indicates that the group is in the top 25% of the whole cohort in terms of contribution)
.
diff --git a/frontend/cypress/tests/chartView/chartView_optimiseTimeline.cy.js b/frontend/cypress/tests/chartView/chartView_optimiseTimeline.cy.js
new file mode 100644
index 0000000000..43e64806fe
--- /dev/null
+++ b/frontend/cypress/tests/chartView/chartView_optimiseTimeline.cy.js
@@ -0,0 +1,151 @@
+describe('optimise timeline', () => {
+ it('ramp padding should only exist when optimise timeline is checked', () => {
+ cy.get('#summary label.optimise-timeline > input:visible')
+ .should('be.visible')
+ .uncheck()
+ .should('be.not.checked');
+
+ cy.get('#summary-charts .summary-chart')
+ .first()
+ .find('.summary-chart__ramp .ramp .ramp-padding')
+ .should('have.css', 'left', '0px');
+
+ cy.get('#summary label.optimise-timeline > input:visible')
+ .should('be.visible')
+ .check()
+ .should('be.checked');
+
+ cy.get('#summary-charts .summary-chart')
+ .first()
+ .find('.summary-chart__ramp .ramp .ramp-padding')
+ .should('have.not.css', 'left', '0px');
+ });
+
+ it('should retain the same number of ramp slices', () => {
+ cy.get('#summary-charts .summary-chart')
+ .first()
+ .find('.summary-chart__ramp .ramp .ramp-padding a')
+ .then(($el) => {
+ const rampSlices = $el.length;
+
+ cy.get('#summary label.optimise-timeline > input:visible')
+ .should('be.visible')
+ .check()
+ .should('be.checked');
+
+ cy.get('#summary-charts .summary-chart')
+ .first()
+ .find('.summary-chart__ramp .ramp .ramp-padding a')
+ .should('have.length', rampSlices);
+ });
+ });
+
+ it('start and end date indicators should exist', () => {
+ cy.get('#summary label.optimise-timeline > input:visible')
+ .should('be.visible')
+ .check()
+ .should('be.checked');
+
+ cy.get('#summary-charts .summary-chart')
+ .first()
+ .find('.summary-chart__ramp .date-indicators span')
+ .first()
+ .should('have.text', '2018-05-03');
+
+ cy.get('#summary-charts .summary-chart')
+ .first()
+ .find('.summary-chart__ramp .date-indicators span')
+ .last()
+
+ // 3/3 on GitHub CI, 3/4 on local
+ .should('have.text', '2023-03-03');
+ });
+
+ it('no commits in range should not have date indicators', () => {
+ cy.get('#summary label.optimise-timeline > input:visible')
+ .should('be.visible')
+ .check()
+ .should('be.checked');
+
+ // change since date
+ cy.get('input[name="since"]')
+ .type('2018-12-31');
+
+ // change until date
+ cy.get('input[name="until"]')
+ .type('2019-01-01');
+
+ cy.get('#summary-charts .summary-chart')
+ .first()
+ .find('.summary-chart__ramp .date-indicators')
+ .should('not.exist');
+ });
+
+ it('zoom panel range should work correctly when timeline is optimised', () => {
+ cy.get('.icon-button.fa-list-ul')
+ .should('exist')
+ .first()
+ .click();
+
+ cy.get('#tab-zoom')
+ .should('be.visible');
+
+ // verifies the ramp chart is not optimised and has empty space on the right
+ cy.get('#tab-zoom .ramp a')
+ .first()
+ .invoke('css', 'right')
+ .then((val) => parseFloat(val))
+ .should('gt', 0);
+
+ cy.get('#summary label.optimise-timeline > input')
+ .check()
+ .should('be.checked');
+
+ cy.get('.icon-button.fa-list-ul')
+ .should('exist')
+ .first()
+ .click();
+
+ cy.get('#tab-zoom')
+ .should('be.visible');
+
+ // verifies the date range is correctly optimised
+ cy.get('#tab-zoom .period')
+ .should('contain', '2018-05-03 to 2023-03-03');
+
+ // verifies the ramp chart is optimised and has no empty space on the right
+ cy.get('#tab-zoom .ramp a')
+ .first()
+ .invoke('css', 'right')
+ .then((val) => parseFloat(val))
+ .should('lt', 1);
+ });
+
+ it('subzoom panel range should work correctly when timeline is optimised', () => {
+ const zoomKey = Cypress.platform === 'darwin' ? '{meta}' : '{ctrl}';
+
+ cy.get('#summary label.optimise-timeline > input:visible')
+ .should('be.visible')
+ .check()
+ .should('be.checked');
+
+ // clicking from the 10th px to the 50th px in the ramp
+ cy.get('body').type(zoomKey, { release: false })
+ .get('#summary-charts .summary-chart__ramp .ramp')
+ .first()
+ .click(110, 20)
+ .click(120, 20);
+
+ cy.get('#tab-zoom')
+ .should('be.visible');
+
+ cy.get('#tab-zoom .ramp .ramp__slice')
+ .should('have.length', 1);
+
+ cy.get('#tab-zoom .ramp .ramp__slice')
+ .invoke('attr', 'title')
+ .then((title) => {
+ cy.wrap(title).should('eq', '[2019-08-18] AboutUs: update team members (#867): +94 -12 lines ');
+ });
+ });
+});
diff --git a/frontend/cypress/tests/codeView/codeView_renderFilterHash.cy.js b/frontend/cypress/tests/codeView/codeView_renderFilterHash.cy.js
index 1cc456e0e4..fbe6d467aa 100644
--- a/frontend/cypress/tests/codeView/codeView_renderFilterHash.cy.js
+++ b/frontend/cypress/tests/codeView/codeView_renderFilterHash.cy.js
@@ -316,6 +316,20 @@ describe('render filter hash', () => {
.should('contain', 'viewRepoTags=true');
});
+ it('optimise timeline: url params should persist after change and reload', () => {
+ cy.get('#summary label.optimise-timeline input:visible')
+ .should('be.visible')
+ .check();
+
+ cy.url()
+ .should('contain', 'optimiseTimeline=true');
+
+ cy.reload();
+
+ cy.url()
+ .should('contain', 'optimiseTimeline=true');
+ });
+
it('checked file types: url params should persist after change and reload', () => {
cy.get('#summary label.filter-breakdown input:visible')
.should('not.be.checked');
diff --git a/frontend/src/components/c-ramp.vue b/frontend/src/components/c-ramp.vue
index 65e1fe780c..40f02b05cb 100644
--- a/frontend/src/components/c-ramp.vue
+++ b/frontend/src/components/c-ramp.vue
@@ -1,22 +1,25 @@
.ramp
template(v-if="tframe === 'commit'")
- template(v-for="(slice, j) in user.commits")
- template(v-for="(commit, k) in slice.commitResults")
- a.ramp__slice(
- draggable="false",
- v-on:click="rampClick",
- v-bind:href="getLink(commit)", target="_blank",
- v-bind:title="getContributionMessageByCommit(slice, commit)",
- v-bind:class="`ramp__slice--color${getRampColor(commit, slice)}`,\
- !isBrokenLink(getLink(commit)) ? '' : 'broken-link'",
- v-bind:style="{\
- zIndex: user.commits.length - j,\
- borderLeftWidth: `${getWidth(commit)}em`,\
- right: `${((getSlicePos(slice.date)\
- + (getCommitPos(k, slice.commitResults.length))) * 100)}%`\
- }"
- )
+ .ramp-padding(
+ v-bind:style="optimiseTimeline ? {width: `${100 - optimisedPadding * 2}%`, left: `${optimisedPadding}%`} : ''"
+ )
+ template(v-for="(slice, j) in user.commits")
+ template(v-for="(commit, k) in slice.commitResults")
+ a.ramp__slice(
+ draggable="false",
+ v-on:click="rampClick",
+ v-bind:href="getLink(commit)", target="_blank",
+ v-bind:title="getContributionMessageByCommit(slice, commit)",
+ v-bind:class="`ramp__slice--color${getRampColor(commit, slice)}`,\
+ !isBrokenLink(getLink(commit)) ? '' : 'broken-link'",
+ v-bind:style="{\
+ zIndex: user.commits.length - j,\
+ borderLeftWidth: `${getWidth(commit)}em`,\
+ right: `${((getSlicePos(slice.date)\
+ + (getCommitPos(k, slice.commitResults.length))) * 100)}%`\
+ }"
+ )
template(v-else)
a(v-bind:href="getReportLink()", target="_blank")
@@ -30,8 +33,11 @@
zIndex: user.commits.length - j,\
borderLeftWidth: `${getWidth(slice)}em`,\
right: `${(getSlicePos(tframe === 'day' ? slice.date : slice.endDate) * 100)}%` \
- }"
+ }"
)
+.date-indicators(v-if="optimiseTimeline")
+ span {{optimisedMinimumDate}}
+ span {{optimisedMaximumDate}}