diff --git a/cypress/e2e/awx/access/users/users.cy.ts b/cypress/e2e/awx/access/users/users.cy.ts index d7a2331581..ef95a9e009 100644 --- a/cypress/e2e/awx/access/users/users.cy.ts +++ b/cypress/e2e/awx/access/users/users.cy.ts @@ -110,7 +110,7 @@ describe('Users Tests', () => { it('deletes a user from the users list toolbar', () => { cy.selectTableRow(user.username); - cy.clickToolbarKebabAction('delete-selected-users'); + cy.clickToolbarKebabAction('delete-users'); cy.get('#confirm').click(); cy.clickButton(/^Delete user/); cy.contains(/^Success$/); diff --git a/cypress/e2e/awx/infrastructure/hosts/hosts.cy.ts b/cypress/e2e/awx/infrastructure/hosts/hosts.cy.ts index 7387098170..953998cb15 100644 --- a/cypress/e2e/awx/infrastructure/hosts/hosts.cy.ts +++ b/cypress/e2e/awx/infrastructure/hosts/hosts.cy.ts @@ -36,7 +36,7 @@ describe('Host Tests', () => { const hostName = 'E2E Inventory host ' + randomString(4); cy.navigateTo('awx', 'hosts'); cy.clickButton(/^Create host$/); - cy.verifyPageTitle('Create Host'); + cy.verifyPageTitle('Create host'); cy.getByDataCy('name').type(hostName); cy.getByDataCy('description').type('This is the description'); cy.singleSelectByDataCy('inventory', inventory.name); @@ -48,7 +48,7 @@ describe('Host Tests', () => { cy.navigateTo('awx', 'hosts'); cy.filterTableByMultiSelect('name', [hostName]); cy.getByDataCy('edit-host').click(); - cy.verifyPageTitle('Edit host'); + cy.verifyPageTitle(`Edit ${hostName}`); cy.getByDataCy('description').clear().type('This is the description edited'); cy.getByDataCy('Submit').click(); cy.hasDetail(/^Description$/, 'This is the description edited'); @@ -82,7 +82,7 @@ describe('Host Tests', () => { const hostName = 'E2E Inventory host ' + randomString(4); cy.navigateTo('awx', 'hosts'); cy.clickButton(/^Create host$/); - cy.verifyPageTitle('Create Host'); + cy.verifyPageTitle('Create host'); cy.getByDataCy('name').type(hostName); cy.getByDataCy('description').type('This is the description'); cy.singleSelectByDataCy('inventory', inventory.name); diff --git a/cypress/e2e/awx/infrastructure/instance-groups/instanceGroups.cy.ts b/cypress/e2e/awx/infrastructure/instance-groups/instanceGroups.cy.ts index 36900323e3..77cdc94144 100644 --- a/cypress/e2e/awx/infrastructure/instance-groups/instanceGroups.cy.ts +++ b/cypress/e2e/awx/infrastructure/instance-groups/instanceGroups.cy.ts @@ -63,7 +63,7 @@ describe(`Instance Groups`, () => { cy.get('[data-cy="max-forks"]').clear(); cy.get('[data-cy="max-forks"]').type('4'); cy.intercept('POST', awxAPI`/instance_groups/`).as('createInstanceGroup'); - cy.clickButton(`Create Instance Group`); + cy.clickButton(`Create instance group`); cy.wait('@createInstanceGroup') .its('response') .then((response) => { @@ -153,7 +153,7 @@ describe(`Instance Groups`, () => { cy.filterTableByMultiSelect('name', arrayOfElementText); cy.get('tbody tr').should('have.length', 5); cy.getByDataCy('select-all').click(); - cy.clickToolbarKebabAction('delete-selected-instance-groups'); + cy.clickToolbarKebabAction('delete-instance-groups'); cy.intercept('DELETE', awxAPI`/instance_groups/*/`).as('deleteInstanceGroup'); cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { cy.get('header').contains(`Permanently delete instance groups`); @@ -187,7 +187,7 @@ describe(`Instance Groups`, () => { cy.filterTableByMultiSelect('name', arrayOfElementText); cy.get('tbody tr').should('have.length', 2); cy.get('#select-all').click(); - cy.clickToolbarKebabAction('delete-selected-instance-groups'); + cy.clickToolbarKebabAction('delete-instance-groups'); cy.intercept('DELETE', awxAPI`/instance_groups/*/`).as('deleteInstanceGroup'); cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { cy.contains( @@ -247,7 +247,7 @@ describe(`Instance Groups`, () => { cy.get('[data-cy="max-forks"]').clear(); cy.get('[data-cy="max-forks"]').type('4'); cy.intercept('POST', awxAPI`/instance_groups/`).as('createInstanceGroup'); - cy.clickButton(`Create Container Group`); + cy.clickButton(`Create container group`); cy.wait('@createInstanceGroup') .its('response') .then((response) => { @@ -326,7 +326,7 @@ describe(`Instance Groups`, () => { cy.filterTableByMultiSelect('name', arrayOfElementText); cy.get('tbody tr').should('have.length', 5); cy.getByDataCy('select-all').click(); - cy.clickToolbarKebabAction('delete-selected-instance-groups'); + cy.clickToolbarKebabAction('delete-instance-groups'); cy.intercept('DELETE', awxAPI`/instance_groups/*/`).as('deleteInstanceGroup'); cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { cy.get('header').contains(`Permanently delete container groups`); @@ -362,7 +362,7 @@ describe(`Instance Groups`, () => { cy.filterTableByMultiSelect('name', arrayOfElementText); cy.get('tbody tr').should('have.length', 2); cy.get('#select-all').click(); - cy.clickToolbarKebabAction('delete-selected-instance-groups'); + cy.clickToolbarKebabAction('delete-instance-groups'); cy.intercept('DELETE', awxAPI`/instance_groups/*/`).as('deleteInstanceGroup'); cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { cy.contains( diff --git a/cypress/e2e/awx/infrastructure/instances/instances.cy.ts b/cypress/e2e/awx/infrastructure/instances/instances.cy.ts index be3a7aa41f..47b8a67942 100644 --- a/cypress/e2e/awx/infrastructure/instances/instances.cy.ts +++ b/cypress/e2e/awx/infrastructure/instances/instances.cy.ts @@ -20,7 +20,7 @@ cyLabel(['upstream'], () => { it('can add a new instance and navigate to the details page', () => { const instanceHostname = 'E2EInstanceTestAddEdit' + randomString(5); // Navigate to the create instance page - cy.getByDataCy('add-instance').click(); + cy.getByDataCy('create-instance').click(); cy.getByDataCy('page-title').should('contain', 'Create instance'); // Create a new instance cy.getByDataCy('hostname').type(instanceHostname); @@ -60,7 +60,7 @@ cyLabel(['upstream'], () => { cy.getByDataCy('enabled').check(); cy.getByDataCy('managed_by_policy').check(); cy.getByDataCy('peers_from_control_nodes').check(); - cy.clickButton(/^Save$/); + cy.clickButton(/^Save instance$/); cy.wait('@editedInstance') .its('response.body') .then((body: Instance) => { @@ -119,7 +119,7 @@ cyLabel(['upstream'], () => { cy.getByDataCy('edit-instance').click(); cy.getByDataCy('enabled').uncheck(); cy.intercept('PATCH', awxAPI`/instances/*/`).as('editedInstance'); - cy.clickButton(/^Save$/); + cy.clickButton(/^Save instance$/); cy.wait('@editedInstance') .then((response) => { expect(response?.response?.statusCode).to.eql(200); @@ -340,17 +340,17 @@ cyLabel(['upstream'], () => { cy.url().then((currentUrl) => { expect(currentUrl.includes('peers')).to.be.true; }); - cy.getByDataCy('associate-peer').click(); + cy.getByDataCy('associate-peers').click(); cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { - cy.get('header').contains('Select Peer Addresses'); - cy.get('button').contains('Associate peer(s)').should('have.attr', 'aria-disabled', 'true'); + cy.get('header').contains('Select peer addresses'); + cy.get('button').contains('Associate peers').should('have.attr', 'aria-disabled', 'true'); cy.filterTableBySingleText(instanceToAssociate.hostname, true); cy.intercept('GET', awxAPI`/instances/${instanceToAssociate.id.toString()}/`).as( 'instanceA' ); cy.getByDataCy('checkbox-column-cell').find('input').click(); cy.wait('@instanceA'); - cy.get('button').contains('Associate peer(s)').click(); + cy.get('button').contains('Associate peers').click(); cy.get('button').contains('Close').click(); }); cy.wait('@associatePeer') @@ -375,15 +375,17 @@ cyLabel(['upstream'], () => { cy.get('tbody tr').should('have.length', 1); cy.get('[data-cy="checkbox-column-cell"] input').click(); }); - cy.clickToolbarKebabAction('disassociate'); + cy.clickToolbarKebabAction('disassociate-peers'); cy.intercept('PATCH', awxAPI`/instances/*/`).as('disassociatePeer'); cy.get('[data-ouia-component-type="PF5/ModalContent"]').within(() => { cy.get('header').contains('Disassociate peers'); - cy.get('button').contains('Disassociate peer').should('have.attr', 'aria-disabled', 'true'); + cy.get('button') + .contains('Disassociate peers') + .should('have.attr', 'aria-disabled', 'true'); cy.getByDataCy('address-column-cell').should('have.text', instanceToAssociate.hostname); cy.get('input[id="confirm"]').click(); cy.get('button') - .contains('Disassociate peer') + .contains('Disassociate peers') .should('have.attr', 'aria-disabled', 'false') .click(); }); diff --git a/cypress/e2e/awx/inventories-constructed/inventoriesConstructed.cy.ts b/cypress/e2e/awx/inventories-constructed/inventoriesConstructed.cy.ts index d508eb93f0..8740d9a599 100644 --- a/cypress/e2e/awx/inventories-constructed/inventoriesConstructed.cy.ts +++ b/cypress/e2e/awx/inventories-constructed/inventoriesConstructed.cy.ts @@ -135,7 +135,7 @@ describe('Constructed Inventories CRUD Tests', () => { cy.verifyPageTitle(newInventory.name); cy.getByDataCy('organization').contains(organization.name); cy.getByDataCy('edit-inventory').click(); - cy.verifyPageTitle('Edit Constructed Inventory'); + cy.verifyPageTitle(`Edit ${newInventory.name}`); cy.getByDataCy('toggle-json').click(); cy.getByDataCy('source-vars').type( `{"plugin": "constructed","strict": true,"groups": {"is_shutdown": "state | default('running') == 'shutdown'","product_dev": "account_alias == 'product_dev'"}}`, diff --git a/cypress/e2e/awx/inventories-source/inventorySource.cy.ts b/cypress/e2e/awx/inventories-source/inventorySource.cy.ts index f93dab004f..77d4ea3a10 100644 --- a/cypress/e2e/awx/inventories-source/inventorySource.cy.ts +++ b/cypress/e2e/awx/inventories-source/inventorySource.cy.ts @@ -65,7 +65,7 @@ describe('Inventory Sources', () => { credential = cred; goToSourceList(inventory.name); cy.clickButton(/^Create source/); - cy.verifyPageTitle('Add new source'); + cy.verifyPageTitle('Create source'); cy.getByDataCy('name').type('project source'); cy.selectDropdownOptionByResourceName('source_control_type', 'Sourced from a Project'); cy.selectDropdownOptionByResourceName('project', project.name); @@ -125,7 +125,7 @@ describe('Inventory Sources', () => { goToSourceList(inventory.name); // Create inventory source cy.getByDataCy('create-source').click(); - cy.verifyPageTitle('Add new source'); + cy.verifyPageTitle('Create source'); cy.getByDataCy('name').type('amazon ec2 source'); cy.selectDropdownOptionByResourceName('source_control_type', 'Amazon EC2'); cy.getByDataCy('host-filter').type('/^test$/'); @@ -136,7 +136,7 @@ describe('Inventory Sources', () => { cy.getByDataCy('Submit').click(); cy.verifyPageTitle('amazon ec2 source'); cy.clickButton('Edit inventory source'); - cy.verifyPageTitle('Edit source'); + cy.verifyPageTitle(`Edit amazon ec2 source`); cy.getByDataCy('name').clear().type('updated amazon ec2 source'); cy.getByDataCy('overwrite_vars').check(); cy.getByDataCy('Submit').click(); @@ -146,7 +146,7 @@ describe('Inventory Sources', () => { // Edit inventory source cy.clickButton('Edit inventory source'); cy.location('pathname').should('match', /\/edit$/); - cy.verifyPageTitle('Edit source'); + cy.verifyPageTitle(`Edit updated amazon ec2 source`); cy.getByDataCy('name').clear().type('new project'); cy.selectDropdownOptionByResourceName('source_control_type', 'Sourced from a Project'); cy.getByDataCy('overwrite_vars').check(); diff --git a/cypress/e2e/awx/inventories/inventoryGroup.cy.ts b/cypress/e2e/awx/inventories/inventoryGroup.cy.ts index ebaef791e0..c0ff72672e 100644 --- a/cypress/e2e/awx/inventories/inventoryGroup.cy.ts +++ b/cypress/e2e/awx/inventories/inventoryGroup.cy.ts @@ -50,7 +50,7 @@ describe('Inventory Groups', () => { cy.verifyPageTitle(inventory.name); cy.get(`a[href*="/groups?"]`).click(); cy.clickButton(/^Create group$/); - cy.verifyPageTitle('Create new group'); + cy.verifyPageTitle('Create group'); cy.get('[data-cy="name"]').type(newGroupName); cy.get('[data-cy="description"]').type('This is a description'); cy.dataEditorTypeByDataCy('variables', 'test: true'); @@ -99,11 +99,8 @@ describe('Inventory Groups', () => { cy.getByDataCy('name-column-cell').should('contain', host.name); cy.clickTab(/^Groups$/, true); cy.filterTableByMultiSelect('name', [group.name]); - cy.clickTableRowAction('name', group.name, 'edit-group', { - inKebab: false, - disableFilter: true, - }); - cy.verifyPageTitle('Edit group'); + cy.clickTableRowKebabAction(group.name, 'edit-group', false); + cy.verifyPageTitle(`Edit ${group.name}`); cy.get('[data-cy="name-form-group"]').type('-changed'); cy.get('[data-cy="Submit"]').click(); cy.verifyPageTitle(group.name + '-changed'); @@ -181,7 +178,7 @@ describe('Inventory Groups', () => { cy.verifyPageTitle(inventory.name); cy.get(`a[href*="/groups?"]`).click(); cy.clickButton(/^Create group$/); - cy.verifyPageTitle('Create new group'); + cy.verifyPageTitle('Create group'); cy.get('[data-cy="name"]').type(newGroupName); cy.get('[data-cy="description"]').type('This is a description'); cy.dataEditorTypeByDataCy('variables', 'test: true'); @@ -229,7 +226,7 @@ describe('Inventory Groups', () => { cy.verifyPageTitle(group.name); cy.intercept('PATCH', awxAPI`/groups/*/`).as('editGroup'); cy.get('[data-cy="edit-group"]').click(); - cy.verifyPageTitle('Edit group'); + cy.verifyPageTitle(`Edit ${group.name}`); cy.get('[data-cy="name-form-group"]').type('-changed'); cy.get('[data-cy="Submit"]').click(); cy.wait('@editGroup') @@ -262,7 +259,7 @@ describe('Inventory Groups', () => { cy.verifyPageTitle(group.name); cy.clickTab(/^Related Groups$/, true); cy.clickButton(/^Create group/); - cy.verifyPageTitle('Create new group'); + cy.verifyPageTitle('Create group'); cy.get('[data-cy="name-form-group"]').type(newRelatedGroup); cy.get('[data-cy="Submit"]').click(); cy.contains(newRelatedGroup); @@ -315,7 +312,7 @@ describe('Inventory Groups', () => { disableFilter: true, }); cy.intercept('PATCH', awxAPI`/groups/*/`).as('editGroup'); - cy.verifyPageTitle('Edit group'); + cy.verifyPageTitle(`Edit ${newGroup}`); cy.get('[data-cy="name-form-group"]').type('-changed'); cy.get('[data-cy="Submit"]').click(); cy.wait('@editGroup') @@ -361,7 +358,7 @@ describe('Inventory Groups', () => { cy.verifyPageTitle(group.name); cy.clickTab(/^Related Groups$/, true); cy.clickButton(/^Create group/); - cy.verifyPageTitle('Create new group'); + cy.verifyPageTitle('Create group'); cy.get('[data-cy="name-form-group"]').type(newRelatedGroup); cy.get('[data-cy="Submit"]').click(); cy.contains(newRelatedGroup); @@ -423,7 +420,7 @@ describe('Inventory Groups', () => { cy.clickModalButton(/^Close/); cy.intercept('POST', awxAPI`/hosts/`).as('createHost'); cy.clickButton(/^Create host$/); - cy.verifyPageTitle('Create Host'); + cy.verifyPageTitle('Create host'); cy.getByDataCy('name').type(newHostName); cy.getByDataCy('description').type('This is the description'); cy.clickButton(/^Create host$/); @@ -432,7 +429,7 @@ describe('Inventory Groups', () => { .then((response) => { expect(response?.statusCode).to.eql(201); }); - cy.verifyPageTitle('Host Details'); + cy.verifyPageTitle(newHostName); cy.clickTab(/^Back to Hosts$/, true); cy.filterTableBySingleSelect('name', newHostName); }); @@ -447,7 +444,7 @@ describe('Inventory Groups', () => { disableFilter: true, }); cy.intercept('PATCH', awxAPI`/hosts/*/`).as('editHost'); - cy.verifyPageTitle('Edit host'); + cy.verifyPageTitle(`Edit ${thisHost.name}`); cy.getByDataCy('name').type('-edited'); cy.getByDataCy('description').type('This is the description'); cy.clickButton(/^Save host$/); @@ -456,7 +453,7 @@ describe('Inventory Groups', () => { .then((response) => { expect(response?.statusCode).to.eql(200); }); - cy.verifyPageTitle('Host Details'); + cy.verifyPageTitle(`${thisHost.name}-edited`); cy.clickTab(/^Back to Hosts$/, true); cy.filterTableBySingleSelect('name', thisHost.name + '-edited'); }); diff --git a/cypress/e2e/awx/inventory-host/inventoryHostRegular.cy.ts b/cypress/e2e/awx/inventory-host/inventoryHostRegular.cy.ts index 40ac0a5d4e..29cca81531 100644 --- a/cypress/e2e/awx/inventory-host/inventoryHostRegular.cy.ts +++ b/cypress/e2e/awx/inventory-host/inventoryHostRegular.cy.ts @@ -61,7 +61,7 @@ describe('Inventory Host Tab Tests for regular inventory', () => { const hostName = 'E2E Inventory host ' + randomString(4); cy.navigateTo('awx', 'hosts'); cy.clickButton(/^Create host$/); - cy.verifyPageTitle('Create Host'); + cy.verifyPageTitle('Create host'); cy.getByDataCy('name').type(hostName); cy.getByDataCy('description').type('This is the description'); cy.singleSelectByDataCy('inventory', inventory.name); diff --git a/cypress/e2e/awx/workflow-approvals/wfApprovalsList.cy.ts b/cypress/e2e/awx/workflow-approvals/wfApprovalsList.cy.ts index 26fbe2dcf2..e2e722ff3d 100644 --- a/cypress/e2e/awx/workflow-approvals/wfApprovalsList.cy.ts +++ b/cypress/e2e/awx/workflow-approvals/wfApprovalsList.cy.ts @@ -429,8 +429,9 @@ describe('Workflow Approvals Tests', () => { function deleteApprovalFromListToolbar() { cy.get('tbody').find('tr').should('have.length', 3); cy.getByDataCy('select-all').click(); - cy.clickToolbarKebabAction('delete'); - + cy.getBy('[data-ouia-component-id="page-toolbar"]').within(() => { + cy.clickKebabAction('actions-dropdown', 'delete-workflow-approvals'); + }); cy.getModal().within(() => { cy.get('[data-ouia-component-id="confirm"]').click(); cy.get('[data-ouia-component-id="submit"]').click(); @@ -525,7 +526,7 @@ describe('Workflow Approvals Tests', () => { cy.getByDataCy('status-column-cell').should('contain', 'Approve'); cy.getByDataCy('checkbox-column-cell').click(); }); - cy.clickToolbarKebabAction('delete'); + cy.clickToolbarKebabAction('delete-workflow-approvals'); cy.getModal().within(() => { cy.get('[data-cy="alert-toaster"]').should( 'contain', diff --git a/cypress/e2e/hub/execution-environments.cy.ts b/cypress/e2e/hub/execution-environments.cy.ts index 1988bbe4f7..bba067453b 100644 --- a/cypress/e2e/hub/execution-environments.cy.ts +++ b/cypress/e2e/hub/execution-environments.cy.ts @@ -36,7 +36,7 @@ describe('Execution Environments', () => { }); }); - it('can add and delete a new execution environment', () => { + it('can add, edit, and delete a new execution environment', () => { cy.createHubRemoteRegistry().then((remoteRegistry) => { const eeName = `execution_environment_${randomString(3, undefined, { isLowercase: true })}`; const upstreamName = `upstream_name_${randomString(3, undefined, { isLowercase: true })}`; @@ -78,6 +78,25 @@ describe('Execution Environments', () => { cy.url().should('contain', '/execution-environments/'); cy.filterTableBySingleText(eeName); cy.get('tbody').find('tr').should('have.length', 1); + // edit ee + cy.get('tbody').within(() => { + cy.getByDataCy('name-column-cell').should('contain', eeName); + cy.get('[data-cy="edit-execution-environment"]').click(); + }); + cy.get('[data-cy="description"]').click().type('nice new description'); + cy.get('[data-cy="upstream-name"]').click().clear().type('pulp/pulp-fixtures/new'); + cy.getByDataCy('Submit').click(); + cy.url().should('contain', '/execution-environments/'); + cy.filterTableBySingleText(eeName); + cy.get('[data-cy="description-column-cell"]').should('contain', 'nice new description'); + cy.get('tbody').within(() => { + cy.getByDataCy('name-column-cell').should('contain', eeName); + cy.get('[data-cy="edit-execution-environment"]').click(); + }); + cy.get('[data-cy="upstream-name"]').click().clear().type(upstreamName); + cy.getByDataCy('Submit').click(); + // delete ee + cy.filterTableBySingleText(eeName); cy.get('tbody').within(() => { cy.getByDataCy('name-column-cell').should('contain', eeName); cy.get('[data-cy="actions-dropdown"]').click(); @@ -199,7 +218,7 @@ describe('Execution Environment Details tab', () => { cy.get('[data-cy="readme"]').contains('this should not be saved.').should('not.exist'); }); - it.skip('should successfully sync execution environment from Docker registry', () => { + it('should successfully sync execution environment from Docker registry', () => { cy.createHubRemoteRegistry().then((remoteRegistry) => { cy.createHubExecutionEnvironment({ executionEnvironment: { @@ -234,7 +253,7 @@ describe('Execution Environment Activity tab', () => { }); }); - it.skip('should display populated activity tab', () => { + it('should display populated activity tab', () => { cy.createHubRemoteRegistry().then((remoteRegistry) => { cy.createHubExecutionEnvironment({ executionEnvironment: { diff --git a/cypress/support/hostsfunctions.ts b/cypress/support/hostsfunctions.ts index 8f11aec1d6..93a51b1d3e 100644 --- a/cypress/support/hostsfunctions.ts +++ b/cypress/support/hostsfunctions.ts @@ -21,12 +21,12 @@ export function createAndCheckHost(host_type: string, inventory: string) { // create host cy.clickButton(/^Create host$/); - cy.verifyPageTitle('Create Host'); + cy.verifyPageTitle('Create host'); cy.getByDataCy('name').type(hostName); cy.getByDataCy('description').type('This is the description'); if (host_type === 'stand_alone_host') { - cy.singleSelectByDataCy('inventory', inventory); + cy.singleSelectByDataCy('inventory-form-group', inventory); } // after creation - verify data is currect @@ -55,19 +55,19 @@ export function createHost(host_type: string, inventoryID: number) { return hostName; } -function editHost(invenotryName: string, host_type: string, hostName: string, view: string) { +function editHost(inventoryName: string, host_type: string, hostName: string, view: string) { // function that editing host data from list or details views // this function cover both inventory host and stand alone host if (view === 'list') { - navigateToBaseView(host_type, invenotryName); + navigateToBaseView(host_type, inventoryName); cy.filterTableByMultiSelect('name', [hostName]); } else { // for details view - navigateToHost(host_type, hostName, '[data-cy="name-column-cell"] a', invenotryName); + navigateToHost(host_type, hostName, '[data-cy="name-column-cell"] a', inventoryName); } cy.getByDataCy('edit-host').click(); - cy.verifyPageTitle('Edit host'); + cy.verifyPageTitle(`Edit ${hostName}`); cy.getByDataCy('description').clear().type('This is the description edited'); cy.getByDataCy('Submit').click(); cy.hasDetail(/^Description$/, 'This is the description edited'); @@ -155,7 +155,7 @@ export function checkHostGroup(host_type: string, organization: Organization) { cy.clickLink(/^Groups$/); //check edit group cy.getByDataCy('edit-group').click(); - cy.verifyPageTitle('Edit group'); + cy.verifyPageTitle(`Edit ${group.name}`); cy.getByDataCy('name-form-group').type('-changed'); cy.getByDataCy('Submit').click(); cy.verifyPageTitle(group.name + '-changed'); @@ -271,7 +271,7 @@ export function createHostAndCancelJob( cy.navigateTo('awx', 'inventories'); cy.filterTableByMultiSelect('name', [inventory.name]); cy.get('[data-cy="name-column-cell"]').contains(inventory.name).click(); - cy.get('.pf-v5-c-tabs__item > a').contains('Job templates').click(); + cy.get('.pf-v5-c-tabs__item > a').contains('Job Templates').click(); // run a template and wait for redirect to Job output cy.get('[data-cy="launch-template"]').first().click(); cy.location('pathname').should('match', /\/output$/); @@ -317,7 +317,7 @@ export function launchHostJob( cy.navigateTo('awx', 'inventories'); cy.filterTableByMultiSelect('name', [inventory.name]); cy.get('[data-cy="name-column-cell"]').contains(inventory.name).click(); - cy.contains(`[role='tab']`, 'Job templates').click(); + cy.contains(`[role='tab']`, 'Job Templates').click(); // run a template and wait for request cy.intercept('POST', awxAPI`/job_templates/*/launch`).as('launch'); diff --git a/framework/PageEmptyStates/PageNotImplemented.tsx b/framework/PageEmptyStates/PageNotImplemented.tsx index 5394b4770e..171e0b16bb 100644 --- a/framework/PageEmptyStates/PageNotImplemented.tsx +++ b/framework/PageEmptyStates/PageNotImplemented.tsx @@ -1,7 +1,6 @@ import { Button, EmptyState, - EmptyStateBody, EmptyStateIcon, Stack, EmptyStateActions, @@ -22,7 +21,6 @@ export function PageNotImplemented() { icon={} headingLevel="h2" /> - {t('This page is not yet available in the tech preview.')} diff --git a/frontend/awx/access/users/UserPage/UserOrganizations.tsx b/frontend/awx/access/users/UserPage/UserOrganizations.tsx index 6a6c01ad5b..d30119586d 100644 --- a/frontend/awx/access/users/UserPage/UserOrganizations.tsx +++ b/frontend/awx/access/users/UserPage/UserOrganizations.tsx @@ -55,7 +55,7 @@ function UserOrganizationsInternal(props: { user: AwxUser }) { variant: ButtonVariant.primary, isPinned: true, icon: PlusCircleIcon, - label: t('Add user to organizations'), + label: t('Add users to organizations'), onClick: () => selectOrganizationsAddUsers([user]), }, { type: PageActionType.Seperator }, @@ -63,7 +63,7 @@ function UserOrganizationsInternal(props: { user: AwxUser }) { type: PageActionType.Button, selection: PageActionSelection.Multiple, icon: MinusCircleIcon, - label: t('Remove user from selected organizations'), + label: t('Remove users from organizations'), onClick: () => removeOrganizationsFromUsers([user], view.selectedItems, view.unselectItemsAndRefresh), isDanger: true, diff --git a/frontend/awx/access/users/UserPage/UserTeams.tsx b/frontend/awx/access/users/UserPage/UserTeams.tsx index 8cc4f40261..7707ac824f 100644 --- a/frontend/awx/access/users/UserPage/UserTeams.tsx +++ b/frontend/awx/access/users/UserPage/UserTeams.tsx @@ -56,7 +56,7 @@ function UserTeamsInternal(props: { user: AwxUser }) { variant: ButtonVariant.primary, isPinned: true, icon: PlusCircleIcon, - label: t('Add user to teams'), + label: t('Add users to teams'), isDisabled: canAddUserToTeam ? undefined : t( @@ -69,7 +69,7 @@ function UserTeamsInternal(props: { user: AwxUser }) { type: PageActionType.Button, selection: PageActionSelection.Multiple, icon: MinusCircleIcon, - label: t('Remove user from selected teams'), + label: t('Remove users from teams'), onClick: () => removeTeamsFromUsers([user], view.selectedItems), isDanger: true, }, diff --git a/frontend/awx/access/users/Users.cy.tsx b/frontend/awx/access/users/Users.cy.tsx index e9f90be9bd..9a65388b99 100644 --- a/frontend/awx/access/users/Users.cy.tsx +++ b/frontend/awx/access/users/Users.cy.tsx @@ -66,7 +66,7 @@ describe('Users.cy.ts', () => { .then((results: AwxUser[]) => { const user = results[0]; cy.selectTableRow(user.username, false); - cy.clickToolbarKebabAction('delete-selected-users'); + cy.clickToolbarKebabAction('delete-users'); cy.contains('Permanently delete users').should('be.visible'); }); }); diff --git a/frontend/awx/access/users/Users.tsx b/frontend/awx/access/users/Users.tsx index 9e19638e2a..a6b2e8f3dc 100644 --- a/frontend/awx/access/users/Users.tsx +++ b/frontend/awx/access/users/Users.tsx @@ -81,14 +81,14 @@ export function Users() { type: PageActionType.Button, selection: PageActionSelection.Multiple, icon: PlusCircleIcon, - label: t('Add selected users to teams'), + label: t('Add users to teams'), onClick: () => selectTeamsAddUsers(view.selectedItems), }, { type: PageActionType.Button, selection: PageActionSelection.Multiple, icon: MinusCircleIcon, - label: t('Remove selected users from teams'), + label: t('Remove users from teams'), onClick: () => selectTeamsRemoveUsers(view.selectedItems), }, { type: PageActionType.Seperator }, @@ -96,14 +96,14 @@ export function Users() { type: PageActionType.Button, selection: PageActionSelection.Multiple, icon: PlusCircleIcon, - label: t('Add selected users to organizations'), + label: t('Add users to organizations'), onClick: () => selectOrganizationsAddUsers(view.selectedItems), }, { type: PageActionType.Button, selection: PageActionSelection.Multiple, icon: MinusCircleIcon, - label: t('Remove selected users from organizations'), + label: t('Remove users from organizations'), onClick: () => selectOrganizationsRemoveUsers(view.selectedItems), }, { type: PageActionType.Seperator }, @@ -111,7 +111,7 @@ export function Users() { type: PageActionType.Button, selection: PageActionSelection.Multiple, icon: TrashIcon, - label: t('Delete selected users'), + label: t('Delete users'), onClick: deleteUsers, isDanger: true, }, diff --git a/frontend/awx/administration/instance-groups/ContainerGroupForm.cy.tsx b/frontend/awx/administration/instance-groups/ContainerGroupForm.cy.tsx index 400ee32d83..a8634a6996 100644 --- a/frontend/awx/administration/instance-groups/ContainerGroupForm.cy.tsx +++ b/frontend/awx/administration/instance-groups/ContainerGroupForm.cy.tsx @@ -18,7 +18,7 @@ describe('Create Edit Container Group Form', () => { }); it('should validate required fields on save', () => { cy.mount(); - cy.clickButton(/^Create Container Group$/); + cy.clickButton(/^Create container group$/); cy.contains('Name is required.').should('be.visible'); }); @@ -38,7 +38,7 @@ describe('Create Edit Container Group Form', () => { cy.get('[data-cy="max-concurrent-jobs"]').type('3'); cy.get('[data-cy="max-forks"]').clear(); cy.get('[data-cy="max-forks"]').type('4'); - cy.clickButton(/^Create Container Group$/); + cy.clickButton(/^Create container group$/); cy.wait('@createIG') .its('request.body') .then((createdIG) => { @@ -97,7 +97,7 @@ describe('Create Edit Container Group Form', () => { cy.get('[data-cy="max-forks"]').clear(); cy.get('[data-cy="max-forks"]').type('4'); - cy.clickButton(/^Save Container Group$/); + cy.clickButton(/^Save container group$/); cy.wait('@editCg') .its('request.body') .then((editedCg: ContainerGroup) => { diff --git a/frontend/awx/administration/instance-groups/ContainerGroupForm.tsx b/frontend/awx/administration/instance-groups/ContainerGroupForm.tsx index ac13a0c362..57790e2175 100644 --- a/frontend/awx/administration/instance-groups/ContainerGroupForm.tsx +++ b/frontend/awx/administration/instance-groups/ContainerGroupForm.tsx @@ -78,7 +78,7 @@ export function CreateContainerGroup() { ]} /> { }); it('should validate required fields on save', () => { cy.mount(); - cy.clickButton(/^Create Instance Group$/); + cy.clickButton(/^Create instance group$/); cy.contains('Name is required.').should('be.visible'); }); @@ -30,7 +30,7 @@ describe('Create Edit Instance Group Form', () => { cy.get('[data-cy="max-forks"]').clear(); cy.get('[data-cy="max-forks"]').type('4'); - cy.clickButton(/^Create Instance Group$/); + cy.clickButton(/^Create instance group$/); cy.wait('@createIG') .its('request.body') .then((createdIG) => { diff --git a/frontend/awx/administration/instance-groups/InstanceGroupForm.tsx b/frontend/awx/administration/instance-groups/InstanceGroupForm.tsx index ea4eedbc19..4c26a0522b 100644 --- a/frontend/awx/administration/instance-groups/InstanceGroupForm.tsx +++ b/frontend/awx/administration/instance-groups/InstanceGroupForm.tsx @@ -55,7 +55,7 @@ export function CreateInstanceGroup() { ]} /> ) { const healthCheckAction = useRunHealthCheckToolbarAction(view, true); @@ -41,7 +42,8 @@ function useIGInstanceAssociateToolbarAction(view: IAwxView) { type: PageActionType.Button, selection: PageActionSelection.None, variant: ButtonVariant.primary, - label: t('Associate'), + icon: PlusCircleIcon, + label: t('Associate instance'), isPinned: true, isDisabled: () => canAssociateInstance ? '' : t('You do not have permission to associate an instance.'), @@ -67,7 +69,7 @@ function useIGInstanceDisassociateToolbarAction(view: IAwxView) { type: PageActionType.Button, selection: PageActionSelection.Multiple, variant: ButtonVariant.primary, - label: t('Disassociate'), + label: t('Disassociate instance'), isPinned: true, onClick: disassociateInstance, isDisabled: (instances: Instance[]) => diff --git a/frontend/awx/administration/instance-groups/hooks/useInstanceGroupActions.tsx b/frontend/awx/administration/instance-groups/hooks/useInstanceGroupActions.tsx index 69b376bfb7..aedb5b2d9c 100644 --- a/frontend/awx/administration/instance-groups/hooks/useInstanceGroupActions.tsx +++ b/frontend/awx/administration/instance-groups/hooks/useInstanceGroupActions.tsx @@ -68,7 +68,7 @@ export function useInstanceGroupToolbarActions( type: PageActionType.Button, selection: PageActionSelection.Multiple, icon: TrashIcon, - label: t('Delete selected instance groups'), + label: t('Delete instance groups'), onClick: deleteInstanceGroups, isDanger: true, }, diff --git a/frontend/awx/administration/instances/InstanceForm.cy.tsx b/frontend/awx/administration/instances/InstanceForm.cy.tsx index f16b792eb1..e2ff22901a 100644 --- a/frontend/awx/administration/instances/InstanceForm.cy.tsx +++ b/frontend/awx/administration/instances/InstanceForm.cy.tsx @@ -5,7 +5,7 @@ describe('Add instance Form', () => { it('should validate required fields on save', () => { cy.mount(); cy.get('[data-cy="listener-port"]').type('0'); - cy.clickButton(/^Save$/); + cy.clickButton(/^Create instance$/); cy.contains('Host name is required.').should('be.visible'); }); @@ -16,7 +16,7 @@ describe('Add instance Form', () => { body: { hostname: ['whitespaces in hostnames are illegal'] }, }).as('postInstance'); cy.get('[data-cy="hostname"]').type('illegal hostname test'); - cy.clickButton(/^Save$/); + cy.clickButton(/^Create instance$/); cy.wait('@postInstance'); cy.contains('whitespaces in hostnames are illegal').should('be.visible'); }); @@ -34,7 +34,7 @@ describe('Add instance Form', () => { }, }).as('addInstance'); cy.get('[data-cy="hostname"]').type('AddInstanceMock'); - cy.clickButton(/^Save$/); + cy.clickButton(/^Create instance$/); cy.wait('@addInstance') .its('request.body') .then((instance: Instance) => { @@ -81,7 +81,7 @@ describe('Add instance Form', () => { cy.get('[data-cy="managed_by_policy"]').click(); cy.get('[data-cy="peers_from_control_nodes"]').click(); - cy.clickButton(/^Save$/); + cy.clickButton(/^Create instance$/); cy.wait('@addInstanceWithPeers') .its('request.body') .then((instance: Instance) => { diff --git a/frontend/awx/administration/instances/InstanceForm.tsx b/frontend/awx/administration/instances/InstanceForm.tsx index f51a8982d6..00a424e6ec 100644 --- a/frontend/awx/administration/instances/InstanceForm.tsx +++ b/frontend/awx/administration/instances/InstanceForm.tsx @@ -45,7 +45,7 @@ export function AddInstance() { ]} /> openPeerInstanceModal({ onPeer: associatePeerToInstance, instanceId: id ?? '' }), }, @@ -72,7 +72,7 @@ export function ResourcePeersList(props: { url: string }) { selection: PageActionSelection.Multiple, variant: ButtonVariant.primary, icon: MinusCircleIcon, - label: t('Disassociate'), + label: t('Disassociate peers'), onClick: (peers: Peer[]) => disassociatePeer(peers), isDanger: true, }, @@ -87,9 +87,9 @@ export function ResourcePeersList(props: { url: string }) { tableColumns={tableColumns} errorStateTitle={t('Error loading peers')} emptyStateTitle={t('No peers found')} - emptyStateDescription={t('Please add Peers to populate this list.')} + emptyStateDescription={t('Please associate peers to populate this list.')} emptyStateButtonIcon={} - emptyStateButtonText={t('Associate peer')} + emptyStateButtonText={t('Associate peers')} emptyStateButtonClick={() => openPeerInstanceModal({ onPeer: associatePeerToInstance, instanceId: id ?? '' }) } diff --git a/frontend/awx/administration/instances/Instances.cy.tsx b/frontend/awx/administration/instances/Instances.cy.tsx index 08bd3f4e24..e46731245b 100644 --- a/frontend/awx/administration/instances/Instances.cy.tsx +++ b/frontend/awx/administration/instances/Instances.cy.tsx @@ -32,7 +32,7 @@ describe('Instances list', () => { 'Ansible node instances dedicated for a particular purpose indicated by node type.' ); cy.get('[data-cy="actions-dropdown"]').click(); - cy.get('[data-cy="add-instance"]').should('be.visible'); + cy.get('[data-cy="create-instance"]').should('be.visible'); cy.get('[data-cy="remove-instance"]').should('be.visible'); cy.get('#remove-instance').should('have.attr', 'aria-disabled', 'true'); cy.get('tbody').find('tr').should('have.length', 10); @@ -51,7 +51,7 @@ describe('Instances list', () => { 'contain', 'Ansible node instances dedicated for a particular purpose indicated by node type.' ); - cy.get('[data-cy="add-instance"]').should('not.exist'); + cy.get('[data-cy="create-instance"]').should('not.exist'); cy.get('[data-cy="actions-dropdown"]').click(); cy.get('[data-cy="remove-instance"]').should('not.exist'); cy.get('tbody').find('tr').should('have.length', 10); diff --git a/frontend/awx/administration/instances/hooks/useDisassociatePeer.tsx b/frontend/awx/administration/instances/hooks/useDisassociatePeer.tsx index 2e9f684cc8..f511ba537e 100644 --- a/frontend/awx/administration/instances/hooks/useDisassociatePeer.tsx +++ b/frontend/awx/administration/instances/hooks/useDisassociatePeer.tsx @@ -38,7 +38,7 @@ export function useDisassociatePeer(onComplete: (peers: Peer[]) => void, instanc : t('Yes, I confirm that I want to disassociate these {{count}} peers.', { count: peersToRemove.length, }), - actionButtonText: t('Disassociate peer', { count: peersToRemove.length }), + actionButtonText: t('Disassociate peers', { count: peersToRemove.length }), items: peersToRemove.sort((l, r) => compareStrings(l.name, r.name)), keyFn: getItemKey, isDanger: true, diff --git a/frontend/awx/administration/instances/hooks/useInstanceRowActions.tsx b/frontend/awx/administration/instances/hooks/useInstanceRowActions.tsx index bbe89dd2f9..e1c62e7bb8 100644 --- a/frontend/awx/administration/instances/hooks/useInstanceRowActions.tsx +++ b/frontend/awx/administration/instances/hooks/useInstanceRowActions.tsx @@ -56,8 +56,8 @@ export function useToggleInstanceRowAction(onComplete: (instances: Instance[]) = isPinned: true, onToggle: (instance, enabled) => handleToggleInstance(instance, enabled), isSwitchOn: (instance) => instance.enabled, - label: t('Enabled'), - labelOff: t('Disabled'), + label: t('Instance enabled'), + labelOff: t('Instance disabled'), showPinnedLabel: false, isHidden: (instance) => instance.node_type === 'hop', isDisabled: (_instance) => diff --git a/frontend/awx/administration/instances/hooks/useInstanceToolbarActions.tsx b/frontend/awx/administration/instances/hooks/useInstanceToolbarActions.tsx index 90116f08d1..51a2a67d8b 100644 --- a/frontend/awx/administration/instances/hooks/useInstanceToolbarActions.tsx +++ b/frontend/awx/administration/instances/hooks/useInstanceToolbarActions.tsx @@ -123,7 +123,7 @@ export function useAddInstanceToolbarAction() { variant: ButtonVariant.primary, isPinned: true, icon: PlusCircleIcon, - label: t('Add instance'), + label: t('Create instance'), onClick: () => pageNavigate(AwxRoute.AddInstance), isDisabled: canAddAndEditInstances ? undefined diff --git a/frontend/awx/administration/instances/hooks/useSelectAssociatePeers.tsx b/frontend/awx/administration/instances/hooks/useSelectAssociatePeers.tsx index 2ebad45c3f..d01a47f618 100644 --- a/frontend/awx/administration/instances/hooks/useSelectAssociatePeers.tsx +++ b/frontend/awx/administration/instances/hooks/useSelectAssociatePeers.tsx @@ -74,12 +74,12 @@ function PeerInstanceModal(props: PeerInstanceModalProps) { return ( ); } diff --git a/frontend/awx/administration/workflow-approvals/hooks/useWorkflowApprovalToolbarActions.tsx b/frontend/awx/administration/workflow-approvals/hooks/useWorkflowApprovalToolbarActions.tsx index e4e2507a29..b4f8e30ec9 100644 --- a/frontend/awx/administration/workflow-approvals/hooks/useWorkflowApprovalToolbarActions.tsx +++ b/frontend/awx/administration/workflow-approvals/hooks/useWorkflowApprovalToolbarActions.tsx @@ -41,7 +41,7 @@ export function useWorkflowApprovalToolbarActions(view: IAwxView cannotDeny(workflow_approval), isHidden: (workflow_approval: WorkflowApproval) => diff --git a/frontend/awx/overview/AwxOverview.tsx b/frontend/awx/overview/AwxOverview.tsx index 6666596417..35d54f8991 100644 --- a/frontend/awx/overview/AwxOverview.tsx +++ b/frontend/awx/overview/AwxOverview.tsx @@ -1,14 +1,11 @@ /* eslint-disable i18next/no-literal-string */ -import { Banner, Bullseye, Button, PageSection, Spinner } from '@patternfly/react-core'; -import { CogIcon, InfoCircleIcon } from '@patternfly/react-icons'; -import { useEffect } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; +import { Bullseye, Button, PageSection, Spinner } from '@patternfly/react-core'; +import { CogIcon } from '@patternfly/react-icons'; +import { useTranslation } from 'react-i18next'; import useSWR from 'swr'; -import { PageHeader, PageLayout, usePageDialog } from '../../../framework'; +import { PageHeader, PageLayout } from '../../../framework'; import { PageDashboard } from '../../../framework/PageDashboard/PageDashboard'; import { awxAPI } from '../common/api/awx-utils'; -import { useAwxConfig } from '../common/useAwxConfig'; -import { WelcomeModal } from './WelcomeModal'; import { AwxCountsCard } from './cards/AwxCountsCard'; import { AwxJobActivityCard } from './cards/AwxJobActivityCard'; import { AwxRecentInventoriesCard } from './cards/AwxRecentInventoriesCard'; @@ -16,17 +13,12 @@ import { AwxRecentJobsCard } from './cards/AwxRecentJobsCard'; import { AwxRecentProjectsCard } from './cards/AwxRecentProjectsCard'; import { useManagedAwxDashboard } from './hooks/useManagedAwxDashboard'; -const HIDE_WELCOME_MESSAGE = 'hide-welcome-message'; type Resource = { id: string; name: string }; export function AwxOverview() { const { t } = useTranslation(); const { openManageDashboard, managedResources } = useManagedAwxDashboard(); const product: string = process.env.PRODUCT ?? t('AWX'); - const config = useAwxConfig(); - const [_, setDialog] = usePageDialog(); - const welcomeMessageSetting = localStorage.getItem(HIDE_WELCOME_MESSAGE); - const hideWelcomeMessage = welcomeMessageSetting ? welcomeMessageSetting === 'true' : false; function renderCustomizeControls() { return ( ); } - useEffect(() => { - if (config?.ui_next && !hideWelcomeMessage) { - setDialog(); - } - }, [config?.ui_next, hideWelcomeMessage, setDialog]); return ( - {config?.ui_next && ( - -

- {' '} - - You are currently viewing a tech preview of the new {{ product }} user interface. To - return to the original interface, click here. - -

-
- )} { - setDialog(undefined); - }, [setDialog]); - const [isChecked, setIsChecked] = useState(false); - - const handleChange = (checked: boolean) => { - setIsChecked(checked); - localStorage.setItem(HIDE_WELCOME_MESSAGE, JSON.stringify(checked)); - }; - - return ( - { - onClose(); - }} - aria-label={t`Close`} - > - {t(`Close`)} - , - ]} - > - - {t( - `The new interface has been updated to provide a consistent and clean user experience. As a tech preview, not all areas within the new UI are immediately available. You can currently perform basic tasks such as create a job template and view a completed job run.` - )} - -
- - handleChange(checked)} - name="do-not-show-welcome-modal" - id="welcome-modal-checkbox" - /> - -
- ); -} diff --git a/frontend/awx/resources/inventories/InventoryForm.tsx b/frontend/awx/resources/inventories/InventoryForm.tsx index dd4daa12be..99f5045847 100644 --- a/frontend/awx/resources/inventories/InventoryForm.tsx +++ b/frontend/awx/resources/inventories/InventoryForm.tsx @@ -98,10 +98,10 @@ export function CreateInventory(props: { inventoryKind: '' | 'constructed' | 'sm const getPageUrl = useGetPageUrl(); const title = inventoryKind === '' - ? t('Create Inventory') + ? t('Create inventory') : inventoryKind === 'smart' - ? t('Create Smart Inventory') - : t('Create Constructed Inventory'); + ? t('Create smart inventory') + : t('Create constructed inventory'); const defaultValue = inventoryKind === 'smart' @@ -257,13 +257,6 @@ export function EditInventory() { ); } - const title = - inventory.kind === '' - ? t('Edit Inventory') - : inventory.kind === 'smart' - ? t('Edit Smart Inventory') - : t('Edit Constructed Inventory'); - const defaultValue = inventory.kind === 'smart' ? { ...inventory, instanceGroups: originalInstanceGroups } @@ -282,10 +275,18 @@ export function EditInventory() { return ( diff --git a/frontend/awx/resources/inventories/InventoryPage/InventoryPage.tsx b/frontend/awx/resources/inventories/InventoryPage/InventoryPage.tsx index 98abae390b..8b2552c2d9 100644 --- a/frontend/awx/resources/inventories/InventoryPage/InventoryPage.tsx +++ b/frontend/awx/resources/inventories/InventoryPage/InventoryPage.tsx @@ -139,7 +139,7 @@ export function InventoryPage() { !isSmartInventory && !isConstructedInventory && { label: t('Sources'), page: AwxRoute.InventorySources }, { label: t('Jobs'), page: AwxRoute.InventoryJobs }, - { label: t('Job templates'), page: AwxRoute.InventoryJobTemplates }, + { label: t('Job Templates'), page: AwxRoute.InventoryJobTemplates }, ]} params={params} componentParams={{ inventory }} diff --git a/frontend/awx/resources/inventories/inventoryGroup/InventoryGroupForm.cy.tsx b/frontend/awx/resources/inventories/inventoryGroup/InventoryGroupForm.cy.tsx index 98712a9bc8..cb9812aae1 100644 --- a/frontend/awx/resources/inventories/inventoryGroup/InventoryGroupForm.cy.tsx +++ b/frontend/awx/resources/inventories/inventoryGroup/InventoryGroupForm.cy.tsx @@ -21,7 +21,7 @@ describe('CreateGroup', () => { .its('results') .should('be.an', 'array') .then(() => { - cy.contains('Create new group'); + cy.contains('Create group'); }); }); diff --git a/frontend/awx/resources/inventories/inventoryGroup/InventoryGroupForm.tsx b/frontend/awx/resources/inventories/inventoryGroup/InventoryGroupForm.tsx index 948ef655e8..bbc6afbc43 100644 --- a/frontend/awx/resources/inventories/inventoryGroup/InventoryGroupForm.tsx +++ b/frontend/awx/resources/inventories/inventoryGroup/InventoryGroupForm.tsx @@ -26,7 +26,7 @@ interface GroupFormPageHeaderProps { interface BreadCrumbLink { label: string; - to: string; + to?: string; } type BreadCrumbs = Record; @@ -66,7 +66,7 @@ function GroupFormPageHeader(props: GroupFormPageHeaderProps) { }), }, relatedGroups: { - label: t('Related groups'), + label: t('Related Groups'), to: getPageUrl(AwxRoute.InventoryGroupRelatedGroups, { params: { ...breadcrumbsParams.id, @@ -75,6 +75,8 @@ function GroupFormPageHeader(props: GroupFormPageHeaderProps) { }, }), }, + createGroup: { label: t('Create group') }, + editGroup: { label: t('Edit {{groupName}}', { groupName: props.groupName }) }, }; return ( @@ -97,12 +99,17 @@ export function CreateGroup() { if (error) return ; if (!inventory) return ; - const breadcrumbs: Array = ['inventories', 'inventory', 'groups']; + const breadcrumbs: Array = [ + 'inventories', + 'inventory', + 'groups', + 'createGroup', + ]; return ( ; if (!group) return ; - const breadcrumbs: Array = ['inventories', 'inventory', 'groups', 'group']; + const breadcrumbs: Array = ['inventories', 'inventory', 'groups', 'editGroup']; return ( - + - + submitText={t('Save host')} onSubmit={onSubmit} diff --git a/frontend/awx/resources/inventories/inventoryHostsPage/InventoryHostPage.tsx b/frontend/awx/resources/inventories/inventoryHostsPage/InventoryHostPage.tsx index d9947292b0..dbdd1e7ac7 100644 --- a/frontend/awx/resources/inventories/inventoryHostsPage/InventoryHostPage.tsx +++ b/frontend/awx/resources/inventories/inventoryHostsPage/InventoryHostPage.tsx @@ -52,7 +52,7 @@ export function InventoryHostPage() { return ( { it('renders create new source page', () => { cy.mount(); - cy.contains('Add new source'); + cy.contains('Create source'); }); it('disables save button when name is empty', () => { diff --git a/frontend/awx/resources/sources/InventorySourceForm.tsx b/frontend/awx/resources/sources/InventorySourceForm.tsx index 5d8fc63633..72c0fcfe8b 100644 --- a/frontend/awx/resources/sources/InventorySourceForm.tsx +++ b/frontend/awx/resources/sources/InventorySourceForm.tsx @@ -79,7 +79,7 @@ export function CreateInventorySource() { return ( ({ value: resource.id as PathValue, - description: resource.description, + description: resource?.description + ? resource.description.slice( + 0, + resource.description.indexOf('.') || resource.description.length + ) + : '', label: resource.name, })) ?? [], next: response.count, diff --git a/frontend/eda/event-streams/EventStreamForm.tsx b/frontend/eda/event-streams/EventStreamForm.tsx index 2586a08c80..2eecd9b036 100644 --- a/frontend/eda/event-streams/EventStreamForm.tsx +++ b/frontend/eda/event-streams/EventStreamForm.tsx @@ -85,11 +85,13 @@ function EventStreamInputs() { label={t('Event stream type')} /> - - isRequired - name="eda_credential_id" - type={eventStreamType?.kind || ''} - /> + {eventStreamType && ( + + isRequired + name="eda_credential_id" + type={eventStreamType?.kind || ''} + /> + )} name="additional_data_headers" data-cy="additional_data_headers-form-field" diff --git a/frontend/eda/projects/ProjectPage/ProjectPage.tsx b/frontend/eda/projects/ProjectPage/ProjectPage.tsx index 0ba0eb8b52..d338195f87 100644 --- a/frontend/eda/projects/ProjectPage/ProjectPage.tsx +++ b/frontend/eda/projects/ProjectPage/ProjectPage.tsx @@ -11,7 +11,6 @@ import { PageActions, PageHeader, PageLayout, - errorToAlertProps, useGetPageUrl, usePageAlertToaster, usePageNavigate, @@ -26,12 +25,14 @@ import { EdaRoute } from '../../main/EdaRoutes'; import { useDeleteProjects } from '../hooks/useDeleteProjects'; import { useOptions } from '../../../common/crud/useOptions'; import { ActionsResponse, OptionsResponse } from '../../interfaces/OptionsResponse'; +import { useEdaErrorMessageParser } from '../../common/edaErrorAdapter'; export function ProjectPage() { const { t } = useTranslation(); const params = useParams<{ id: string }>(); const pageNavigate = usePageNavigate(); const getPageUrl = useGetPageUrl(); + const parseError = useEdaErrorMessageParser(); const alertToaster = usePageAlertToaster(); const { data } = useOptions>( edaAPI`/projects/${params.id ?? ''}/` @@ -49,9 +50,17 @@ export function ProjectPage() { timeout: 5000, }); }) - .catch((err) => alertToaster.addAlert(errorToAlertProps(err))) + .catch((err: Error) => { + const errorResults = parseError(err); + alertToaster.addAlert({ + variant: 'danger', + title: `${t('Failed to sync')} ${project.name}`, + children: <>{errorResults.parsedErrors.map((errorResult) => errorResult.message)}, + timeout: 5000, + }); + }) .finally(() => refresh()), - [alertToaster, refresh, t] + [alertToaster, refresh, parseError, t] ); const deleteProjects = useDeleteProjects((deleted) => { if (deleted.length > 0) { diff --git a/frontend/eda/projects/hooks/useProjectActions.tsx b/frontend/eda/projects/hooks/useProjectActions.tsx index 97e4bd6973..5601d0a050 100644 --- a/frontend/eda/projects/hooks/useProjectActions.tsx +++ b/frontend/eda/projects/hooks/useProjectActions.tsx @@ -6,7 +6,6 @@ import { IPageAction, PageActionSelection, PageActionType, - errorToAlertProps, usePageAlertToaster, usePageNavigate, } from '../../../../framework'; @@ -17,10 +16,12 @@ import { EdaProject } from '../../interfaces/EdaProject'; import { ImportStateEnum } from '../../interfaces/generated/eda-api'; import { EdaRoute } from '../../main/EdaRoutes'; import { useDeleteProjects } from './useDeleteProjects'; +import { useEdaErrorMessageParser } from '../../common/edaErrorAdapter'; export function useProjectActions(view: IEdaView) { const { t } = useTranslation(); const pageNavigate = usePageNavigate(); + const parseError = useEdaErrorMessageParser(); const deleteProjects = useDeleteProjects(view.unselectItemsAndRefresh); const alertToaster = usePageAlertToaster(); const syncProject = useCallback( @@ -34,8 +35,16 @@ export function useProjectActions(view: IEdaView) { }); view.unselectItemsAndRefresh([project]); }) - .catch((err) => alertToaster.addAlert(errorToAlertProps(err))), - [alertToaster, view, t] + .catch((err: Error) => { + const errorResults = parseError(err); + alertToaster.addAlert({ + variant: 'danger', + title: `${t('Failed to sync')} ${project.name}`, + children: <>{errorResults.parsedErrors.map((errorResult) => errorResult.message)}, + timeout: 5000, + }); + }), + [alertToaster, view, parseError, t] ); return useMemo[]>( () => [ diff --git a/frontend/eda/rulebook-activations/RulebookActivationPage/RulebookActivationPage.tsx b/frontend/eda/rulebook-activations/RulebookActivationPage/RulebookActivationPage.tsx index 66db54c9eb..4f27c4fde3 100644 --- a/frontend/eda/rulebook-activations/RulebookActivationPage/RulebookActivationPage.tsx +++ b/frontend/eda/rulebook-activations/RulebookActivationPage/RulebookActivationPage.tsx @@ -27,6 +27,7 @@ import { useRestartRulebookActivations, } from '../hooks/useControlRulebookActivations'; import { useDeleteRulebookActivations } from '../hooks/useDeleteRulebookActivations'; +import { useEdaErrorMessageParser } from '../../common/edaErrorAdapter'; export function RulebookActivationPage() { const { t } = useTranslation(); @@ -34,6 +35,7 @@ export function RulebookActivationPage() { const pageNavigate = usePageNavigate(); const getPageUrl = useGetPageUrl(); const alertToaster = usePageAlertToaster(); + const parseError = useEdaErrorMessageParser(); const { data: rulebookActivation, refresh } = useGet( edaAPI`/activations/${params.id ?? ''}/` @@ -67,16 +69,18 @@ export function RulebookActivationPage() { }; await postRequest(edaAPI`/activations/${activation.id.toString()}/enable/`, undefined) .then(() => alertToaster.addAlert(alert)) - .catch(() => { + .catch((err: Error) => { + const errorResults = parseError(err); alertToaster.addAlert({ variant: 'danger', title: `${t('Failed to enable')} ${activation.name}`, + children: <>{errorResults.parsedErrors.map((errorResult) => errorResult.message)}, timeout: 5000, }); }); refresh(); }, - [alertToaster, refresh, t] + [alertToaster, refresh, parseError, t] ); const isActionTab = location.href.includes( diff --git a/frontend/eda/rulebook-activations/hooks/useControlRulebookActivations.tsx b/frontend/eda/rulebook-activations/hooks/useControlRulebookActivations.tsx index 38a0734a7a..94b1b226d0 100644 --- a/frontend/eda/rulebook-activations/hooks/useControlRulebookActivations.tsx +++ b/frontend/eda/rulebook-activations/hooks/useControlRulebookActivations.tsx @@ -7,12 +7,14 @@ import { edaAPI } from '../../common/eda-utils'; import { EdaRulebookActivation } from '../../interfaces/EdaRulebookActivation'; import { useRulebookActivationColumns } from './useRulebookActivationColumns'; import { useEdaBulkConfirmation } from '../../common/useEdaBulkConfirmation'; +import { useEdaErrorMessageParser } from '../../common/edaErrorAdapter'; export function useEnableRulebookActivations( onComplete: (rulebookActivations: EdaRulebookActivation[]) => void ) { const { t } = useTranslation(); const postRequest = usePostRequest(); + const parseError = useEdaErrorMessageParser(); const alertToaster = usePageAlertToaster(); return useCallback( async (rulebookActivations: EdaRulebookActivation[]) => { @@ -28,10 +30,14 @@ export function useEnableRulebookActivations( undefined ) .then(() => alertToaster.addAlert(alert)) - .catch(() => { + .catch((err: Error) => { + const errorResults = parseError(err); alertToaster.addAlert({ variant: 'danger', title: `${t('Failed to enable')} ${activation.name}`, + children: ( + <>{errorResults.parsedErrors.map((errorResult) => errorResult.message)} + ), timeout: 5000, }); }); @@ -39,7 +45,7 @@ export function useEnableRulebookActivations( ); onComplete(rulebookActivations); }, - [alertToaster, onComplete, postRequest, t] + [alertToaster, onComplete, postRequest, parseError, t] ); } diff --git a/frontend/eda/rulebook-activations/hooks/useRulebookActivationActions.tsx b/frontend/eda/rulebook-activations/hooks/useRulebookActivationActions.tsx index 1f97dd33d4..52415b1f3c 100644 --- a/frontend/eda/rulebook-activations/hooks/useRulebookActivationActions.tsx +++ b/frontend/eda/rulebook-activations/hooks/useRulebookActivationActions.tsx @@ -18,6 +18,7 @@ import { useRestartRulebookActivations, } from './useControlRulebookActivations'; import { useDeleteRulebookActivations } from './useDeleteRulebookActivations'; +import { useEdaErrorMessageParser } from '../../common/edaErrorAdapter'; export function useRulebookActivationActions(view: IEdaView) { const { t } = useTranslation(); @@ -25,6 +26,7 @@ export function useRulebookActivationActions(view: IEdaView Promise = useCallback( async (activation) => { const alert: AlertProps = { @@ -34,16 +36,18 @@ export function useRulebookActivationActions(view: IEdaView alertToaster.addAlert(alert)) - .catch(() => { + .catch((err: Error) => { + const errorResults = parseError(err); alertToaster.addAlert({ variant: 'danger', title: `${t('Failed to enable')} ${activation.name}`, + children: <>{errorResults.parsedErrors.map((errorResult) => errorResult.message)}, timeout: 5000, }); }); view.unselectItemsAndRefresh([activation]); }, - [view, alertToaster, t] + [view, alertToaster, parseError, t] ); return useMemo[]>(() => { diff --git a/frontend/eda/rulebook-activations/hooks/useRulebookActivationsActions.tsx b/frontend/eda/rulebook-activations/hooks/useRulebookActivationsActions.tsx index b4e6684bb3..54bac4c678 100644 --- a/frontend/eda/rulebook-activations/hooks/useRulebookActivationsActions.tsx +++ b/frontend/eda/rulebook-activations/hooks/useRulebookActivationsActions.tsx @@ -21,6 +21,7 @@ import { import { useDeleteRulebookActivations } from './useDeleteRulebookActivations'; import { useOptions } from '../../../common/crud/useOptions'; import { ActionsResponse, OptionsResponse } from '../../interfaces/OptionsResponse'; +import { useEdaErrorMessageParser } from '../../common/edaErrorAdapter'; export function useRulebookActivationsActions(view: IEdaView) { const { t } = useTranslation(); @@ -28,6 +29,7 @@ export function useRulebookActivationsActions(view: IEdaView>(edaAPI`/activations/`); const canCreateActivations = Boolean(data && data.actions && data.actions['POST']); const alertToaster = usePageAlertToaster(); @@ -42,15 +44,17 @@ export function useRulebookActivationsActions(view: IEdaView alertToaster.addAlert(alert)) - .catch(() => { + .catch((err: Error) => { + const errorResults = parseError(err); alertToaster.addAlert({ variant: 'danger', title: `${t('Failed to enable')} ${activation.name}`, + children: <>{errorResults.parsedErrors.map((errorResult) => errorResult.message)}, timeout: 5000, }); }); }, - [alertToaster, t] + [alertToaster, parseError, t] ); const enableRulebookActivations = useCallback( (activations: EdaRulebookActivation[]) => {