Pack flow: Refactor ManagePacksPage (#3376)

This commit is contained in:
RachelElysia 2021-12-16 15:35:19 -08:00 committed by GitHub
parent f6a74f69c9
commit dc3143b437
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 82 deletions

View file

@ -0,0 +1 @@
* ManagePacksPage loading state updated along with use of Context API

View file

@ -116,6 +116,8 @@ describe(
// On the Settings pages, they should…
// See everything except for the “Teams” pages
cy.visit("/settings/organization");
cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting
cy.findByText(/teams/i).should("not.exist");
cy.get(".react-tabs").within(() => {
cy.findByText(/organization settings/i).should("exist");

View file

@ -1,17 +1,16 @@
import React, { useState, useCallback, useEffect } from "react";
import React, { useState, useCallback, useContext } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useQuery } from "react-query";
import { push } from "react-router-redux";
import pack, { IPack } from "interfaces/pack";
import { IUser } from "interfaces/user";
import { IPack } from "interfaces/pack";
import { IError } from "interfaces/errors";
// @ts-ignore
import packActions from "redux/nodes/entities/packs/actions";
import { AppContext } from "context/app";
import packsAPI from "services/entities/packs";
// @ts-ignore
import { renderFlash } from "redux/nodes/notifications/actions";
import paths from "router/paths";
import permissionUtils from "utilities/permissions";
import PATHS from "router/paths";
// @ts-ignore
import deepDifference from "utilities/deep_difference";
@ -21,17 +20,13 @@ import PacksListWrapper from "./components/PacksListWrapper";
import RemovePackModal from "./components/RemovePackModal";
const baseClass = "manage-packs-page";
interface IRootState {
auth: {
user: IUser;
};
entities: {
packs: {
isLoading: boolean;
data: IPack[];
errors: { name: string; reason: string }[];
};
};
interface IManagePacksPageProps {
router: any;
}
interface IPacksResponse {
packs: IPack[];
}
const renderTable = (
@ -39,45 +34,56 @@ const renderTable = (
onEnablePackClick: React.MouseEventHandler<HTMLButtonElement>,
onDisablePackClick: React.MouseEventHandler<HTMLButtonElement>,
onCreatePackClick: React.MouseEventHandler<HTMLButtonElement>,
packsList: IPack[],
packsErrors: { name: string; reason: string }[]
packs: IPack[] | undefined,
packsError: IError | null,
isLoadingPacks: boolean
): JSX.Element => {
if (Object.keys(packsErrors).length > 0) {
if (packsError) {
return <TableDataError />;
}
const isTableDataLoading = isLoadingPacks || packs === null;
return (
<PacksListWrapper
onRemovePackClick={onRemovePackClick}
onEnablePackClick={onEnablePackClick}
onDisablePackClick={onDisablePackClick}
onCreatePackClick={onCreatePackClick}
packsList={packsList}
packs={packs}
isLoading={isTableDataLoading}
/>
);
};
const ManagePacksPage = (): JSX.Element => {
const currentUser = useSelector((state: IRootState) => state.auth.user);
const isOnlyObserver = permissionUtils.isOnlyObserver(currentUser);
const ManagePacksPage = ({ router }: IManagePacksPageProps): JSX.Element => {
const { isOnlyObserver } = useContext(AppContext);
const dispatch = useDispatch();
const { NEW_PACK } = paths;
const onCreatePackClick = () => dispatch(push(NEW_PACK));
useEffect(() => {
dispatch(packActions.loadAll());
}, [dispatch]);
const packs = useSelector((state: IRootState) => state.entities.packs);
const packsList = Object.values(packs.data);
const packsErrors = packs.errors;
const onCreatePackClick = () => router.push(PATHS.NEW_PACK);
const [selectedPackIds, setSelectedPackIds] = useState<number[]>([]);
const [showRemovePackModal, setShowRemovePackModal] = useState<boolean>(
false
);
const {
data: packs,
error: packsError,
isLoading: isLoadingPacks,
refetch: refetchPacks,
} = useQuery<IPacksResponse, IError, IPack[]>(
"packs",
() => packsAPI.loadAll(),
{
// refetchOnMount: false,
// refetchOnReconnect: false,
refetchOnWindowFocus: false,
select: (data: IPacksResponse) => data.packs,
}
);
const toggleRemovePackModal = useCallback(() => {
setShowRemovePackModal(!showRemovePackModal);
}, [showRemovePackModal, setShowRemovePackModal]);
@ -91,7 +97,8 @@ const ManagePacksPage = (): JSX.Element => {
const packOrPacks = selectedPackIds.length === 1 ? "pack" : "packs";
const promises = selectedPackIds.map((id: number) => {
return dispatch(packActions.destroy({ id }));
packsAPI.destroy(id);
return null;
});
return Promise.all(promises)
@ -100,7 +107,6 @@ const ManagePacksPage = (): JSX.Element => {
renderFlash("success", `Successfully deleted ${packOrPacks}.`)
);
toggleRemovePackModal();
dispatch(packActions.loadAll());
})
.catch(() => {
dispatch(
@ -109,9 +115,12 @@ const ManagePacksPage = (): JSX.Element => {
`Unable to remove ${packOrPacks}. Please try again.`
)
);
})
.finally(() => {
refetchPacks();
toggleRemovePackModal();
});
}, [dispatch, selectedPackIds, toggleRemovePackModal]);
}, [dispatch, refetchPacks, selectedPackIds, toggleRemovePackModal]);
const onEnableDisablePackSubmit = useCallback(
(selectedTablePackIds: any, disablePack: boolean) => {
@ -119,7 +128,8 @@ const ManagePacksPage = (): JSX.Element => {
const enableOrDisable = disablePack ? "disabled" : "enabled";
const promises = selectedTablePackIds.map((id: number) => {
return dispatch(packActions.update({ id }, { disabled: disablePack }));
packsAPI.update(id, { disabled: disablePack });
return null;
});
return Promise.all(promises)
@ -130,7 +140,6 @@ const ManagePacksPage = (): JSX.Element => {
`Successfully ${enableOrDisable} selected ${packOrPacks}.`
)
);
dispatch(packActions.loadAll());
})
.catch(() => {
dispatch(
@ -139,9 +148,12 @@ const ManagePacksPage = (): JSX.Element => {
`Unable to ${enableOrDisable} selected ${packOrPacks}. Please try again.`
)
);
})
.finally(() => {
refetchPacks();
});
},
[dispatch, selectedPackIds]
[dispatch, refetchPacks, selectedPackIds]
);
const onEnablePackClick = (selectedTablePackIds: any) => {
@ -171,7 +183,7 @@ const ManagePacksPage = (): JSX.Element => {
</div>
</div>
</div>
{!isOnlyObserver && packsList.length > 0 && (
{!isOnlyObserver && packs && packs.length > 0 && (
<div className={`${baseClass}__action-button-container`}>
<Button
variant="brand"
@ -184,14 +196,15 @@ const ManagePacksPage = (): JSX.Element => {
)}
</div>
<div>
{!packs.isLoading &&
{!isLoadingPacks &&
renderTable(
onRemovePackClick,
onEnablePackClick,
onDisablePackClick,
onCreatePackClick,
packsList,
packsErrors
packs,
packsError,
isLoadingPacks
)}
</div>
{showRemovePackModal && (

View file

@ -18,19 +18,14 @@ interface IPacksListWrapperProps {
onEnablePackClick: any;
onDisablePackClick: any;
onCreatePackClick: any;
packsList: IPack[];
packs?: IPack[];
isLoading: boolean;
}
interface IRootState {
auth: {
user: IUser;
};
entities: {
packs: {
isLoading: boolean;
data: IPack[];
};
};
}
const PacksListWrapper = ({
@ -38,29 +33,28 @@ const PacksListWrapper = ({
onEnablePackClick,
onDisablePackClick,
onCreatePackClick,
packsList,
packs,
isLoading,
}: IPacksListWrapperProps): JSX.Element => {
const loadingTableData = useSelector(
(state: IRootState) => state.entities.packs.isLoading
);
const currentUser = useSelector((state: IRootState) => state.auth.user);
const isOnlyObserver = permissionUtils.isOnlyObserver(currentUser);
const [filteredPacks, setFilteredPacks] = useState<IPack[]>(packsList);
const [filteredPacks, setFilteredPacks] = useState<IPack[] | undefined>(
packs
);
const [searchString, setSearchString] = useState<string>("");
useEffect(() => {
setFilteredPacks(packsList);
}, [packsList]);
setFilteredPacks(packs);
}, [packs]);
useEffect(() => {
setFilteredPacks(() => {
return packsList.filter((pack) => {
return packs?.filter((pack) => {
return pack.name.toLowerCase().includes(searchString.toLowerCase());
});
});
}, [packsList, searchString, setFilteredPacks]);
}, [packs, searchString, setFilteredPacks]);
const onQueryChange = useCallback(
(queryData) => {
@ -129,14 +123,14 @@ const PacksListWrapper = ({
resultsTitle={"packs"}
columns={tableHeaders}
data={generateDataSet(filteredPacks)}
isLoading={loadingTableData}
isLoading={isLoading}
defaultSortHeader={"pack"}
defaultSortDirection={"desc"}
showMarkAllPages={false}
isAllPagesSelected={false}
onQueryChange={onQueryChange}
inputPlaceHolder="Search by name"
searchable={packsList.length > 0}
searchable={packs && packs.length > 0}
disablePagination
onPrimarySelectActionClick={onRemovePackClick}
primarySelectActionButtonVariant="text-icon"

View file

@ -47,12 +47,12 @@ interface IDataColumn {
}
interface IPackTableData {
id: number;
name: string;
query_count: number;
status: string;
total_hosts_count: number;
updated_at: string;
id?: number;
name?: string;
query_count?: number;
status?: string;
total_hosts_count?: number;
updated_at?: string;
}
// NOTE: cellProps come from react-table
@ -146,20 +146,23 @@ const generateTableHeaders = (isOnlyObserver = true): IDataColumn[] => {
return tableHeaders;
};
const enhancePackData = (packs: IPack[]): IPackTableData[] => {
return packs.map((pack: IPack) => {
return {
id: pack.id,
name: pack.name,
query_count: pack.query_count,
status: pack.disabled ? "disabled" : "enabled",
total_hosts_count: pack.total_hosts_count,
updated_at: pack.updated_at,
};
});
const enhancePackData = (packs: IPack[] | undefined): IPackTableData[] => {
if (packs) {
return packs.map((pack: IPack) => {
return {
id: pack.id,
name: pack.name,
query_count: pack.query_count,
status: pack.disabled ? "disabled" : "enabled",
total_hosts_count: pack.total_hosts_count,
updated_at: pack.updated_at,
};
});
}
return [];
};
const generateDataSet = (packs: IPack[]): IPackTableData[] => {
const generateDataSet = (packs: IPack[] | undefined): IPackTableData[] => {
return [...enhancePackData(packs)];
};