diff --git a/docs/ug/usingReports.md b/docs/ug/usingReports.md index 65d3f78031..7c19e8ba8c 100644 --- a/docs/ug/usingReports.md +++ b/docs/ug/usingReports.md @@ -94,6 +94,7 @@ The `Tool Bar` at the top of the Chart panel provides a set of configuration opt * Multiple keywords/terms can be used, separated by spaces. * Entries that contain _any_ (not necessarily _all_) of the search terms will be displayed. * The keywords used to filter the author and repository are case-insensitive. + * Starting a search with `tag:` will filter author and repository by git tags. Similar search rules as above (like separating multiple tag names by space) apply. * `Group by`: grouping criteria for the rows of results. * `None`: results will not be grouped in any particular way. * `Repo/Branch`: results will be grouped by repositories and its' associating branches. diff --git a/frontend/cypress/tests/chartView/chartView_toolBar_searchBox.cy.js b/frontend/cypress/tests/chartView/chartView_toolBar_searchBox.cy.js index 246ceff67e..8fb9352fb6 100644 --- a/frontend/cypress/tests/chartView/chartView_toolBar_searchBox.cy.js +++ b/frontend/cypress/tests/chartView/chartView_toolBar_searchBox.cy.js @@ -5,8 +5,6 @@ describe('search bar', () => { .type('abcdef') .type('{enter}'); - // Enter does not work. Related issue: https://github.com/cypress-io/cypress/issues/3405 - // Let's manually submit form cy.get('#summary-wrapper form.summary-picker') .submit(); @@ -23,8 +21,6 @@ describe('search bar', () => { .type('Yong Hao TENG') .type('{enter}'); - // Enter does not work. Related issue: https://github.com/cypress-io/cypress/issues/3405 - // Let's manually submit form cy.get('#summary-wrapper form.summary-picker') .submit(); @@ -33,4 +29,119 @@ describe('search bar', () => { expect(children).to.equal(1); }); }); + + it('searching by non-existent tag shows no results', () => { + cy.get('#app #tab-resize .tab-close').click(); + cy.get('#summary-wrapper input[type=text]') + .type('tag: asdfghjkl') + .type('{enter}'); + + cy.get('#summary-wrapper form.summary-picker') + .submit(); + + cy.get('#summary-wrapper #summary-charts') + .should('be.empty'); + }); + + it("searching tag that only exists in one author's commits shows one result", () => { + cy.get('#app #tab-resize .tab-close').click(); + cy.get('#summary-wrapper input[type=text]') + .type('tag: v1.8') + .type('{enter}'); + + cy.get('#summary-wrapper form.summary-picker') + .submit(); + + cy.get('.summary-chart__title--name') + .should('have.length', 1) + .and('contain', 'Eugene (eugenepeh)'); + + cy.get('.icon-button.fa-list-ul') + .should('exist') + .first() + .click(); + + cy.get('.zoom__title--tags > .tag span') + .should('contain', 'v1.8'); + }); + + it("searching tag that only exists in two authors' commits shows two results", () => { + cy.get('#app #tab-resize .tab-close').click(); + cy.get('#summary-wrapper input[type=text]') + .type('tag: v1.10') + .type('{enter}'); + + cy.get('#summary-wrapper form.summary-picker') + .submit(); + + cy.get('.summary-chart__title--name') + .should('have.length', 2) + .and('contain', 'Eugene (eugenepeh)') + .and('contain', 'James (jamessspanggg)'); + + cy.get('.icon-button.fa-list-ul') + .should('exist') + .each(($ele) => { + cy.wrap($ele).click(); + cy.get('.zoom__title--tags > .tag span') + .should('contain', 'v1.10'); + }); + }); + + it("search field doesn't start with 'tag:' prefix but still contains it shows no results", () => { + cy.get('#app #tab-resize .tab-close').click(); + cy.get('#summary-wrapper input[type=text]') + .type('v1.10 tag: v1.10') + .type('{enter}'); + + cy.get('#summary-wrapper form.summary-picker') + .submit(); + + cy.get('#summary-wrapper #summary-charts') + .should('be.empty'); + }); + + it("search field doesn't contain 'tag:' at all shows no results", () => { + cy.get('#app #tab-resize .tab-close').click(); + cy.get('#summary-wrapper input[type=text]') + .type('v1.10') + .type('{enter}'); + + cy.get('#summary-wrapper form.summary-picker') + .submit(); + + cy.get('#summary-wrapper #summary-charts') + .should('be.empty'); + }); + + it('searching for multiple tags shows results containing all the tags searched', () => { + cy.get('#app #tab-resize .tab-close').click(); + cy.get('#summary-wrapper input[type=text]') + .type('tag: bb v1.10') + .type('{enter}'); + + cy.get('#summary-wrapper form.summary-picker') + .submit(); + + cy.get('.summary-chart__title--name') + .should('have.length', 2) + .and('contain', 'Eugene (eugenepeh)') + .and('contain', 'James (jamessspanggg)'); + + cy.get('.icon-button.fa-list-ul') + .should('exist') + .first() + .click(); + + cy.get('.zoom__title--tags > .tag span') + .should('contain', 'bb'); + + cy.get('.icon-button.fa-list-ul') + .should('exist') + .eq(1) + .click(); + + cy.get('.zoom__title--tags > .tag span') + .should('contain', 'v1.10'); + }); }); diff --git a/frontend/src/views/c-summary.vue b/frontend/src/views/c-summary.vue index b6149b702a..55035a06cc 100644 --- a/frontend/src/views/c-summary.vue +++ b/frontend/src/views/c-summary.vue @@ -153,7 +153,11 @@ import { CommitResult, } from '../types/types'; import { ErrorMessage } from '../types/zod/summary-type'; -import { AuthorFileTypeContributions, FileTypeAndContribution } from '../types/zod/commits-type'; +import { + AuthorDailyContributions, + AuthorFileTypeContributions, + FileTypeAndContribution, +} from '../types/zod/commits-type'; import { ZoomInfo } from '../types/vuex.d'; import { FilterGroupSelection, FilterTimeFrame, SortGroupSelection, SortWithinGroupSelection, @@ -472,6 +476,13 @@ export default defineComponent({ .some((param) => user.searchPath.includes(param)); }, + isMatchSearchedTag(filterSearch: string, tag: string) { + return !filterSearch || filterSearch.toLowerCase() + .split(' ') + .filter(Boolean) + .some((param) => tag.includes(param)); + }, + toggleBreakdown() { // Reset the file type filter if (this.checkedFileTypes.length !== this.fileTypes.length) { @@ -493,29 +504,51 @@ export default defineComponent({ getFilteredRepos() { // array of array, sorted by repo const full: Array> = []; + const tagSearchPrefix = 'tag:'; // create deep clone of this.repos to not modify the original content of this.repos // when merging groups const groups = this.hasMergedGroups() ? JSON.parse(JSON.stringify(this.repos)) as Array : this.repos; - groups.forEach((repo) => { - const res: Array = []; + const res: Array = []; - // filtering - repo.users?.forEach((user) => { - if (this.isMatchSearchedUser(this.filterSearch, user)) { - this.getUserCommits(user, this.filterSinceDate, this.filterUntilDate); - if (this.filterTimeFrame === 'week') { - this.splitCommitsWeek(user, this.filterSinceDate, this.filterUntilDate); + if (this.filterSearch.startsWith(tagSearchPrefix)) { + const searchedTags = this.filterSearch.split(tagSearchPrefix)[1]; + groups.forEach((repo) => { + const commits = repo.commits; + if (!commits) return; + + Object.entries(commits.authorDailyContributionsMap).forEach(([author, contributions]) => { + contributions = contributions as Array; + const tags = contributions.flatMap((c) => c.commitResults).flatMap((r) => r.tags); + + if (tags.some((tag) => tag && this.isMatchSearchedTag(searchedTags, tag))) { + const user = repo.users?.find((u) => u.name === author); + if (user) { + this.updateCheckedFileTypeContribution(user); + res.push(user); + } } - this.updateCheckedFileTypeContribution(user); - res.push(user); - } + }); + }); + } else { + groups.forEach((repo) => { + // filtering + repo.users?.forEach((user) => { + if (this.isMatchSearchedUser(this.filterSearch, user)) { + this.getUserCommits(user, this.filterSinceDate, this.filterUntilDate); + if (this.filterTimeFrame === 'week') { + this.splitCommitsWeek(user, this.filterSinceDate, this.filterUntilDate); + } + this.updateCheckedFileTypeContribution(user); + res.push(user); + } + }); }); + } - if (res.length) { - full.push(res); - } - }); + if (res.length) { + full.push(res); + } this.filtered = full; this.getOptionWithOrder();