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({