diff --git a/webpack/JobInvocationDetail/CheckboxesActions.js b/webpack/JobInvocationDetail/CheckboxesActions.js index 5d8bb0e1c..f8f825f87 100644 --- a/webpack/JobInvocationDetail/CheckboxesActions.js +++ b/webpack/JobInvocationDetail/CheckboxesActions.js @@ -14,23 +14,21 @@ import { } from '@patternfly/react-icons'; import axios from 'axios'; import { foremanUrl } from 'foremanReact/common/helpers'; -import { useAPI } from 'foremanReact/common/hooks/API/APIHooks'; import { translate as __, sprintf } from 'foremanReact/common/I18n'; import { addToast } from 'foremanReact/components/ToastsList'; import PropTypes from 'prop-types'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { DIRECT_OPEN_HOST_LIMIT, - MAX_HOSTS_API_SIZE, templateInvocationPageUrl, } from './JobInvocationConstants'; import { selectHasPermission, selectTaskCancelable, } from './JobInvocationSelectors'; -import OpenAllInvocationsModal, { PopupAlert } from './OpenAllInvocationsModal'; +import OpenAllInvocationsModal from './OpenAllInvocationsModal'; /* eslint-disable camelcase */ const ActionsKebab = ({ @@ -73,7 +71,7 @@ const ActionsKebab = ({ onClick={() => handleOpenHosts('failed')} isDisabled={failedCount === 0} > - {sprintf(__('Open all failed runs (%s)'), failedCount)} + {sprintf(__('Open all failed runs on this page (%s)'), failedCount)} , ]; @@ -104,17 +102,18 @@ const ActionsKebab = ({ export const CheckboxesActions = ({ selectedIds, - failedCount, + allJobs, jobID, filter, bulkParams, + setShowAlert, }) => { const [isModalOpen, setIsModalOpen] = useState(false); - const [showAlert, setShowAlert] = useState(false); const [isOpenFailed, setIsOpenFailed] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const isTaskCancelable = useSelector(selectTaskCancelable); const dispatch = useDispatch(); + const [toBeOpened, setToBeOpened] = useState([]); const hasCreatePermission = useSelector( selectHasPermission('create_job_invocations') @@ -129,19 +128,14 @@ export const CheckboxesActions = ({ : ''; const combinedQuery = `${bulkParams}${filterQuery}`; - const { response: failedHostsData } = useAPI( - 'get', - foremanUrl(`/api/job_invocations/${jobID}/hosts`), - { - params: { - per_page: MAX_HOSTS_API_SIZE, - search: `job_invocation.result = failed`, - }, - skip: failedCount === 0, - } - ); + const [failedHosts, setFailedHosts] = useState([]); - const failedHosts = failedHostsData?.results || []; + useEffect(() => { + const failed = allJobs.filter(i => i.job_status === 'error'); + setFailedHosts(failed); + }, [allJobs]); + + const failedCount = failedHosts.length; const openLink = url => { const newWin = window.open(url); @@ -151,24 +145,35 @@ export const CheckboxesActions = ({ } }; + const openTabs = tabs => { + tabs.forEach(open => { + const openId = open.id ?? open; + openLink(templateInvocationPageUrl(openId, jobID)); + }); + }; + const handleOpenHosts = async (type = 'all') => { if (type === 'failed') { if (failedCount <= DIRECT_OPEN_HOST_LIMIT) { - failedHosts.forEach(host => - openLink(templateInvocationPageUrl(host.id, jobID)) - ); + openTabs(failedHosts); return; } + setToBeOpened(failedHosts); setIsOpenFailed(true); setIsModalOpen(true); return; } + if (selectedIds.length === 0) { + selectedIds = allJobs; + } + if (selectedIds.length <= DIRECT_OPEN_HOST_LIMIT) { - selectedIds.forEach(id => openLink(templateInvocationPageUrl(id, jobID))); + openTabs(selectedIds); return; } + setToBeOpened(selectedIds); setIsOpenFailed(false); setIsModalOpen(true); }; @@ -237,13 +242,19 @@ export const CheckboxesActions = ({ @@ -281,16 +292,13 @@ export const CheckboxesActions = ({ isDropdownOpen={isDropdownOpen} setIsDropdownOpen={setIsDropdownOpen} /> - {showAlert && } setIsModalOpen(false)} failedCount={failedCount} - failedHosts={failedHosts} - jobID={jobID} isOpenFailed={isOpenFailed} - setShowAlert={setShowAlert} selectedIds={selectedIds} + confirmCallback={() => openTabs(toBeOpened)} /> ); @@ -314,10 +322,11 @@ ActionsKebab.defaultProps = { CheckboxesActions.propTypes = { selectedIds: PropTypes.array.isRequired, - failedCount: PropTypes.number.isRequired, + allJobs: PropTypes.array.isRequired, jobID: PropTypes.string.isRequired, bulkParams: PropTypes.string, filter: PropTypes.string, + setShowAlert: PropTypes.func.isRequired, }; CheckboxesActions.defaultProps = { diff --git a/webpack/JobInvocationDetail/JobInvocationConstants.js b/webpack/JobInvocationDetail/JobInvocationConstants.js index 06d0a5510..c491347af 100644 --- a/webpack/JobInvocationDetail/JobInvocationConstants.js +++ b/webpack/JobInvocationDetail/JobInvocationConstants.js @@ -17,7 +17,6 @@ export const GET_REPORT_TEMPLATES = 'GET_REPORT_TEMPLATES'; export const GET_REPORT_TEMPLATE_INPUTS = 'GET_REPORT_TEMPLATE_INPUTS'; export const JOB_INVOCATION_HOSTS = 'JOB_INVOCATION_HOSTS'; export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION'; -export const MAX_HOSTS_API_SIZE = 100; export const DIRECT_OPEN_HOST_LIMIT = 3; export const ALL_JOB_HOSTS = 'ALL_JOB_HOSTS'; export const currentPermissionsUrl = foremanUrl( diff --git a/webpack/JobInvocationDetail/JobInvocationHostTable.js b/webpack/JobInvocationDetail/JobInvocationHostTable.js index f5941291b..0e6b8e151 100644 --- a/webpack/JobInvocationDetail/JobInvocationHostTable.js +++ b/webpack/JobInvocationDetail/JobInvocationHostTable.js @@ -8,9 +8,10 @@ import { ToolbarItem, } from '@patternfly/react-core'; import { ExpandableRowContent, Tbody, Td, Tr } from '@patternfly/react-table'; +import { useDispatch } from 'react-redux'; +import { APIActions } from 'foremanReact/redux/API'; import { translate as __ } from 'foremanReact/common/I18n'; import { foremanUrl } from 'foremanReact/common/helpers'; -import { useAPI } from 'foremanReact/common/hooks/API/APIHooks'; import { RowSelectTd } from 'foremanReact/components/HostsIndex/RowSelectTd'; import SelectAllCheckbox from 'foremanReact/components/PF4/TableIndexPage/Table/SelectAllCheckbox'; import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table'; @@ -26,22 +27,20 @@ import PropTypes from 'prop-types'; import React, { useEffect, useMemo, useState, useRef } from 'react'; import { FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; -import URI from 'urijs'; +import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext'; import { CheckboxesActions } from './CheckboxesActions'; import DropdownFilter from './DropdownFilter'; import Columns, { JOB_INVOCATION_HOSTS, - MAX_HOSTS_API_SIZE, - STATUS_UPPERCASE, LIST_TEMPLATE_INVOCATIONS, + STATUS_UPPERCASE, ALL_JOB_HOSTS, } from './JobInvocationConstants'; -import { PopupAlert } from './OpenAllInvocationsModal'; import { TemplateInvocation } from './TemplateInvocation'; import { RowActions } from './TemplateInvocationComponents/TemplateActionButtons'; +import { PopupAlert } from './OpenAllInvocationsModal'; const JobInvocationHostTable = ({ - failedCount, id, initialFilter, onFilterUpdate, @@ -50,19 +49,29 @@ const JobInvocationHostTable = ({ }) => { const columns = Columns(); const columnNamesKeys = Object.keys(columns); - const apiOptions = { key: JOB_INVOCATION_HOSTS }; + const history = useHistory(); - const [selectedFilter, setSelectedFilter] = useState(initialFilter); + const dispatch = useDispatch(); + + const [showAlert, setShowAlert] = useState(false); + + const [apiResponse, setApiResponse] = useState([]); + const [status, setStatus] = useState(STATUS_UPPERCASE.PENDING); + const [allHostsIds, setAllHostsIds] = useState([]); + + // Expansive items const [expandedHost, setExpandedHost] = useState([]); const prevStatusLabel = useRef(statusLabel); - useEffect(() => { - if (initialFilter !== selectedFilter) { - wrapSetSelectedFilter(initialFilter); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [initialFilter]); + const isHostExpanded = host => expandedHost.includes(host); + const setHostExpanded = (host, isExpanding = true) => + setExpandedHost(prevExpanded => { + const otherExpandedHosts = prevExpanded.filter(h => h !== host); + return isExpanding ? [...otherExpandedHosts, host] : otherExpandedHosts; + }); + // Page table params + // Parse URL const { searchParam: urlSearchQuery = '', page: urlPage, @@ -70,10 +79,27 @@ const JobInvocationHostTable = ({ order: urlOrder, } = useUrlParams(); - const constructFilter = ( - filter = selectedFilter, - search = urlSearchQuery - ) => { + const { perPage: foremanPerPage } = useForemanSettings(); + + // default + const defaultParams = useMemo( + () => ({ + page: urlPage ? Number(urlPage) : 1, + per_page: urlPerPage || Number(urlPerPage) || foremanPerPage, + order: urlOrder || '', + }), + [urlPage, urlPerPage, foremanPerPage, urlOrder] + ); + + // Page row for table + const { pageRowCount } = getPageStats({ + total: apiResponse?.total || 0, + page: apiResponse?.page || urlPage || 1, + perPage: apiResponse?.per_page || urlPerPage || 0, + }); + + // Search filter + const constructFilter = (filter = initialFilter, search = urlSearchQuery) => { const dropdownFilterClause = filter && filter !== 'all_statuses' ? `job_invocation.result = ${filter}` @@ -85,68 +111,93 @@ const JobInvocationHostTable = ({ .join(' AND '); }; - const defaultParams = useMemo( - () => ({ - ...(urlPage ? { page: Number(urlPage) } : {}), - ...(urlPerPage ? { per_page: Number(urlPerPage) } : {}), - ...(urlOrder ? { order: urlOrder } : {}), - }), - [urlPage, urlPerPage, urlOrder] - ); + const handleResponse = (data, key) => { + if (key === JOB_INVOCATION_HOSTS) { + const ids = data.data.results.map(i => i.id); - useAPI('get', `/job_invocations/${id}/hosts`, { - params: { - search: defaultParams.search, - }, - key: LIST_TEMPLATE_INVOCATIONS, - }); - const { response, status, setAPIOptions } = useAPI( - 'get', - `/api/job_invocations/${id}/hosts`, - { - params: defaultParams, + setApiResponse(data.data); + setAllHostsIds(ids); } - ); - const [allPagesResponse, setAllPagesResponse] = useState([]); - const apiAllParams = { - page: 1, - per_page: Math.min(response?.subtotal || 1, MAX_HOSTS_API_SIZE), - search: constructFilter(selectedFilter, urlSearchQuery), + setStatus(STATUS_UPPERCASE.RESOLVED); + }; + + // Call hosts data with params + const makeApiCall = (requestParams, callParams = {}) => { + dispatch( + APIActions.get({ + key: callParams.key ?? ALL_JOB_HOSTS, + url: callParams.url ?? `/api/job_invocations/${id}/hosts`, + params: requestParams, + handleSuccess: data => handleResponse(data, callParams.key), + handleError: () => setStatus(STATUS_UPPERCASE.ERROR), + errorToast: ({ response }) => + response?.data?.error?.full_messages?.[0] || response, + }) + ); }; - const { response: allResponse, setAPIOptions: setAllAPIOptions } = useAPI( - 'get', - `/api/job_invocations/${id}/hosts`, - { - params: apiAllParams, - key: ALL_JOB_HOSTS, + const filterApiCall = newAPIOptions => { + const newParams = newAPIOptions?.params ?? newAPIOptions ?? {}; + + const filterSearch = constructFilter( + initialFilter, + newParams.search ?? urlSearchQuery + ); + + const finalParams = { + ...defaultParams, + ...newParams, + }; + + if (filterSearch !== '') { + finalParams.search = filterSearch; } - ); + makeApiCall(finalParams, { key: JOB_INVOCATION_HOSTS }); + + const urlSearchParams = new URLSearchParams(window.location.search); + + ['page', 'per_page', 'order'].forEach(key => { + if (finalParams[key]) urlSearchParams.set(key, finalParams[key]); + }); + + history.push({ search: urlSearchParams.toString() }); + }; + + // Filter change + const handleFilterChange = newFilter => { + onFilterUpdate(newFilter); + }; + + // Effects + // run after mount useEffect(() => { - if (response?.subtotal) { - setAllAPIOptions(prevOptions => ({ - ...prevOptions, - params: apiAllParams, - })); + // Job Invo template load + makeApiCall( + {}, + { + url: `/job_invocations/${id}/hosts`, + key: LIST_TEMPLATE_INVOCATIONS, + } + ); + + if (initialFilter === '') { + onFilterUpdate('all_statuses'); } + // eslint-disable-next-line react-hooks/exhaustive-deps - }, [response?.subtotal, selectedFilter, urlSearchQuery]); + }, []); useEffect(() => { - if (allResponse?.results) { - setAllPagesResponse(allResponse.results); - } - }, [allResponse]); + if (initialFilter !== '') filterApiCall(); - useEffect(() => { if (statusLabel !== prevStatusLabel.current) { - setAPIOptions(prevOptions => ({ ...prevOptions })); prevStatusLabel.current = statusLabel; + filterApiCall(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [statusLabel]); + }, [initialFilter, statusLabel]); const { updateSearchQuery: updateSearchQueryBulk, @@ -155,11 +206,11 @@ const JobInvocationHostTable = ({ exclusionSet, ...selectAllOptions } = useBulkSelect({ - results: response?.results, + results: apiResponse?.results, metadata: { - total: response?.total, - page: response?.page, - selectable: response?.subtotal, + total: apiResponse?.total, + page: apiResponse?.page, + selectable: apiResponse?.subtotal, }, initialSearchQuery: urlSearchQuery, }); @@ -175,35 +226,11 @@ const JobInvocationHostTable = ({ isSelected, } = selectAllOptions; - const allHostIds = allPagesResponse?.map(item => item.id) || []; const selectedIds = areAllRowsSelected() || exclusionSet.size > 0 - ? allHostIds.filter(hostId => !exclusionSet.has(hostId)) + ? allHostsIds.filter(hostId => !exclusionSet.has(hostId)) : Array.from(inclusionSet); - const { pageRowCount } = getPageStats({ - total: response?.total || 0, - page: response?.page || urlPage || 1, - perPage: response?.per_page || urlPerPage || 0, - }); - - const selectionToolbar = ( - - - - ); - const controller = 'hosts'; const memoDefaultSearchProps = useMemo( () => getControllerSearchProps(controller), @@ -213,70 +240,40 @@ const JobInvocationHostTable = ({ `/${controller}/auto_complete_search` ); - const wrapSetSelectedFilter = newFilter => { - setSelectedFilter(newFilter); - onFilterUpdate(newFilter); - - const filterSearch = constructFilter(newFilter, urlSearchQuery); - - const newParams = { - ...defaultParams, - page: 1, - }; - - if (filterSearch !== '') { - newParams.search = filterSearch; - } - - setAPIOptions(prev => ({ ...prev, params: newParams })); - - const urlSearchParams = new URLSearchParams(window.location.search); - urlSearchParams.set('page', '1'); - history.push({ search: urlSearchParams.toString() }); - }; - - const wrapSetAPIOptions = newAPIOptions => { - const newParams = newAPIOptions?.params ?? newAPIOptions ?? {}; - - const filterSearch = constructFilter( - selectedFilter, - newParams.search ?? urlSearchQuery - ); - - const mergedParams = { - ...defaultParams, - ...newParams, - }; - - if (filterSearch !== '') { - mergedParams.search = filterSearch; - } else if ('search' in mergedParams) { - delete mergedParams.search; - } - - setAPIOptions(prev => ({ ...prev, params: mergedParams })); - - const { search: _search, ...paramsForUrl } = mergedParams; - const uri = new URI(); - uri.setSearch(paramsForUrl); - history.push({ search: uri.search() }); - }; - const combinedResponse = { response: { search: urlSearchQuery, can_create: false, - results: response?.results || [], - total: response?.total || 0, + results: apiResponse?.results || [], + total: apiResponse?.total || 0, per_page: defaultParams?.perPage, page: defaultParams?.page, - subtotal: response?.subtotal || 0, - message: response?.message || 'error', + subtotal: apiResponse?.subtotal || 0, + message: apiResponse?.message || 'error', }, status, - setAPIOptions: wrapSetAPIOptions, + setAPIOptions: filterApiCall, }; + const results = apiResponse.results ?? []; + + const selectionToolbar = ( + + + + ); + const customEmptyState = ( @@ -314,22 +311,11 @@ const JobInvocationHostTable = ({ ); - const { results = [] } = response; - - const isHostExpanded = host => expandedHost.includes(host); - const setHostExpanded = (host, isExpanding = true) => - setExpandedHost(prevExpanded => { - const otherExpandedHosts = prevExpanded.filter(h => h !== host); - return isExpanding ? [...otherExpandedHosts, host] : otherExpandedHosts; - }); - const [showAlert, setShowAlert] = useState(false); - return ( <> {showAlert && } , 0 ? fetchBulkParams() : null} selectedIds={selectedIds} - failedCount={failedCount} + allJobs={results} jobID={id} key="checkboxes-actions" - filter={selectedFilter} + filter={initialFilter} + setShowAlert={setShowAlert} />, ]} selectionToolbar={selectionToolbar} @@ -361,21 +348,21 @@ const JobInvocationHostTable = ({ : null } params={{ - page: response?.page || Number(urlPage), - per_page: response?.per_page || Number(urlPerPage), + page: defaultParams.page || Number(urlPage), + per_page: defaultParams.per_page || Number(urlPerPage), order: urlOrder, }} - page={response?.page || Number(urlPage)} - perPage={response?.per_page || Number(urlPerPage)} - setParams={wrapSetAPIOptions} - itemCount={response?.subtotal} + page={defaultParams.page || Number(urlPage)} + perPage={defaultParams.per_page || Number(urlPerPage)} + setParams={filterApiCall} + itemCount={apiResponse?.subtotal} results={results} url="" showCheckboxes refreshData={() => {}} errorMessage={ - status === STATUS_UPPERCASE.ERROR && response?.message - ? response.message + status === STATUS_UPPERCASE.ERROR && apiResponse?.message + ? apiResponse.message : null } isPending={status === STATUS_UPPERCASE.PENDING} @@ -439,7 +426,6 @@ const JobInvocationHostTable = ({ JobInvocationHostTable.propTypes = { id: PropTypes.string.isRequired, targeting: PropTypes.object.isRequired, - failedCount: PropTypes.number.isRequired, initialFilter: PropTypes.string.isRequired, statusLabel: PropTypes.string, onFilterUpdate: PropTypes.func, diff --git a/webpack/JobInvocationDetail/OpenAllInvocationsModal.js b/webpack/JobInvocationDetail/OpenAllInvocationsModal.js index e6d393332..2bf2a0f27 100644 --- a/webpack/JobInvocationDetail/OpenAllInvocationsModal.js +++ b/webpack/JobInvocationDetail/OpenAllInvocationsModal.js @@ -7,10 +7,6 @@ import { import { sprintf, translate as __ } from 'foremanReact/common/I18n'; import PropTypes from 'prop-types'; import React from 'react'; -import { - templateInvocationPageUrl, - MAX_HOSTS_API_SIZE, -} from './JobInvocationConstants'; export const PopupAlert = ({ setShowAlert }) => ( ( const OpenAllInvocationsModal = ({ failedCount, - failedHosts, isOpen, isOpenFailed, - jobID, onClose, - setShowAlert, selectedIds, + confirmCallback, }) => { - const modalText = isOpenFailed ? 'failed' : 'selected'; + const modalText = () => { + if (isOpenFailed) return 'failed'; + if (selectedIds.length > 0) return 'selected'; + return 'current page'; + }; - const openLink = url => { - const newWin = window.open(url); - if (!newWin || newWin.closed || typeof newWin.closed === 'undefined') { - setShowAlert(true); + const selectedText = () => { + if (isOpenFailed) { + return ( + <> + {__('The number of failed invocations is:')} {failedCount} + + ); } + if (selectedIds.length > 0) { + return ( + <> + {__('The number of selected invocations is:')}{' '} + {selectedIds.length} + + ); + } + return <>; }; return ( @@ -48,7 +58,7 @@ const OpenAllInvocationsModal = ({ isOpen={isOpen} onClose={onClose} ouiaId="template-invocation-new-tab-modal" - title={sprintf(__('Open all %s invocations in new tabs'), modalText)} + title={sprintf(__('Open all %s invocations in new tabs'), modalText())} titleIconVariant="warning" width={590} actions={[ @@ -57,16 +67,7 @@ const OpenAllInvocationsModal = ({ key="confirm" variant="primary" onClick={() => { - const hostsToOpen = isOpenFailed - ? failedHosts - : selectedIds.map(id => ({ id })); - - hostsToOpen - .slice(0, MAX_HOSTS_API_SIZE) - .forEach(({ id }) => - openLink(templateInvocationPageUrl(id, jobID), '_blank') - ); - + confirmCallback(); onClose(); }} > @@ -84,30 +85,24 @@ const OpenAllInvocationsModal = ({ > {sprintf( __('Are you sure you want to open all %s invocations in new tabs?'), - modalText + modalText() )}
- {__('This will open a new tab for each invocation. The maximum is 100.')} -
- {sprintf(__('The number of %s invocations is:'), modalText)}{' '} - {isOpenFailed ? failedCount : selectedIds.length} + {selectedText()} ); }; OpenAllInvocationsModal.propTypes = { failedCount: PropTypes.number.isRequired, - failedHosts: PropTypes.array, isOpen: PropTypes.bool.isRequired, isOpenFailed: PropTypes.bool, - jobID: PropTypes.string.isRequired, onClose: PropTypes.func.isRequired, - setShowAlert: PropTypes.func.isRequired, selectedIds: PropTypes.array.isRequired, + confirmCallback: PropTypes.func.isRequired, }; OpenAllInvocationsModal.defaultProps = { - failedHosts: [], isOpenFailed: false, }; diff --git a/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js b/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js index 760ec4053..969afccb7 100644 --- a/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +++ b/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js @@ -32,6 +32,29 @@ selectors.selectTaskCancelable.mockImplementation(() => true); const mockStore = configureStore([]); const store = mockStore({}); +const allJobs = [ + { + id: 1, + job_status: 'error', + }, + { + id: 2, + job_status: 'error', + }, + { + id: 3, + job_status: 'error', + }, + { + id: 4, + job_status: 'error', + }, + { + id: 5, + job_status: 'error', + }, +]; + describe('TableToolbarActions', () => { const jobID = '42'; let openSpy; @@ -58,8 +81,9 @@ describe('TableToolbarActions', () => { ); @@ -76,8 +100,9 @@ describe('TableToolbarActions', () => { ); @@ -94,47 +119,52 @@ describe('TableToolbarActions', () => { test('shows alert when popups are blocked', async () => { openSpy.mockReturnValue(null); const selectedIds = [1, 2]; + const setShowMock = jest.fn(); render( ); fireEvent.click( screen.getByLabelText(/open all template invocations in new tab/i) ); - expect( - await screen.findByText(/Popups are blocked by your browser/) - ).toBeInTheDocument(); + expect(setShowMock).toHaveBeenCalledWith(true); }); }); describe('Opening failed in new tabs', () => { test('opens links when results length is less than or equal to 3', async () => { - const failedHosts = [{ id: 301 }, { id: 302 }]; - useAPI.mockReturnValue({ - response: { results: failedHosts }, - status: 'success', - }); render( - + ); fireEvent.click(screen.getByLabelText(/actions dropdown toggle/i)); fireEvent.click(await screen.findByText(/open all failed runs/i)); await waitFor(() => { - expect(openSpy).toHaveBeenCalledTimes(failedHosts.length); + expect(openSpy).toHaveBeenCalledTimes(2); }); }); test('shows modal when results length is greater than 3', async () => { render( - + ); fireEvent.click(screen.getByLabelText(/actions dropdown toggle/i)); @@ -145,21 +175,6 @@ describe('TableToolbarActions', () => { }) ).toBeInTheDocument(); }); - - test('calls useApi with skip: true when failedCount is 0', () => { - render( - - - - ); - expect(useAPI).toHaveBeenCalledWith( - 'get', - foremanUrl(`/api/job_invocations/${jobID}/hosts`), - expect.objectContaining({ - skip: true, - }) - ); - }); }); describe('PopupAlert', () => { @@ -188,8 +203,9 @@ describe('TableToolbarActions', () => { ); diff --git a/webpack/JobInvocationDetail/index.js b/webpack/JobInvocationDetail/index.js index eef296dc2..4b1a82834 100644 --- a/webpack/JobInvocationDetail/index.js +++ b/webpack/JobInvocationDetail/index.js @@ -40,7 +40,6 @@ const JobInvocationDetailPage = ({ const items = useSelector(selectItems); const { description, - failed = 0, status_label: statusLabel, task, start_at: startAt, @@ -182,7 +181,7 @@ const JobInvocationDetailPage = ({