Skip to content

Commit

Permalink
ui: allow for regular expressions to be used in search (PROJQUAY-6597) (
Browse files Browse the repository at this point in the history
quay#2611)

allow regex search and simplify search input

---------

Signed-off-by: dmesser <dmesser@redhat.com>
  • Loading branch information
dmesser authored Jan 22, 2024
1 parent 46d1322 commit 40bcd1f
Show file tree
Hide file tree
Showing 22 changed files with 387 additions and 11,854 deletions.
11,735 changes: 13 additions & 11,722 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 22 additions & 7 deletions web/cypress/e2e/org-list.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,28 @@ describe('Org List Page', () => {
cy.visit('/organization');

// Filter for a single org
cy.get('#toolbar-text-input').type('user1');
cy.get('#orgslist-search-input').type('user1');
cy.contains('1 - 1 of 1');
cy.get('#toolbar-text-input').clear();
cy.get('[aria-label="Reset search"]').click();

// Filter for a non-existent org
cy.get('#toolbar-text-input').type('asdf');
cy.get('#orgslist-search-input').type('asdf');
cy.contains('0 - 0 of 0');
cy.get('#toolbar-text-input').clear();
cy.get('[aria-label="Reset search"]').click();
});

it('Search by name via regex', () => {
cy.visit('/organization');
cy.get('[id="filter-input-advanced-search"]').should('not.exist');
cy.get('[aria-label="Open advanced search"]').click();
cy.get('[id="filter-input-advanced-search"]').should('be.visible');
cy.get('[id="filter-input-regex-checker"]').click();
cy.get('#orgslist-search-input').type('^co');
cy.contains('coreos').should('exist');
cy.contains('calico').should('not.exist');
cy.get('[aria-label="Reset search"]').click();
cy.contains('coreos').should('exist');
cy.contains('calico').should('exist');
});

it('Create Org', () => {
Expand All @@ -39,7 +53,7 @@ describe('Org List Page', () => {
cy.get('#create-org-email-input').type('cypress@redhat.com');
cy.get('#create-org-confirm').click({timeout: 10000});

cy.get('input[name="search input"]').type('cypress');
cy.get('#orgslist-search-input').type('cypress');
cy.contains('1 - 1 of 1');

// Validate all required fields are populated
Expand Down Expand Up @@ -74,7 +88,7 @@ describe('Org List Page', () => {
cy.get('#delete-org-cancel').click();

// Delete single org
cy.get('#toolbar-text-input').type('projectquay');
cy.get('#orgslist-search-input').type('projectquay');
cy.contains('1 - 1 of 1');
cy.get('button[id="toolbar-dropdown-checkbox"]').click();
cy.contains('Select page').click();
Expand All @@ -89,7 +103,8 @@ describe('Org List Page', () => {
cy.get('[id="bulk-delete-modal"]')
.within(() => cy.get('button:contains("Delete")').click())
.then(() => {
cy.get('#toolbar-text-input').clear().type('projectquay');
cy.get('[aria-label="Reset search"]').click();
cy.get('#orgslist-search-input').type('projectquay');
cy.contains('0 - 0 of 0');
});
});
Expand Down
35 changes: 28 additions & 7 deletions web/cypress/e2e/repositories-list.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('Repositories List Page', () => {
cy.get('[data-testid="repository-list-table"]')
.children()
.should('have.length', 20);
cy.get('input[placeholder="Search by Name..."]').type('user1');
cy.get('#repositorylist-search-input').type('user1');
cy.get('[data-testid="repository-list-table"]').within(() => {
cy.contains('user1/hello-world').should('exist');
cy.contains('user1/nested/repo').should('exist');
Expand Down Expand Up @@ -71,7 +71,7 @@ describe('Repositories List Page', () => {
cy.get('[id="create-repository-modal"]').within(() =>
cy.get('button:contains("Create")').click(),
);
cy.get('input[placeholder="Search by Name..."]').type('user1');
cy.get('#repositorylist-search-input').type('user1');
cy.contains('user1/new-repo').should('exist');
cy.get('tr:contains("user1/new-repo")').within(() =>
cy.contains('public').should('exist'),
Expand All @@ -92,7 +92,7 @@ describe('Repositories List Page', () => {
cy.get('[id="create-repository-modal"]').within(() =>
cy.get('button:contains("Create")').click(),
);
cy.get('input[placeholder="Search by Name..."]').type('user1');
cy.get('#repositorylist-search-input').type('user1');
cy.contains('new-repo').should('exist');
cy.get('tr:contains("new-repo")').within(() =>
cy.contains('private').should('exist'),
Expand Down Expand Up @@ -153,7 +153,7 @@ describe('Repositories List Page', () => {

it('makes multiple repositories public', () => {
cy.visit('/repository');
cy.get('input[placeholder="Search by Name..."]').type('user1');
cy.get('#repositorylist-search-input').type('user1');
cy.get('button[id="toolbar-dropdown-checkbox"]').click();
cy.contains('Select page (2)').click();
cy.contains('Actions').click();
Expand All @@ -168,7 +168,7 @@ describe('Repositories List Page', () => {

it('makes multiple repositories private', () => {
cy.visit('/repository');
cy.get('input[placeholder="Search by Name..."]').type('projectquay');
cy.get('#repositorylist-search-input').type('projectquay');
cy.get('button[id="toolbar-dropdown-checkbox"]').click();
cy.contains('Select page (20)').click();
cy.contains('Actions').click();
Expand All @@ -183,15 +183,36 @@ describe('Repositories List Page', () => {

it('searches by name', () => {
cy.visit('/repository');
cy.get('input[placeholder="Search by Name..."]').type('hello');
cy.get('#repositorylist-search-input').type('hello');
cy.get('td[data-label="Name"]')
.filter(':contains("hello-world")')
.should('have.length', 2);
});

it('searches by name via regex', () => {
cy.visit('/repository');
cy.get('[id="filter-input-advanced-search"]').should('not.exist');
cy.get('[aria-label="Open advanced search"]').click();
cy.get('[id="filter-input-advanced-search"]').should('be.visible');
cy.get('[id="filter-input-regex-checker"]').click();
cy.get('#repositorylist-search-input').type('repo[0-9]$');
cy.get('td[data-label="Name"]')
.filter(':contains("repo1")')
.should('exist');
cy.get('td[data-label="Name"]')
.filter(':contains("repo10")')
.should('not.exist');
cy.contains('1 - 9 of 9').should('exist');
cy.get('[aria-label="Reset search"]').click();
cy.get('td[data-label="Name"]')
.filter(':contains("repo10")')
.should('exist');
cy.contains('1 - 20 of 156').should('exist');
});

it('searches by name including organization', () => {
cy.visit('/repository');
cy.get('input[placeholder="Search by Name..."]').type('user1');
cy.get('#repositorylist-search-input').type('user1');
cy.get('td[data-label="Name"]')
.filter(':contains("user1")')
.should('have.length', 2);
Expand Down
26 changes: 20 additions & 6 deletions web/cypress/e2e/repository-details.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import moment from 'moment';
import {formatDate} from '../../src/libs/utils';
import moment from 'moment';

describe('Repository Details Page', () => {
beforeEach(() => {
Expand All @@ -24,15 +23,15 @@ describe('Repository Details Page', () => {
cy.visit('/repository/user1/hello-world');
const latestRow = cy.get('tbody:contains("latest")');
latestRow.first().within(() => {
cy.get(`[data-label="Name"]`).should('have.text', 'latest');
cy.get(`[data-label="Tag"]`).should('have.text', 'latest');
cy.get(`[data-label="Security"]`).should('have.text', '3 Critical');
cy.get(`[data-label="Size"]`).should('have.text', '2.48 kB');
cy.get(`[data-label="Last Modified"]`).should(
'have.text',
formatDate('Thu, 27 Jul 2023 17:31:10 -0000'),
);
cy.get(`[data-label="Expires"]`).should('have.text', 'Never');
cy.get(`[data-label="Manifest"]`).should(
cy.get(`[data-label="Digest"]`).should(
'have.text',
'sha256:f54a58bc1aac',
);
Expand All @@ -50,7 +49,7 @@ describe('Repository Details Page', () => {
const manifestListRow = cy.get('tbody:contains("manifestlist")');
manifestListRow.first().within(() => {
// Assert values for top level row
cy.get(`[data-label="Name"]`).should('have.text', 'manifestlist');
cy.get(`[data-label="Tag"]`).should('have.text', 'manifestlist');
cy.get(`[data-label="Security"]`).should(
'have.text',
'See Child Manifests',
Expand All @@ -61,7 +60,7 @@ describe('Repository Details Page', () => {
formatDate('Thu, 04 Nov 2022 19:15:15 -0000'),
);
cy.get(`[data-label="Expires"]`).should('have.text', 'Never');
cy.get(`[data-label="Manifest"]`).should(
cy.get(`[data-label="Digest"]`).should(
'have.text',
'sha256:7693efac53eb',
);
Expand Down Expand Up @@ -343,10 +342,25 @@ describe('Repository Details Page', () => {
cy.contains('manifestlist').should('not.exist');
});

it('search by name via regex', () => {
cy.visit('/repository/user1/hello-world');
cy.get('[id="filter-input-advanced-search"]').should('not.exist');
cy.get('[aria-label="Open advanced search"]').click();
cy.get('[id="filter-input-advanced-search"]').should('be.visible');
cy.get('[id="filter-input-regex-checker"]').click();
cy.get('#tagslist-search-input').type('test$');
cy.contains('latest').should('exist');
cy.contains('manifestlist').should('not.exist');
cy.get('[aria-label="Reset search"]').click();
cy.get('#tagslist-search-input').type('^manifest');
cy.contains('latest').should('not.exist');
cy.contains('manifestlist').should('exist');
});

it('search by manifest', () => {
cy.visit('/repository/user1/hello-world');
cy.get('#toolbar-dropdown-filter').click();
cy.get('span').contains('Manifest').click();
cy.get('span').contains('Digest').click();
cy.get('#tagslist-search-input').type('f54a58bc1aac');
cy.contains('latest').should('exist');
cy.contains('manifestlist').should('not.exist');
Expand Down
2 changes: 1 addition & 1 deletion web/cypress/e2e/robot-accounts.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe('Robot Accounts Page', () => {
it('Update Repo Permissions', () => {
cy.visit('/organization/testorg?tab=Robotaccounts');
cy.contains('1 repository').click();
cy.get('#add-repository-bulk-select').contains('1 selected');
cy.get('#add-repository-bulk-select').contains('1');
cy.get('#toggle-descriptions').click();
cy.get('[role="menuitem"]').contains('Admin').click();
cy.get('footer')
Expand Down
36 changes: 33 additions & 3 deletions web/src/atoms/OrganizationListState.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,52 @@
import {atom} from 'recoil';
import {atom, selector} from 'recoil';
import {SearchState} from 'src/components/toolbar/SearchTypes';
import {IOrganization} from 'src/resources/OrganizationResource';
import {OrganizationDetail} from 'src/hooks/UseOrganizations';
import ColumnNames from 'src/routes/OrganizationsList/ColumnNames';
import {OrganizationsTableItem} from 'src/routes/OrganizationsList/OrganizationsList';

export const refreshPageState = atom({
key: 'refreshOrgPageState',
default: 0,
});

// Organization List page
export const selectedOrgsState = atom<IOrganization[]>({
export const selectedOrgsState = atom<OrganizationsTableItem[]>({
key: 'selectedOrgsState',
default: [],
});

export const searchOrgsState = atom<SearchState>({
key: 'searchOrgsState',
default: {
query: '',
field: ColumnNames.name,
isRegEx: false,
},
});

export const searchOrgsFilterState = selector({
key: 'searchOrgsFilter',
get: ({get}) => {
const search = get(searchOrgsState);
if (search.query === '') {
return null;
}

const filterByName = (org: OrganizationDetail) =>
org.name.includes(search.query);
const filterByNameRegex = (org: OrganizationDetail) => {
try {
const regex = new RegExp(search.query, 'i');
return regex.test(org.name);
} catch (e) {
return false;
}
};

if (search.isRegEx) {
return filterByNameRegex;
} else {
return filterByName;
}
},
});
47 changes: 43 additions & 4 deletions web/src/atoms/RepositoryState.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,56 @@
import {atom} from 'recoil';
import {SearchState} from 'src/components/toolbar/SearchTypes';
import {atom, selector} from 'recoil';
import {OrgSearchState} from 'src/components/toolbar/SearchTypes';
import {RepositoryListColumnNames} from 'src/routes/RepositoriesList/ColumnNames';
import {RepoListTableItem} from 'src/routes/RepositoriesList/RepositoriesList';

export const selectedReposState = atom({
key: 'selectedReposState',
default: [],
});

export const searchRepoState = atom<SearchState>({
key: 'searchRepoState',
export const searchReposState = atom<OrgSearchState>({
key: 'searchReposState',
default: {
query: '',
field: RepositoryListColumnNames.name,
isRegEx: false,
currentOrganization: null,
},
});

export const searchReposFilterState = selector({
key: 'searchReposFilter',
get: ({get}) => {
const search = get(searchReposState);
if (search.query === '') {
return null;
}

const filterByName = (repo: RepoListTableItem) => {
const repoName =
search.currentOrganization == null
? `${repo.namespace}/${repo.name}`
: repo.name;
return repoName.includes(search.query);
};
const filterByNameRegex = (repo: RepoListTableItem) => {
const repoName =
search.currentOrganization == null
? `${repo.namespace}/${repo.name}`
: repo.name;
try {
const regex = new RegExp(search.query, 'i');
return regex.test(repoName);
} catch (e) {
return false;
}
};

if (search.isRegEx) {
return filterByNameRegex;
} else {
return filterByName;
}
},
});

Expand Down
31 changes: 28 additions & 3 deletions web/src/atoms/TagListState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const searchTagsState = atom<SearchState>({
default: {
query: '',
field: ColumnNames.name,
isRegEx: false,
},
});

Expand All @@ -20,15 +21,39 @@ export const searchTagsFilterState = selector({
}

const filterByName = (tag: Tag) => tag.name.includes(search.query);
const filterByNameRegex = (tag: Tag) => {
try {
const regex = new RegExp(search.query, 'i');
return regex.test(tag.name);
} catch (e) {
return false;
}
};
const filterByDigest = (tag: Tag) =>
tag.manifest_digest.includes(search.query);
const filterByDigestRegex = (tag: Tag) => {
try {
const regex = new RegExp(search.query, 'i');
return regex.test(tag.manifest_digest);
} catch (e) {
return false;
}
};

switch (search.field) {
case ColumnNames.manifest:
return filterByDigest;
case ColumnNames.digest:
if (search.isRegEx) {
return filterByDigestRegex;
} else {
return filterByDigest;
}
case ColumnNames.name:
default:
return filterByName;
if (search.isRegEx) {
return filterByNameRegex;
} else {
return filterByName;
}
}
},
});
Expand Down
Loading

0 comments on commit 40bcd1f

Please sign in to comment.