Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#2109] Add search by tag functionality #2116

Merged
merged 13 commits into from
Mar 19, 2024
Merged
1 change: 1 addition & 0 deletions docs/ug/usingReports.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
127 changes: 127 additions & 0 deletions frontend/cypress/tests/chartView/chartView_toolBar_searchBox.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,131 @@ describe('search bar', () => {
expect(children).to.equal(1);
});
});

jonasongg marked this conversation as resolved.
Show resolved Hide resolved
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}');

// 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();

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}');

// 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();

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}');

// 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();

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}');

// Enter does not work. Related issue:
ckcherry23 marked this conversation as resolved.
Show resolved Hide resolved
// Let's manually submit form
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}');

// Enter does not work. Related issue:
// Let's manually submit form
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', () => {
jonasongg marked this conversation as resolved.
Show resolved Hide resolved
cy.get('#app #tab-resize .tab-close').click();
cy.get('#summary-wrapper input[type=text]')
.type('tag: bb v1.10')
.type('{enter}');

// Enter does not work. Related issue:
// Let's manually submit form
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');
});
});
65 changes: 49 additions & 16 deletions frontend/src/views/c-summary.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -493,29 +504,51 @@ export default defineComponent({
getFilteredRepos() {
// array of array, sorted by repo
const full: Array<Array<User>> = [];
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<Repo> : this.repos;
groups.forEach((repo) => {
const res: Array<User> = [];
const res: Array<User> = [];

// 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<AuthorDailyContributions>;
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();
Expand Down
Loading