Skip to content

Commit

Permalink
Add hardware profile table (#3573)
Browse files Browse the repository at this point in the history
* Add hardware profile table

* address feedback
  • Loading branch information
DaoDaoNoCode authored Dec 17, 2024
1 parent 5d3efb2 commit 065da14
Show file tree
Hide file tree
Showing 17 changed files with 999 additions and 5 deletions.
107 changes: 107 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/hardwareProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Contextual } from '~/__tests__/cypress/cypress/pages/components/Contextual';
import { TableRow } from './components/table';

class HardwareProfileTableToolbar extends Contextual<HTMLElement> {
findToggleButton(id: string) {
return this.find().pfSwitch(id).click();
}

findFilterMenuOption(id: string, name: string): Cypress.Chainable<JQuery<HTMLElement>> {
return this.findToggleButton(id).parents().findByRole('menuitem', { name });
}

findFilterInput(name: string): Cypress.Chainable<JQuery<HTMLElement>> {
return this.find().findByLabelText(`Filter by ${name}`);
}

findSearchInput(): Cypress.Chainable<JQuery<HTMLElement>> {
return this.find().findByTestId('filter-toolbar-text-field');
}

selectEnableFilter(name: string) {
this.find()
.findByTestId('hardware-profile-filter-enable-select')
.findSelectOption(name)
.click();
}
}

class HardwareProfileRow extends TableRow {
findDescription() {
return this.find().findByTestId('table-row-title-description');
}

findEnabled() {
return this.find().pfSwitchValue('enable-switch');
}

findEnableSwitch() {
return this.find().pfSwitch('enable-switch');
}

findExpandableSection() {
return this.find().parent().find('[data-label="Other information"]');
}

findNodeResourceTable() {
return this.findExpandableSection().findByTestId('hardware-profile-node-resources-table');
}

findNodeSelectorTable() {
return this.findExpandableSection().findByTestId('hardware-profile-node-selectors-table');
}

findTolerationTable() {
return this.findExpandableSection().findByTestId('hardware-profile-tolerations-table');
}
}

class HardwareProfile {
visit() {
cy.visitWithLogin('/hardwareProfiles');
this.wait();
}

private wait() {
this.findAppPage();
cy.testA11y();
}

private findAppPage() {
return cy.findByTestId('app-page-title');
}

findTableHeaderButton(name: string) {
return this.findTable().find('thead').findByRole('button', { name });
}

private findTable() {
return cy.findByTestId('hardware-profile-table');
}

getRow(name: string) {
return new HardwareProfileRow(() =>
this.findTable().find(`[data-label=Name]`).contains(name).parents('tr'),
);
}

findRows() {
return this.findTable().find(`[data-label=Name]`);
}

getTableToolbar() {
return new HardwareProfileTableToolbar(() =>
cy.findByTestId('hardware-profiles-table-toolbar'),
);
}

findCreateButton() {
return cy.findByTestId('create-hardware-profile');
}

findClearFiltersButton() {
return cy.findByTestId('clear-filters-button');
}
}

export const hardwareProfile = new HardwareProfile();
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ declare global {
*/
interceptK8s: (<K extends K8sResourceCommon>(
modelOrOptions: K8sModelCommon | K8sOptions,
response?:
response:
| K
| K8sStatus
| Patch[]
Expand All @@ -67,7 +67,7 @@ declare global {
(<K extends K8sResourceCommon>(
method: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT',
modelOrOptions: K8sModelCommon | K8sOptions,
response?:
response:
| K
| K8sStatus
| Patch[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { hardwareProfile } from '~/__tests__/cypress/cypress/pages/hardwareProfile';
import { mockHardwareProfile } from '~/__mocks__/mockHardwareProfile';
import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';
import { HardwareProfileModel } from '~/__tests__/cypress/cypress/utils/models';
import { mock200Status, mockK8sResourceList } from '~/__mocks__';
import { be } from '~/__tests__/cypress/cypress/utils/should';
import { asProductAdminUser } from '~/__tests__/cypress/cypress/utils/mockUsers';
import { testPagination } from '~/__tests__/cypress/cypress/utils/pagination';

const initIntercepts = () => {
cy.interceptK8sList(
{ model: HardwareProfileModel, ns: 'opendatahub' },
mockK8sResourceList([
mockHardwareProfile({ displayName: 'Test Hardware Profile' }),
mockHardwareProfile({
name: 'test-hardware-profile-delete',
displayName: 'Test Hardware Profile Delete',
enabled: false,
}),
]),
);
};

describe('Hardware Profile', () => {
beforeEach(() => {
asProductAdminUser();
});

describe('main table', () => {
it('table sorting and pagination', () => {
const totalItems = 50;
cy.interceptK8sList(
{ model: HardwareProfileModel, ns: 'opendatahub' },
mockK8sResourceList(
Array.from({ length: totalItems }, (_, i) =>
mockHardwareProfile({
displayName: `Test Hardware Profile - ${i}`,
description: `hardware profile ${i}`,
}),
),
),
);
hardwareProfile.visit();
const tableRow = hardwareProfile.getRow('Test Hardware Profile - 0');
tableRow.findDescription().contains('hardware profile 0');

// top pagination
testPagination({
totalItems,
firstElement: 'Test Hardware Profile - 0',
paginationVariant: 'top',
});

// bottom pagination
testPagination({
totalItems,
firstElement: 'Test Hardware Profile - 0',
paginationVariant: 'bottom',
});

//sort by Name
hardwareProfile.findTableHeaderButton('Name').click();
hardwareProfile.findTableHeaderButton('Name').should(be.sortDescending);
hardwareProfile.findTableHeaderButton('Name').click();
hardwareProfile.findTableHeaderButton('Name').should(be.sortAscending);

// sort by last modified
hardwareProfile.findTableHeaderButton('Last modified').click();
hardwareProfile.findTableHeaderButton('Last modified').should(be.sortAscending);
hardwareProfile.findTableHeaderButton('Last modified').click();
hardwareProfile.findTableHeaderButton('Last modified').should(be.sortDescending);

hardwareProfile.findCreateButton().should('be.enabled');
});

it('table filtering and searching ', () => {
initIntercepts();
hardwareProfile.visit();

const hardwareProfileTableToolbar = hardwareProfile.getTableToolbar();
hardwareProfile.findRows().should('have.length', 2);

hardwareProfileTableToolbar.findSearchInput().type('Test Hardware Profile Delete');
hardwareProfile.findRows().should('have.length', 1);
hardwareProfile.getRow('Test Hardware Profile Delete').find().should('exist');

hardwareProfileTableToolbar.findFilterInput('name').clear();
hardwareProfile.findRows().should('have.length', 2);

hardwareProfileTableToolbar
.findFilterMenuOption('filter-toolbar-dropdown', 'Enabled')
.click();
hardwareProfileTableToolbar.selectEnableFilter('Enabled');
hardwareProfile.findRows().should('have.length', 1);
hardwareProfile.getRow('Test Hardware Profile').find().should('exist');

hardwareProfileTableToolbar.selectEnableFilter('Disabled');
hardwareProfile.findRows().should('have.length', 1);
hardwareProfile.getRow('Test Hardware Profile Delete').find().should('exist');
hardwareProfileTableToolbar.findFilterMenuOption('filter-toolbar-dropdown', 'Name').click();
hardwareProfileTableToolbar.findFilterInput('name').type('No match');
hardwareProfile.findRows().should('have.length', 0);
hardwareProfile.findClearFiltersButton().click();
hardwareProfile.findRows().should('have.length', 2);
});

it('delete hardware profile', () => {
initIntercepts();
cy.interceptK8s(
'DELETE',
{
model: HardwareProfileModel,
ns: 'test-project',
name: 'test-hardware-profile-delete',
},
mock200Status({}),
).as('delete');
hardwareProfile.visit();
hardwareProfile.getRow('Test Hardware Profile Delete').findKebabAction('Delete').click();
deleteModal.findSubmitButton().should('be.disabled');
deleteModal.findInput().fill('Test Hardware Profile Delete');
deleteModal.findSubmitButton().should('be.enabled').click();
cy.wait('@delete');
});

it('toggle hardware profile enablement', () => {
initIntercepts();
cy.interceptK8s('PATCH', HardwareProfileModel, mockHardwareProfile({})).as(
'toggleHardwareProfile',
);
hardwareProfile.visit();
hardwareProfile.getRow('Test Hardware Profile Delete').findEnabled().should('not.be.checked');
hardwareProfile.getRow('Test Hardware Profile').findEnabled().should('be.checked');
hardwareProfile.getRow('Test Hardware Profile').findEnableSwitch().click();

cy.wait('@toggleHardwareProfile').then((interception) => {
expect(interception.request.body).to.eql([
{ op: 'replace', path: '/spec/enabled', value: false },
]);
});
hardwareProfile.getRow('Test Hardware Profile').findEnabled().should('not.be.checked');
hardwareProfile.getRow('Test Hardware Profile').findEnableSwitch().click();
cy.wait('@toggleHardwareProfile').then((interception) => {
expect(interception.request.body).to.eql([
{ op: 'replace', path: '/spec/enabled', value: true },
]);
});
hardwareProfile.getRow('Test Hardware Profile').findEnabled().should('be.checked');
});
});

describe('expandable section', () => {
it('should hide nested tables that do not have data', () => {
cy.interceptK8sList(
{ model: HardwareProfileModel, ns: 'opendatahub' },
mockK8sResourceList([
mockHardwareProfile({
displayName: 'Test Hardware Profile',
}),
mockHardwareProfile({
displayName: 'Test Hardware Profile Empty',
nodeSelectors: [],
identifiers: [],
tolerations: [],
}),
]),
);
hardwareProfile.visit();
const row1 = hardwareProfile.getRow('Test Hardware Profile');
row1.findExpandButton().click();
row1.findNodeSelectorTable().should('exist');
row1.findNodeResourceTable().should('exist');
row1.findTolerationTable().should('exist');
const row2 = hardwareProfile.getRow('Test Hardware Profile Empty');
row2.findExpandButton().click();
row2.findNodeSelectorTable().should('not.exist');
row2.findNodeResourceTable().should('not.exist');
row2.findTolerationTable().should('not.exist');
});

it('should show dash when there is no value on the tolerations table', () => {
cy.interceptK8sList(
{ model: HardwareProfileModel, ns: 'opendatahub' },
mockK8sResourceList([
mockHardwareProfile({
displayName: 'Test Hardware Profile Empty',
nodeSelectors: [],
identifiers: [],
tolerations: [{ key: 'test-key' }],
}),
]),
);
hardwareProfile.visit();
const row = hardwareProfile.getRow('Test Hardware Profile Empty');
row.findExpandButton().click();
row.findNodeSelectorTable().should('not.exist');
row.findNodeResourceTable().should('not.exist');
row.findTolerationTable().should('exist');

row.findTolerationTable().find(`[data-label=Operator]`).should('contain.text', '-');
row.findTolerationTable().find(`[data-label=Key]`).should('contain.text', 'test-key');
row.findTolerationTable().find(`[data-label=Value]`).should('contain.text', '-');
row.findTolerationTable().find(`[data-label=Effect]`).should('contain.text', '-');
row
.findTolerationTable()
.find(`[data-label="Toleration seconds"]`)
.should('contain.text', '-');
});
});
});
24 changes: 24 additions & 0 deletions frontend/src/api/k8s/hardwareProfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
k8sDeleteResource,
k8sGetResource,
k8sListResource,
k8sPatchResource,
K8sStatus,
k8sUpdateResource,
} from '@openshift/dynamic-plugin-sdk-utils';
Expand Down Expand Up @@ -72,6 +73,29 @@ export const updateHardwareProfile = (
);
};

export const toggleHardwareProfileEnablement = (
name: string,
namespace: string,
enabled: boolean,
opts?: K8sAPIOptions,
): Promise<HardwareProfileKind> =>
k8sPatchResource<HardwareProfileKind>(
applyK8sAPIOptions(
{
model: HardwareProfileModel,
queryOptions: { name, ns: namespace },
patches: [
{
op: 'replace',
path: '/spec/enabled',
value: enabled,
},
],
},
opts,
),
);

export const deleteHardwareProfile = (
hardwareProfileName: string,
namespace: string,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/FilterToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function FilterToolbar<T extends string>({
data-testid={`${testId}-dropdown`}
id={`${testId}-toggle-button`}
ref={toggleRef}
aria-label="Pipeline Filter toggle"
aria-label="Filter toggle"
onClick={() => setOpen(!open)}
isExpanded={open}
icon={<FilterIcon />}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/k8sTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,7 @@ export type AcceleratorProfileKind = K8sResourceCommon & {
export type HardwareProfileKind = K8sResourceCommon & {
metadata: {
name: string;
namespace: string;
};
spec: {
displayName: string;
Expand Down
Loading

0 comments on commit 065da14

Please sign in to comment.