From e6f0c01aa666d9a09ddf8b91bd18c4e27c555ba9 Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 21 Feb 2020 16:13:21 -0500 Subject: [PATCH] Schedule list now uses useRequest hooks for fetching and deleting. Also rolled a component for schedule toggles that can be used throughout the tree. --- .../Schedule/ScheduleList/ScheduleList.jsx | 132 ++++++------------ .../ScheduleList/ScheduleListItem.jsx | 35 +---- .../ScheduleList/ScheduleListItem.test.jsx | 11 -- .../Schedule/shared/ScheduleToggle.jsx | 78 +++++++++++ .../Schedule/shared/ScheduleToggle.test.jsx | 97 +++++++++++++ 5 files changed, 223 insertions(+), 130 deletions(-) create mode 100644 awx/ui_next/src/screens/Schedule/shared/ScheduleToggle.jsx create mode 100644 awx/ui_next/src/screens/Schedule/shared/ScheduleToggle.test.jsx diff --git a/awx/ui_next/src/screens/Schedule/ScheduleList/ScheduleList.jsx b/awx/ui_next/src/screens/Schedule/ScheduleList/ScheduleList.jsx index 36b0b9ccdd..2c1197e7c7 100644 --- a/awx/ui_next/src/screens/Schedule/ScheduleList/ScheduleList.jsx +++ b/awx/ui_next/src/screens/Schedule/ScheduleList/ScheduleList.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -10,6 +10,7 @@ import DataListToolbar from '@components/DataListToolbar'; import PaginatedDataList, { ToolbarDeleteButton, } from '@components/PaginatedDataList'; +import useRequest, { useDeleteItems } from '@util/useRequest'; import { getQSConfig, parseQueryString } from '@util/qs'; import { ScheduleListItem } from '.'; @@ -20,38 +21,54 @@ const QS_CONFIG = getQSConfig('schedule', { }); function ScheduleList({ i18n }) { - const [contentError, setContentError] = useState(null); - const [scheduleCount, setScheduleCount] = useState(0); - const [schedules, setSchedules] = useState([]); - const [deletionError, setDeletionError] = useState(null); - const [hasContentLoading, setHasContentLoading] = useState(true); const [selected, setSelected] = useState([]); - const [toggleError, setToggleError] = useState(null); - const [toggleLoading, setToggleLoading] = useState(null); const location = useLocation(); - const loadSchedules = async ({ search }) => { - const params = parseQueryString(QS_CONFIG, search); - setContentError(null); - setHasContentLoading(true); - try { + const { + result: { schedules, itemCount }, + error: contentError, + isLoading, + request: fetchSchedules, + } = useRequest( + useCallback(async () => { + const params = parseQueryString(QS_CONFIG, location.search); const { data: { count, results }, } = await SchedulesAPI.read(params); - - setSchedules(results); - setScheduleCount(count); - } catch (error) { - setContentError(error); - } finally { - setHasContentLoading(false); + return { + itemCount: count, + schedules: results, + }; + }, [location]), + { + schedules: [], + itemCount: 0, } - }; + ); useEffect(() => { - loadSchedules(location); - }, [location]); // eslint-disable-line react-hooks/exhaustive-deps + fetchSchedules(); + }, [fetchSchedules]); + + const isAllSelected = + selected.length === schedules.length && selected.length > 0; + + const { + isLoading: isDeleteLoading, + deleteItems: deleteJobs, + deletionError, + clearDeletionError, + } = useDeleteItems( + useCallback(async () => { + return Promise.all(selected.map(({ id }) => SchedulesAPI.destroy(id))); + }, [selected]), + { + qsConfig: QS_CONFIG, + allItemsSelected: isAllSelected, + fetchItems: fetchSchedules, + } + ); const handleSelectAll = isSelected => { setSelected(isSelected ? [...schedules] : []); @@ -66,64 +83,18 @@ function ScheduleList({ i18n }) { }; const handleDelete = async () => { - setHasContentLoading(true); - - try { - await Promise.all( - selected.map(schedule => SchedulesAPI.destroy(schedule.id)) - ); - } catch (error) { - setDeletionError(error); - } - - const params = parseQueryString(QS_CONFIG, location.search); - try { - const { - data: { count, results }, - } = await SchedulesAPI.read(params); - - setSchedules(results); - setScheduleCount(count); - setSelected([]); - } catch (error) { - setContentError(error); - } - - setHasContentLoading(false); + await deleteJobs(); + setSelected([]); }; - const handleScheduleToggle = async scheduleToToggle => { - setToggleLoading(scheduleToToggle.id); - try { - const { data: updatedSchedule } = await SchedulesAPI.update( - scheduleToToggle.id, - { - enabled: !scheduleToToggle.enabled, - } - ); - setSchedules( - schedules.map(schedule => - schedule.id === updatedSchedule.id ? updatedSchedule : schedule - ) - ); - } catch (err) { - setToggleError(err); - } finally { - setToggleLoading(null); - } - }; - - const isAllSelected = - selected.length > 0 && selected.length === schedules.length; - return ( ( @@ -131,9 +102,7 @@ function ScheduleList({ i18n }) { isSelected={selected.some(row => row.id === item.id)} key={item.id} onSelect={() => handleSelect(item)} - onToggleSchedule={handleScheduleToggle} schedule={item} - toggleLoading={toggleLoading === item.id} /> )} toolbarSearchColumns={[ @@ -176,23 +145,12 @@ function ScheduleList({ i18n }) { )} /> - {toggleError && !toggleLoading && ( - setToggleError(null)} - > - {i18n._(t`Failed to toggle schedule.`)} - - - )} {deletionError && ( setDeletionError(null)} + onClose={clearDeletionError} > {i18n._(t`Failed to delete one or more schedules.`)} diff --git a/awx/ui_next/src/screens/Schedule/ScheduleList/ScheduleListItem.jsx b/awx/ui_next/src/screens/Schedule/ScheduleList/ScheduleListItem.jsx index 0e6207ebe8..7d34f8b52d 100644 --- a/awx/ui_next/src/screens/Schedule/ScheduleList/ScheduleListItem.jsx +++ b/awx/ui_next/src/screens/Schedule/ScheduleList/ScheduleListItem.jsx @@ -11,15 +11,14 @@ import { DataListItem, DataListItemRow, DataListItemCells, - Switch, Tooltip, } from '@patternfly/react-core'; import { PencilAltIcon } from '@patternfly/react-icons'; - import { DetailList, Detail } from '@components/DetailList'; import styled from 'styled-components'; import { Schedule } from '@types'; import { formatDateString } from '@util/dates'; +import ScheduleToggle from '../shared/ScheduleToggle'; const DataListAction = styled(_DataListAction)` align-items: center; @@ -28,14 +27,7 @@ const DataListAction = styled(_DataListAction)` grid-template-columns: auto 40px; `; -function ScheduleListItem({ - i18n, - isSelected, - onSelect, - onToggleSchedule, - schedule, - toggleLoading, -}) { +function ScheduleListItem({ i18n, isSelected, onSelect, schedule }) { const labelId = `check-action-${schedule.id}`; const jobTypeLabels = { @@ -111,27 +103,7 @@ function ScheduleListItem({ id={labelId} key="actions" > - - onToggleSchedule(schedule)} - aria-label={i18n._(t`Toggle schedule`)} - /> - + {schedule.summary_fields.user_capabilities.edit && (