mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Pack flow: Refactor ManagePacksPage (#3376)
This commit is contained in:
parent
f6a74f69c9
commit
dc3143b437
5 changed files with 95 additions and 82 deletions
1
changes/issue-3281-packs-loading-state
Normal file
1
changes/issue-3281-packs-loading-state
Normal file
|
|
@ -0,0 +1 @@
|
|||
* ManagePacksPage loading state updated along with use of Context API
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)];
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue