From fff6f4464eebda2ac96edf44b1f334f02b7bb930 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Mon, 18 Dec 2023 18:21:47 +0100 Subject: [PATCH] Role imports - show imports list (#4760) No-Issue --- src/api/legacy-role.ts | 1 + src/components/ansible-role/import-list.tsx | 199 +++++++++++ src/components/index.ts | 1 + src/components/my-imports/import-console.tsx | 2 +- src/containers/ansible-role/imports.tsx | 351 ++++++------------- 5 files changed, 311 insertions(+), 243 deletions(-) create mode 100644 src/components/ansible-role/import-list.tsx diff --git a/src/api/legacy-role.ts b/src/api/legacy-role.ts index 1f088706b5..5e54821713 100644 --- a/src/api/legacy-role.ts +++ b/src/api/legacy-role.ts @@ -12,6 +12,7 @@ export class API extends LegacyAPI { return super.get(id + '/versions'); } + // get(id) // list(params?) } diff --git a/src/components/ansible-role/import-list.tsx b/src/components/ansible-role/import-list.tsx new file mode 100644 index 0000000000..015d340e78 --- /dev/null +++ b/src/components/ansible-role/import-list.tsx @@ -0,0 +1,199 @@ +import { t } from '@lingui/macro'; +import cx from 'classnames'; +import React, { useEffect, useState } from 'react'; +import { LegacyImportAPI, LegacyRoleImportDetailType } from 'src/api'; +import { + AlertType, + DateComponent, + EmptyStateFilter, + EmptyStateNoData, + HubListToolbar, + LoadingPageSpinner, + Pagination, +} from 'src/components'; +import { filterIsSet, handleHttpError } from 'src/utilities'; + +interface IProps { + addAlert: (alert: AlertType) => void; + params: { + page?: number; + page_size?: number; + role_id?: number; + sort?: string; + state?: string; + }; + selectImport: (item: LegacyRoleImportDetailType) => void; + selectedImport?: LegacyRoleImportDetailType; + updateParams: (params) => void; +} + +const StatusIcon = ({ state }: { state: string }) => ( + +); + +export function RoleImportList({ + addAlert, + params, + selectImport, + selectedImport, + updateParams, +}: IProps) { + const [count, setCount] = useState(0); + const [imports, setImports] = useState([]); + const [loading, setLoading] = useState(false); + + const query = () => { + setLoading(true); + LegacyImportAPI.list({ + page: 1, + page_size: 10, + sort: '-created', + ...params, + detail: true, + }) + .then(({ data: { count, results } }) => { + setImports(results); + setCount(count); + }) + .catch( + handleHttpError(t`Failed to list role imports`, () => null, addAlert), + ) + .finally(() => setLoading(false)); + }; + + useEffect(() => { + query(); + }, [params]); + + const statusMap = { + SUCCESS: t`Completed`, + FAILED: t`Failed`, + RUNNING: t`Running`, + WAITING: t`Waiting`, + }; + + return ( +
+ + +
+ {loading ? ( +
+ +
+ ) : !count && + filterIsSet(params, ['role_name', 'namespace_name', 'state']) ? ( + + ) : !count ? ( + + ) : ( +
+ {imports.map((item) => ( +
selectImport(item)} + key={item.pulp_id} + className={cx({ + clickable: true, + 'list-container': true, + 'hub-c-toolbar__item-selected-item': + item.pulp_id === selectedImport?.pulp_id, + })} + data-cy={`RoleImportList-row-${item.role_id}`} + > +
+ +
+
+
+
+ + {item.summary_fields?.github_user}/ + {item.summary_fields?.github_repo} + {' '} +
+
+ Status: {statusMap[item.state] || item.state}{' '} + {item.summary_fields?.task_messages?.at(-1)?.id ? ( + + ) : null} +
+
+
+
+ ))} +
+ )} +
+ + {!loading && count ? ( + + ) : null} +
+ ); +} diff --git a/src/components/index.ts b/src/components/index.ts index 94c5f5727e..6c753641d7 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,4 +1,5 @@ export { AboutModalWindow } from './about-modal/about-modal'; +export { RoleImportList } from './ansible-role/import-list'; export { LegacyNamespaceListItem } from './ansible-role/namespace-item'; export { ProviderLink } from './ansible-role/provider-link'; export { RoleImportForm } from './ansible-role/role-import-form'; diff --git a/src/components/my-imports/import-console.tsx b/src/components/my-imports/import-console.tsx index 3020a51a09..4ebc399c91 100644 --- a/src/components/my-imports/import-console.tsx +++ b/src/components/my-imports/import-console.tsx @@ -16,7 +16,7 @@ interface IProps { collection?: CollectionVersionSearch; empty?: boolean; followMessages?: boolean; - loading: boolean; + loading?: boolean; roleImport?: LegacyRoleImportDetailType; selectedImport?: ImportListType; setFollowMessages?: (follow: boolean) => void; diff --git a/src/containers/ansible-role/imports.tsx b/src/containers/ansible-role/imports.tsx index 4ac5435bb1..3a1edb6d86 100644 --- a/src/containers/ansible-role/imports.tsx +++ b/src/containers/ansible-role/imports.tsx @@ -1,21 +1,18 @@ import { t } from '@lingui/macro'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { - CollectionVersionAPI, - CollectionVersionSearch, - ImportAPI, - ImportDetailType, - ImportListType, - PulpStatus, + LegacyImportAPI, + LegacyRoleAPI, + LegacyRoleImportDetailType, } from 'src/api'; import { AlertList, AlertType, BaseHeader, ImportConsole, - ImportList, Main, + RoleImportList, closeAlertMixin, } from 'src/components'; import { Paths, formatPath } from 'src/paths'; @@ -23,25 +20,63 @@ import { ParamHelper, RouteProps, withRouter } from 'src/utilities'; interface IState { alerts: AlertType[]; - collection: CollectionVersionSearch; + error; followLogs: boolean; - importDetailError: string; - importList: ImportListType[]; - loadingImportDetails: boolean; - loadingImports: boolean; - params: { - keyword?: string; - namespace?: string; - page?: number; - page_size?: number; - }; - resultsCount: number; - selectedImport: ImportListType; - selectedImportDetails: ImportDetailType; + params; + role; + selectedImport: LegacyRoleImportDetailType; } -// TODO left side recent imports, plus filters -// right side import log as is except roleImport= +const RoleLink = ({ role_id }: { role_id?: number }) => { + const [role, setRole] = useState(null); + + useEffect(() => { + if (!role_id) { + setRole(null); + return; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + LegacyRoleAPI.get(role_id as any).then( + ({ + data: { + name, + summary_fields: { + namespace: { name: namespace }, + }, + }, + }) => setRole({ namespace, name }), + ); + }, [role_id]); + + if (!role) { + return null; + } + + const { namespace, name } = role; + + return ( +
+ {!role ? ( + `${namespace}.${name}` + ) : ( + + {namespace}.{name} + + )} +
+ ); +}; class AnsibleRoleImports extends React.Component { polling: ReturnType; @@ -59,39 +94,17 @@ class AnsibleRoleImports extends React.Component { this.state = { alerts: [], - collection: null, + error: null, followLogs: false, - importDetailError: '', - importList: [], - loadingImportDetails: true, - loadingImports: true, params, - resultsCount: 0, - selectedImport: undefined, - selectedImportDetails: undefined, + role: null, + selectedImport: null, }; } componentDidMount() { - // Load namespaces, use the namespaces to query the import list, - // use the import list to load the task details - this.loadImportList(() => this.loadTaskDetails()); - this.polling = setInterval(() => { - if (!this.state.params.namespace) { - return; - } - - const { selectedImport, selectedImportDetails } = this.state; - const allowedStates = [PulpStatus.running, PulpStatus.waiting]; - - // selectedImportDetails can be failed while selectedImport is still running, poll() updates selectedImport - if ( - allowedStates.includes(selectedImportDetails?.state) || - allowedStates.includes(selectedImport?.state) - ) { - this.poll(); - } + this.loadTaskDetails(); }, 10000); } @@ -110,108 +123,38 @@ class AnsibleRoleImports extends React.Component { } render() { - const { - collection, - followLogs, - importDetailError, - importList, - loadingImportDetails, - loadingImports, - params, - resultsCount, - selectedImport, - selectedImportDetails, - } = this.state; - - if (!importList) { - return null; - } + const { alerts, error, followLogs, params, selectedImport } = this.state; return ( <>
- this.closeAlert(i)} - /> + this.closeAlert(i)} />
- {false && ( - this.addAlert(alert)} - importList={importList} - selectedImport={selectedImport} - loading={loadingImports} - numberOfResults={resultsCount} - params={params} - selectImport={(sImport) => this.selectImport(sImport)} - updateParams={(params) => { - this.updateParams(params, () => { - if (params.namespace) { - this.setState( - { - loadingImports: true, - loadingImportDetails: true, - }, - () => - this.loadImportList(() => this.loadTaskDetails()), - ); - } else { - this.setState({ - importDetailError: t`No data`, - loadingImportDetails: false, - }); - } - }); - }} - /> - )} + this.addAlert(alert)} + params={params} + selectImport={(s) => this.selectImport(s)} + selectedImport={selectedImport} + updateParams={(params) => this.updateParams(params)} + />
- {selectedImport && this.state.params.namespace && ( -
- {!collection ? ( - `${selectedImport.namespace}.${selectedImport.name}` - ) : ( - - {selectedImport.namespace}.{selectedImport.name} - - )} -
- )} + this.setState({ followLogs }) } - task={selectedImportDetails} />
@@ -225,124 +168,48 @@ class AnsibleRoleImports extends React.Component { return ParamHelper.updateParamsMixin(); } - private selectImport(sImport) { - this.setState( - { selectedImport: sImport, loadingImportDetails: true }, - () => { - this.topOfPage.current.scrollIntoView({ - behavior: 'smooth', - }); - this.loadTaskDetails(); - }, + private selectImport(selectedImport) { + this.setState({ selectedImport }, () => this.loadTaskDetails()); + window.requestAnimationFrame( + () => this.topOfPage.current?.scrollIntoView({ behavior: 'smooth' }), ); } - private poll() { - this.loadTaskDetails(() => { - // Update the state of the selected import in the list if it's - // different from the one loaded from the API. - const { selectedImport, selectedImportDetails, importList } = this.state; - - if (!selectedImportDetails) { - return; - } - - if (selectedImport.state !== selectedImportDetails.state) { - const importIndex = importList.findIndex( - (x) => x.id === selectedImport.id, - ); - - const imports = [...importList]; - const newSelectedImport = { - ...selectedImport, - state: selectedImportDetails.state, - finished_at: selectedImportDetails.finished_at, - }; + private loadTaskDetails() { + const { selectedImport } = this.state; - imports[importIndex] = newSelectedImport; - - this.setState({ - selectedImport: newSelectedImport, - importList: imports, - }); - } - }); - } + if (!selectedImport) { + return null; + } - private loadImportList(callback?: () => void) { - if (!this.state.params.namespace) { - this.setState({ - importDetailError: t`No data`, - loadingImportDetails: false, - }); + if (!['RUNNING', 'WAITING'].includes(selectedImport.state)) { return; } - ImportAPI.list({ ...this.state.params, sort: '-created' }) - .then((importList) => { - this.setState( - { - importList: importList.data.data, - selectedImport: importList.data.data[0], - resultsCount: importList.data.meta.count, - loadingImports: false, + this.setState({ error: null }); + return LegacyImportAPI.list({ + detail: true, + page_size: 1, + role_id: selectedImport.role_id, + sort: '-created', + }) + .then( + ({ + data: { + results: [first], }, - callback, - ); - }) - .catch((result) => console.log(result)); - } - - private loadTaskDetails(callback?: () => void) { - if (!this.state.selectedImport) { - this.setState({ - importDetailError: t`No data`, - loadingImportDetails: false, - }); - } else { - ImportAPI.get(this.state.selectedImport.id) - .then((result) => { - this.setState( - { - importDetailError: '', - loadingImportDetails: false, - selectedImportDetails: result.data, - collection: null, - }, - () => { - const { namespace, name, version } = - this.state.selectedImportDetails; - - // have to use list instead of get because repository_list isn't - // available on collection version details - CollectionVersionAPI.list({ - namespace, - name, - version, - }) - .then((result) => { - if (result.data.meta.count === 1) { - this.setState({ - collection: result.data.data[0], - }); - } - }) - .finally(() => { - if (callback) { - callback(); - } - }); - }, - ); - }) - .catch(() => { + }) => this.setState({ - selectedImportDetails: undefined, - importDetailError: t`Error fetching import from API`, - loadingImportDetails: false, - }); - }); - } + error: null, + selectedImport: first || selectedImport, + }), + ) + .catch(() => + this.setState({ + error: t`Error fetching import from API`, + selectedImport: null, + }), + ); } }