diff --git a/src/api/collection.ts b/src/api/collection.ts index eb92f11f5d..7175050c8c 100644 --- a/src/api/collection.ts +++ b/src/api/collection.ts @@ -169,7 +169,7 @@ export class API extends HubAPI { name, version, }, - `pulp/api/v3/content/ansible/collection_versions/`, + 'pulp/api/v3/content/ansible/collection_versions/', ); } diff --git a/src/components/ansible-repository-form.tsx b/src/components/ansible-repository-form.tsx index 9dea3dc82c..599ba7be8c 100644 --- a/src/components/ansible-repository-form.tsx +++ b/src/components/ansible-repository-form.tsx @@ -7,12 +7,12 @@ import { FormGroup, TextInput, } from '@patternfly/react-core'; -import { Select, SelectOption } from '@patternfly/react-core/deprecated'; import React, { useEffect, useState } from 'react'; import { AnsibleRemoteAPI, type AnsibleRepositoryType } from 'src/api'; import { FormFieldHelper, HelpButton, + HubSelect, LazyDistributions, PulpLabels, Spinner, @@ -150,12 +150,11 @@ export const AnsibleRepositoryForm = ({ : 'none', ); - const [selectOpen, setSelectOpen] = useState(false); - const selectPipeline = (value) => { if (disableHideFromSearch && value !== 'staging') { setHideFromSearch(repository?.pulp_labels?.hide_from_search === ''); } + if (value === 'staging') { setSelectedPipeline(value); setPipeline(value); @@ -170,13 +169,12 @@ export const AnsibleRepositoryForm = ({ setPipeline(null); setDisableHideFromSearch(false); } - setSelectOpen(false); }; const selectOptions = { - staging: { id: 'staging', toString: () => t`Staging` }, - approved: { id: 'approved', toString: () => t`Approved` }, - none: { id: 'none', toString: () => t`None` }, + staging: t`Staging`, + approved: t`Approved`, + none: t`None`, }; const pipelineHelp = ( @@ -252,17 +250,11 @@ export const AnsibleRepositoryForm = ({ t`Pipeline`, pipelineHelp,
- +
, )} diff --git a/src/components/detail-list.tsx b/src/components/detail-list.tsx index d8b79d229e..bb4750cd7c 100644 --- a/src/components/detail-list.tsx +++ b/src/components/detail-list.tsx @@ -114,7 +114,7 @@ export function DetailList({ ) : ( <> -
+
diff --git a/src/components/hub-select.tsx b/src/components/hub-select.tsx new file mode 100644 index 0000000000..725587d886 --- /dev/null +++ b/src/components/hub-select.tsx @@ -0,0 +1,50 @@ +import { + MenuToggle, + type MenuToggleElement, + Select, + SelectOption, +} from '@patternfly/react-core'; +import React, { type Ref, useState } from 'react'; + +// single select component - render options object in order, onSelect(option key), selected=option key +export const HubSelect = ({ + onSelect, + options, + selected, +}: { + onSelect: (value) => void; + options: Record; + selected: string; +}) => { + const [isOpen, setOpen] = useState(false); + + const toggle = (toggleRef: Ref) => ( + setOpen(!isOpen)} + ref={toggleRef} + > + {options[selected]} + + ); + + return ( + + ); +}; diff --git a/src/components/index.ts b/src/components/index.ts index 3b07723c0b..67ca5d622e 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -49,6 +49,7 @@ export { HubAboutModal } from './hub-about-modal'; export { HubCopyButton } from './hub-copy-button'; export { HubListToolbar } from './hub-list-toolbar'; export { HubPagination } from './hub-pagination'; +export { HubSelect } from './hub-select'; export { ImportConsole } from './import-console'; export { ImportList } from './import-list'; export { ImportModal } from './import-modal'; diff --git a/src/components/typeahead.tsx b/src/components/typeahead.tsx index f854f1ea49..4798fc17b9 100644 --- a/src/components/typeahead.tsx +++ b/src/components/typeahead.tsx @@ -1,121 +1,192 @@ import { t } from '@lingui/macro'; import { + Button, + MenuToggle, + type MenuToggleElement, Select, + SelectList, SelectOption, - SelectVariant, -} from '@patternfly/react-core/deprecated'; -import React, { type CSSProperties, Component, type ReactElement } from 'react'; + TextInputGroup, + TextInputGroupMain, + TextInputGroupUtilities, +} from '@patternfly/react-core'; +import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; +import React, { + type CSSProperties, + type ReactElement, + type Ref, + useState, +} from 'react'; +import { Chip, ChipGroup } from 'src/components'; interface IProps { - results: { name: string; id: number | string }[]; + isDisabled?: boolean; loadResults: (filter: string) => void; - onSelect: (event, selection, isPlaceholder) => void; - - selections?: { name: string; id: number | string }[]; + menuAppendTo?: 'parent' | 'inline'; multiple?: boolean; - placeholderText?: string; onClear?: () => void; - - isDisabled?: boolean; - endLink?: { - href: string; - name: string; - }; - menuAppendTo?: 'parent' | 'inline'; - toggleIcon?: ReactElement; + onSelect: (event, selection, isPlaceholder) => void; + placeholderText?: string; + results: { name: string; id: number | string }[]; + selections?: { name: string; id: number | string }[]; style?: CSSProperties; + toggleIcon?: ReactElement; } -interface IState { - isOpen: boolean; -} - -export class Typeahead extends Component { - constructor(props) { - super(props); - - this.state = { - isOpen: false, - }; - } - - render() { - let selected = null; - if (this.props.selections) { - selected = this.props.selections.map((group) => group.name); - } - - const { isOpen } = this.state; - const variant = this.props.multiple - ? SelectVariant.typeaheadMulti - : SelectVariant.typeahead; +export const Typeahead = ({ + isDisabled, + loadResults, + menuAppendTo, + multiple, + onClear, + onSelect, + placeholderText, + results, + selections, + style, + toggleIcon, +}: IProps) => { + const [isOpen, setOpen] = useState(false); + const selected = selections?.map((group) => group.name) || null; + + const toggle = (toggleRef: Ref) => { + const isEmpty = multiple ? !selected.length : !inputValue; return ( - + + + {multiple ? ( + + {selected.map((selection, index) => ( + { + ev.stopPropagation(); + onSelect(selection); + }} + > + {selection} + + ))} + + ) : null} + + + {!isEmpty ? ( + + ) : null} + + + ); - } - - private onClear = () => { - this.props.loadResults(''); - if (this.props.onClear) { - this.props.onClear(); - } - }; - - private getOptions() { - const options = this.props.results.map(({ id, name }) => ( - - )); - - if (options.length === 0) { - options.push( - , - ); - } - - return options; - } - - private onFilter = (evt) => { - if (evt !== null) { - const textInput = evt.target.value; - this.props.loadResults(textInput); - } - return this.getOptions(); - }; - - private onToggle = (isOpen) => { - this.setState({ - isOpen, - }); }; - private onSelect = (event, selection, isPlaceholder) => { - this.props.onSelect(event, selection, isPlaceholder); - - if (!this.props.multiple) { - this.setState( - { - isOpen: false, - }, - () => this.props.loadResults(''), - ); - } - }; -} + return ( + + ); + + return ( + + ); +}; diff --git a/src/containers/ansible-role/imports.tsx b/src/containers/ansible-role/imports.tsx index 4104cc2fca..f9f3139ba5 100644 --- a/src/containers/ansible-role/imports.tsx +++ b/src/containers/ansible-role/imports.tsx @@ -158,7 +158,7 @@ class AnsibleRoleImports extends Component { { }); cy.galaxykit('-i task wait all'); range(1, max).forEach((i) => { - cy.galaxykit(`-i repository create`, 'repo' + i, '--pipeline=approved'); + cy.galaxykit('-i repository create', 'repo' + i, '--pipeline=approved'); cy.galaxykit('-i distribution create', 'repo' + i); }); cy.galaxykit('-i task wait all'); diff --git a/test/cypress/e2e/approval-modal/approval-multiple-repos.js b/test/cypress/e2e/approval-modal/approval-multiple-repos.js index c4af2548f3..6db400a8de 100644 --- a/test/cypress/e2e/approval-modal/approval-multiple-repos.js +++ b/test/cypress/e2e/approval-modal/approval-multiple-repos.js @@ -41,7 +41,7 @@ function rejectItem(repo) { cy.visit(`${uiPrefix}approval-dashboard`); cy.contains('Clear all filters').click(); cy.contains( - `[data-cy="ApprovalRow-rejected-namespace-collection1"]`, + '[data-cy="ApprovalRow-rejected-namespace-collection1"]', 'Rejected', ); } @@ -82,7 +82,7 @@ describe('Approval Dashboard process with multiple repos', () => { }); cy.galaxykit('-i task wait all'); range(1, max).forEach((i) => { - cy.galaxykit(`-i repository create`, 'repo' + i, '--pipeline=approved'); + cy.galaxykit('-i repository create', 'repo' + i, '--pipeline=approved'); cy.galaxykit('-i distribution create', 'repo' + i); }); cy.galaxykit('-i task wait all'); diff --git a/test/cypress/e2e/collections/collection.js b/test/cypress/e2e/collections/collection.js index c19a984a63..3042414cde 100644 --- a/test/cypress/e2e/collections/collection.js +++ b/test/cypress/e2e/collections/collection.js @@ -175,13 +175,13 @@ describe('collection tests', () => { cy.visit( `${uiPrefix}repo/repo2/test_namespace/test_repo_collection_version2/?version=1.0.0`, ); - cy.contains(`We couldn't find the page you're looking for!`); + cy.contains("We couldn't find the page you're looking for!"); cy.visit( `${uiPrefix}repo/published/test_namespace/test_repo_collection_version2/?version=1.0.0`, ); cy.contains('test_repo_collection_version2'); - cy.contains(`We couldn't find the page you're looking for!`).should( + cy.contains("We couldn't find the page you're looking for!").should( 'not.exist', ); diff --git a/test/cypress/e2e/namespaces/group-roles.js b/test/cypress/e2e/namespaces/group-roles.js index 683399df2c..cdcb931b36 100644 --- a/test/cypress/e2e/namespaces/group-roles.js +++ b/test/cypress/e2e/namespaces/group-roles.js @@ -175,7 +175,7 @@ describe('Group Roles Tests', () => { it('should show group empty state', () => { cy.galaxykit('-i group create', 'empty_group'); cy.menuGo('User Access > Groups'); - cy.get(`[data-cy="GroupList-row-empty_group"] a`).click(); + cy.get('[data-cy="GroupList-row-empty_group"] a').click(); cy.contains('There are currently no roles assigned to this group.'); }); }); diff --git a/test/cypress/e2e/namespaces/rbac-access.js b/test/cypress/e2e/namespaces/rbac-access.js index ec1539ed41..1d03b47b9d 100644 --- a/test/cypress/e2e/namespaces/rbac-access.js +++ b/test/cypress/e2e/namespaces/rbac-access.js @@ -8,7 +8,7 @@ describe('Namespace Access tab', () => { cy.deleteTestGroups(); cy.galaxykit(`-i namespace create rbac_access_${num}`); - cy.galaxykit('-i group create', `access_group`); + cy.galaxykit('-i group create', 'access_group'); }); beforeEach(() => { @@ -49,7 +49,7 @@ describe('Execution Environment Access tab', () => { 'library/alpine', `rbac_access_${num}_registry`, ); - cy.galaxykit('-i group create', `access_group`); + cy.galaxykit('-i group create', 'access_group'); }); beforeEach(() => { diff --git a/test/cypress/e2e/namespaces/rbac-create.js b/test/cypress/e2e/namespaces/rbac-create.js index be6a47f853..e2fb42b6a8 100644 --- a/test/cypress/e2e/namespaces/rbac-create.js +++ b/test/cypress/e2e/namespaces/rbac-create.js @@ -33,7 +33,7 @@ describe('add and delete roles', () => { cy.get('#role_name').clear().type('test'); helperText('role_name').should( 'have.text', - `This field must start with 'galaxy.'.`, + "This field must start with 'galaxy.'.", ); cy.get('#role_name').clear(); diff --git a/test/cypress/e2e/namespaces/rbac.js b/test/cypress/e2e/namespaces/rbac.js index 97ba185a3e..18730f6d07 100644 --- a/test/cypress/e2e/namespaces/rbac.js +++ b/test/cypress/e2e/namespaces/rbac.js @@ -17,9 +17,9 @@ describe('RBAC test for user without permissions', () => { cy.galaxykit( '-i container create', - `testcontainer`, + 'testcontainer', 'library/alpine', - `docker`, + 'docker', ); cy.galaxykit('-i user create', userName, userPassword); @@ -222,9 +222,9 @@ describe('RBAC test for user with permissions', () => { 'https://registry.hub.docker.com/', ); cy.addRemoteContainer({ - name: `testcontainer`, + name: 'testcontainer', upstream_name: 'library/alpine', - registry: `docker`, + registry: 'docker', include_tags: 'latest', }); diff --git a/test/cypress/e2e/namespaces/user-dashboard.js b/test/cypress/e2e/namespaces/user-dashboard.js index 25712e70c1..dec7159db0 100644 --- a/test/cypress/e2e/namespaces/user-dashboard.js +++ b/test/cypress/e2e/namespaces/user-dashboard.js @@ -4,9 +4,9 @@ describe('Hub User Management Tests', () => { cy.login(); cy.menuGo('User Access > Users'); - const actionsSelector = `[data-cy="UserList-row-admin"] [aria-label="Actions"]`; - cy.get(actionsSelector).click(); - cy.containsnear(actionsSelector, 'Delete').click(); + const kebab = '[data-cy="UserList-row-admin"] [aria-label="Actions"]'; + cy.get(kebab).click(); + cy.containsnear(kebab, 'Delete').click(); cy.get('button').contains('Delete').should('be.disabled'); cy.get('button').contains('Cancel').click(); }); diff --git a/test/cypress/e2e/repo/container-signing.js b/test/cypress/e2e/repo/container-signing.js index ce451a2b24..1fcb3351e7 100644 --- a/test/cypress/e2e/repo/container-signing.js +++ b/test/cypress/e2e/repo/container-signing.js @@ -19,7 +19,7 @@ describe('Container Signing', () => { cy.galaxykit( 'registry create', - `docker`, + 'docker', 'https://registry.hub.docker.com/', );