feature: clear filters when navigating down and restore filters when navigating up folders in secrets dashboard

This commit is contained in:
Scott Wilson
2024-10-01 09:26:25 -07:00
parent acde0867a0
commit fbf52850e8
13 changed files with 122 additions and 52 deletions
@@ -79,7 +79,7 @@ export const SecretMainPage = () => {
ProjectPermissionSub.SecretRollback
);
const [filter, setFilter] = useState<Filter>({
const defaultFilterState = {
tags: {},
searchFilter: (router.query.searchFilter as string) || "",
include: {
@@ -88,8 +88,11 @@ export const SecretMainPage = () => {
[RowType.DynamicSecret]: canReadSecret,
[RowType.Secret]: canReadSecret
}
});
const debouncedSearchFilter = useDebounce(filter.searchFilter);
};
const [filter, setFilter] = useState<Filter>(defaultFilterState);
const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(filter.searchFilter);
const [filterHistory, setFilterHistory] = useState<Map<string, Filter>>(new Map());
// change filters if permissions change at different paths/env
useEffect(() => {
@@ -255,10 +258,39 @@ export const SecretMainPage = () => {
if (totalCount < paginationOffset) setPage(1);
}, [totalCount]);
useEffect(() => {
// restore filters for path if set
const restore = filterHistory.get(secretPath);
setFilter(restore ?? defaultFilterState);
setDebouncedSearchFilter(restore?.searchFilter ?? "");
const { searchFilter, ...query } = router.query;
// this is a temp work around until we fully transition state to query params,
// setting the initial search filter by query and then moving it to internal state
if (router.query.searchFilter) {
router.push({
pathname: router.pathname,
query
});
}
}, [secretPath]);
if (isDetailsLoading) {
return <ContentLoader text={LOADER_TEXT} />;
}
const handleResetFilter = () => {
// store for breadcrumb nav to restore previously used filters
setFilterHistory((prev) => {
const curr = new Map(prev);
curr.set(secretPath, filter);
return curr;
});
setFilter(defaultFilterState);
setDebouncedSearchFilter("");
};
return (
<StoreProvider>
<div className="container mx-auto flex flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]">
@@ -339,6 +371,7 @@ export const SecretMainPage = () => {
environment={environment}
workspaceId={workspaceId}
secretPath={secretPath}
onNavigateToFolder={handleResetFilter}
/>
)}
{canReadSecret && dynamicSecrets?.length && (
@@ -1,4 +1,3 @@
import { useState } from "react";
import { TypeOptions } from "react-toastify";
import { subject } from "@casl/ability";
import {
@@ -56,7 +55,6 @@ import { usePopUp } from "@app/hooks";
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
import { SecretType, SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
import { debounce } from "@app/lib/fn/debounce";
import {
PopUpNames,
@@ -118,7 +116,6 @@ export const ActionBar = ({
] as const);
const { subscription } = useSubscription();
const { openPopUp } = usePopUpAction();
const [search, setSearch] = useState(filter.searchFilter);
const { mutateAsync: createFolder } = useCreateFolder();
const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch();
@@ -134,8 +131,6 @@ export const ActionBar = ({
(rule.subject as ProjectPermissionSub[]).includes(ProjectPermissionSub.SecretFolders)
);
const debouncedOnSearch = debounce(onSearchChange, 500);
const handleFolderCreate = async (folderName: string) => {
try {
await createFolder({
@@ -286,11 +281,15 @@ export const ActionBar = ({
<Input
className="bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
placeholder="Search by folder name, key name, comment..."
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
value={search}
leftIcon={
<FontAwesomeIcon
className={filter.searchFilter ? "text-primary" : ""}
icon={faMagnifyingGlass}
/>
}
value={filter.searchFilter}
onChange={(evt) => {
setSearch(evt.target.value);
debouncedOnSearch(evt.target.value);
onSearchChange(evt.target.value);
}}
/>
</div>
@@ -18,13 +18,15 @@ type Props = {
environment: string;
workspaceId: string;
secretPath?: string;
onNavigateToFolder: (path: string) => void;
};
export const FolderListView = ({
folders = [],
environment,
workspaceId,
secretPath = "/"
secretPath = "/",
onNavigateToFolder
}: Props) => {
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
"updateFolder",
@@ -88,13 +90,16 @@ export const FolderListView = ({
};
const handleFolderClick = (name: string) => {
router.push({
pathname: router.pathname,
query: {
...router.query,
secretPath: `${router.query?.secretPath || ""}/${name}`
}
});
const path = `${router.query?.secretPath || ""}/${name}`;
router
.push({
pathname: router.pathname,
query: {
...router.query,
secretPath: path
}
})
.then(() => onNavigateToFolder(path));
};
return (
@@ -84,7 +84,7 @@ export const CopySecretsFromBoard = ({
const envCopySecPath = watch("secretPath");
const selectedEnvSlug = watch("environment");
const debouncedEnvCopySecretPath = useDebounce(envCopySecPath);
const [debouncedEnvCopySecretPath] = useDebounce(envCopySecPath);
const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({
workspaceId,