diff --git a/docs/ug/usingReports.md b/docs/ug/usingReports.md
index ebe587bd2c..6aa5ebae6c 100644
--- a/docs/ug/usingReports.md
+++ b/docs/ug/usingReports.md
@@ -118,6 +118,7 @@ The `Tool Bar` at the top of the Chart panel provides a set of configuration opt
* a breakdown of the number of lines of codes added to each file type (if the checkbox is checked). More info on note [3] below.
* `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
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_showTags.cy.js b/frontend/cypress/tests/chartView/chartView_showTags.cy.js
new file mode 100644
index 0000000000..0e5ed49e67
--- /dev/null
+++ b/frontend/cypress/tests/chartView/chartView_showTags.cy.js
@@ -0,0 +1,67 @@
+describe('show tags', () => {
+ it('unchecked should not display any tags for a group', () => {
+ cy.get('#summary label.show-tags > input:visible')
+ .should('be.visible')
+ .uncheck()
+ .should('not.be.checked');
+
+ cy.get('.summary-charts__title--tags')
+ .should('not.exist');
+ });
+
+ it('checked should display all tags for a group', () => {
+ cy.get('#summary label.show-tags > input:visible')
+ .should('be.visible')
+ .check()
+ .should('be.checked');
+
+ cy.get('#summary label.merge-group > input:visible')
+ .should('be.visible')
+ .check()
+ .should('be.checked');
+
+ cy.get('.icon-button.fa-list-ul')
+ .should('exist')
+ .first()
+ .click();
+
+ const correctTags = [];
+
+ cy.get('.zoom__title--tags')
+ .find('.tag')
+ .each(($tag) => correctTags.push($tag.text().trim()))
+ .then(() => {
+ cy.get('.summary-charts')
+ .first()
+ .find('.summary-charts__title--tags')
+ .find('.tag')
+ .each(($tag) => {
+ expect(correctTags).to.include($tag.text().trim());
+ });
+
+ cy.get('.summary-charts')
+ .first()
+ .find('.summary-charts__title--tags')
+ .find('.tag')
+ .should('have.length', correctTags.length);
+ });
+ });
+
+ it('clicked should redirect to the correct tag page', () => {
+ cy.get('#summary label.show-tags > input:visible')
+ .should('be.visible')
+ .check()
+ .should('be.checked');
+
+ cy.get('.summary-charts__title--tags')
+ .find('.tag')
+ .first()
+ .invoke('removeAttr', 'target') // to open in the same window
+ .click();
+
+ cy.origin('https://github.com', () => {
+ cy.url()
+ .should('equal', 'https://github.com/reposense/RepoSense/releases/tag/v1.0');
+ });
+ });
+});
diff --git a/frontend/cypress/tests/chartView/chartView_zoomFeature.cy.js b/frontend/cypress/tests/chartView/chartView_zoomFeature.cy.js
index e3abafacb2..5fb72ddede 100644
--- a/frontend/cypress/tests/chartView/chartView_zoomFeature.cy.js
+++ b/frontend/cypress/tests/chartView/chartView_zoomFeature.cy.js
@@ -242,7 +242,7 @@ describe('date changes in chart view should reflect in zoom', () => {
});
describe('range changes in chartview should reflect in zoom', () => {
- const zoomKey = Cypress.platform === 'darwin' ? '{meta}' : '{ctrl}';
+ const zoomKeyOption = Cypress.platform === 'darwin' ? { metaKey: true } : { ctrlKey: true };
// Assumptions: Contributer 'jamessspanggg' is the first result,
// he does not add more commits in the future,
@@ -254,11 +254,11 @@ describe('range changes in chartview should reflect in zoom', () => {
cy.get('input[name="until"]:visible')
.type('2023-12-31');
- cy.get('body').type(zoomKey, { release: false })
+ cy.get('body')
.get('#summary-charts .summary-chart__ramp .ramp')
.first()
- .click(120, 20)
- .click(250, 20);
+ .click(120, 20, zoomKeyOption)
+ .click(250, 20, zoomKeyOption);
cy.get('#tab-zoom')
.should('be.visible');
@@ -283,11 +283,11 @@ describe('range changes in chartview should reflect in zoom', () => {
cy.get('input[name="until"]:visible')
.type('2023-12-31');
- cy.get('body').type(zoomKey, { release: false })
+ cy.get('body')
.get('#summary-charts .summary-chart__ramp .ramp')
.first()
- .click(120, 20)
- .click(170, 20);
+ .click(120, 20, zoomKeyOption)
+ .click(170, 20, zoomKeyOption);
cy.get('#tab-zoom')
.should('be.visible');
@@ -312,11 +312,11 @@ describe('range changes in chartview should reflect in zoom', () => {
cy.get('input[name="until"]:visible')
.type('2023-12-31');
- cy.get('body').type(zoomKey, { release: false })
+ cy.get('body')
.get('#summary-charts .summary-chart__ramp .ramp')
.first()
- .click(170, 20)
- .click(250, 20);
+ .click(170, 20, zoomKeyOption)
+ .click(250, 20, zoomKeyOption);
cy.get('#tab-zoom')
.should('be.visible');
@@ -341,11 +341,11 @@ describe('range changes in chartview should reflect in zoom', () => {
cy.get('input[name="until"]:visible')
.type('2023-12-31');
- cy.get('body').type(zoomKey, { release: false })
+ cy.get('body')
.get('#summary-charts .summary-chart__ramp .ramp')
.first()
- .click(170, 20)
- .click(225, 20);
+ .click(170, 20, zoomKeyOption)
+ .click(225, 20, zoomKeyOption);
cy.get('#tab-zoom')
.should('be.visible');
diff --git a/frontend/cypress/tests/codeView/codeView_renderFilterHash.cy.js b/frontend/cypress/tests/codeView/codeView_renderFilterHash.cy.js
index c42998b856..1cc456e0e4 100644
--- a/frontend/cypress/tests/codeView/codeView_renderFilterHash.cy.js
+++ b/frontend/cypress/tests/codeView/codeView_renderFilterHash.cy.js
@@ -302,6 +302,20 @@ describe('render filter hash', () => {
.should('contain', 'mergegroup=reposense%2FRepoSense%5Bcypress%5D');
});
+ it('show tags: url params should persist after change and reload', () => {
+ cy.get('#summary label.show-tags input:visible')
+ .should('be.visible')
+ .check();
+
+ cy.url()
+ .should('contain', 'viewRepoTags=true');
+
+ cy.reload();
+
+ cy.url()
+ .should('contain', 'viewRepoTags=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-summary-charts.vue b/frontend/src/components/c-summary-charts.vue
index 34f676bb88..f7677ca50b 100644
--- a/frontend/src/components/c-summary-charts.vue
+++ b/frontend/src/components/c-summary-charts.vue
@@ -141,6 +141,18 @@
v-if="sortGroupSelection.includes('totalCommits')"
) {{ getPercentile(i) }} % 
span.tooltip-text.right-aligned {{ getPercentileExplanation(i) }}
+ .summary-charts__title--tags(
+ v-if="isViewingTagsByRepo"
+ )
+ a.tag(
+ v-for="tag in getTags(repo)",
+ v-bind:href="getTagLink(repo[0], tag)",
+ target="_blank",
+ vbind:key="tag",
+ tabindex="-1"
+ )
+ font-awesome-icon(icon="tags")
+ span {{ tag }}
.summary-charts__fileType--breakdown(v-if="filterBreakdown")
template(v-if="filterGroupSelection !== 'groupByNone'")
.summary-charts__fileType--breakdown__legend(
@@ -261,6 +273,16 @@
v-if="filterGroupSelection === 'groupByNone' && sortGroupSelection.includes('totalCommits')"
) {{ getPercentile(j) }} % 
span.tooltip-text.right-aligned {{ getPercentileExplanation(j) }}
+ .summary-chart__title--tags(v-if="isViewingTagsByAuthor")
+ a.tag(
+ v-for="tag in getTags(repo, user)",
+ v-bind:href="getTagLink(user, tag)",
+ target="_blank",
+ vbind:key="tag",
+ tabindex="-1"
+ )
+ font-awesome-icon(icon="tags")
+ span {{ tag }}
.summary-chart__ramp(
v-on:click="openTabZoomSubrange(user, $event, isGroupMerged(getGroupName(repo)))"
@@ -374,6 +396,10 @@ export default defineComponent({
type: Number,
default: undefined,
},
+ viewRepoTags: {
+ type: Boolean,
+ default: false,
+ },
},
data(): {
drags: Array,
@@ -420,6 +446,14 @@ export default defineComponent({
isChartWidgetMode(): boolean {
return this.chartIndex !== undefined && this.chartIndex >= 0 && this.isChartGroupWidgetMode;
},
+ isViewingTagsByRepo() {
+ return this.filterGroupSelection === FilterGroupSelection.GroupByRepos && this.viewRepoTags;
+ },
+ isViewingTagsByAuthor() {
+ return (this.filterGroupSelection === FilterGroupSelection.GroupByAuthors
+ || this.filterGroupSelection === FilterGroupSelection.GroupByNone)
+ && this.viewRepoTags;
+ },
...mapState({
mergedGroups: (state: unknown) => (state as StoreState).mergedGroups,
fileTypeColors: (state: unknown) => (state as StoreState).fileTypeColors,
@@ -576,6 +610,10 @@ export default defineComponent({
}
},
+ getTagLink(repo: User, tag: string): string | undefined {
+ return window.filterUnsupported(`${window.getRepoLinkUnfiltered(repo.repoId)}releases/tag/${tag}`);
+ },
+
// triggering opening of tabs //
openTabAuthorship(user: User, repo: Array, index: number, isMerged: boolean): void {
const {
@@ -902,6 +940,16 @@ export default defineComponent({
chart.scrollIntoView({ block: 'nearest' });
}
},
+
+ getTags(repo: Array, user?: User): Array {
+ if (user) repo = repo.filter((r) => r.name === user.name);
+ return [...new Set(repo.flatMap((r) => r.commits).flatMap((c) => c.commitResults).flatMap((r) => r.tags))]
+ .filter(Boolean) as Array;
+ },
},
});
+
+
diff --git a/frontend/src/styles/summary-chart.scss b/frontend/src/styles/summary-chart.scss
index 8cfa722497..20dd8b8feb 100644
--- a/frontend/src/styles/summary-chart.scss
+++ b/frontend/src/styles/summary-chart.scss
@@ -97,6 +97,7 @@
&__title {
align-items: center;
display: flex;
+ flex-wrap: wrap;
font-weight: bold;
text-align: left;
@@ -128,6 +129,10 @@
@include mini-font;
display: inline;
}
+
+ &--tags {
+ margin: .25rem 0;
+ }
}
&__fileType--breakdown {
diff --git a/frontend/src/styles/tags.scss b/frontend/src/styles/tags.scss
new file mode 100644
index 0000000000..13aea03fac
--- /dev/null
+++ b/frontend/src/styles/tags.scss
@@ -0,0 +1,16 @@
+@import 'colors';
+
+/* Tags in commits */
+.tag {
+ @include mini-font;
+ background: mui-color('grey', '600');
+ border-radius: 5px;
+ color: mui-color('white');
+ display: inline-block;
+ margin: .2rem .2rem .2rem 0;
+ padding: 0 3px 0 3px;
+
+ .fa-tags {
+ width: .65rem;
+ }
+}
diff --git a/frontend/src/views/c-summary.vue b/frontend/src/views/c-summary.vue
index 777ec86c48..e9fc704997 100644
--- a/frontend/src/views/c-summary.vue
+++ b/frontend/src/views/c-summary.vue
@@ -73,6 +73,13 @@
v-bind:disabled="filterGroupSelection === 'groupByNone'"
)
span merge all groups
+ label.show-tags
+ input.mui-checkbox(
+ type="checkbox",
+ v-model="viewRepoTags",
+ v-on:change="getFiltered"
+ )
+ span show tags
.error-message-box(v-if="Object.entries(errorMessages).length && !isWidgetMode")
.error-message-box__close-button(v-on:click="dismissTab($event)") ×
.error-message-box__message The following issues occurred when analyzing the following repositories:
@@ -123,7 +130,8 @@
v-bind:max-date="maxDate",
v-bind:sort-group-selection="sortGroupSelection",
v-bind:chart-group-index="chartGroupIndex",
- v-bind:chart-index="chartIndex"
+ v-bind:chart-index="chartIndex",
+ v-bind:view-repo-tags="viewRepoTags"
)
@@ -205,7 +213,8 @@ export default defineComponent({
chartGroupIndex: number | undefined,
chartIndex: number | undefined,
errorIsShowingMore: boolean,
- numberOfErrorMessagesToShow: number
+ numberOfErrorMessagesToShow: number,
+ viewRepoTags: boolean,
} {
return {
checkedFileTypes: [] as Array,
@@ -235,6 +244,7 @@ export default defineComponent({
chartIndex: undefined as number | undefined,
errorIsShowingMore: false,
numberOfErrorMessagesToShow: 4,
+ viewRepoTags: false,
};
},
computed: {
@@ -388,7 +398,7 @@ export default defineComponent({
},
setSummaryHash(): void {
- const { addHash, encodeHash } = window;
+ const { addHash, encodeHash, removeHash } = window;
addHash('search', this.filterSearch);
addHash('sort', this.sortGroupSelection);
@@ -419,7 +429,13 @@ export default defineComponent({
: '';
addHash('checkedFileTypes', checkedFileTypesHash);
} else {
- window.removeHash('checkedFileTypes');
+ removeHash('checkedFileTypes');
+ }
+
+ if (this.viewRepoTags) {
+ addHash('viewRepoTags', 'true');
+ } else {
+ removeHash('viewRepoTags');
}
encodeHash();
@@ -470,6 +486,9 @@ export default defineComponent({
if (hash.chartIndex) {
this.chartIndex = parseInt(hash.chartIndex, 10);
}
+ if (hash.viewRepoTags) {
+ this.viewRepoTags = convertBool(hash.viewRepoTags);
+ }
},
getGroupName(group: Array): string {
diff --git a/frontend/src/views/c-zoom.vue b/frontend/src/views/c-zoom.vue
index af521cbfa5..4c2f151785 100644
--- a/frontend/src/views/c-zoom.vue
+++ b/frontend/src/views/c-zoom.vue
@@ -385,6 +385,7 @@ export default defineComponent({