Skip to content

Commit

Permalink
Handle timeouts in monitor queries in the UI (#5519)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpople authored Nov 20, 2024
1 parent 4702281 commit 3480266
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 11 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The types of changes are:
- The CMP override `fides_privacy_policy_url` will now apply even if the `fides_override_language` doesn't match [#5515](https://github.com/ethyca/fides/pull/5515)
- Updated POST taxonomy endpoints to handle creating resources without specifying fides_key [#5468](https://github.com/ethyca/fides/pull/5468)
- Disabled connection pooling for task workers and added retries and keep-alive configurations for database connections [#5448](https://github.com/ethyca/fides/pull/5448)
- Added timeout handling in the UI for async discovery monitor-related queries [#5519](https://github.com/ethyca/fides/pull/5519)

### Developer Experience
- Migrated several instances of Chakra's Select component to use Ant's Select component [#5475](https://github.com/ethyca/fides/pull/5475)
Expand All @@ -45,6 +46,9 @@ The types of changes are:
- Fixing issue where "privacy request received" emails would not be sent if the request had custom identities [#5518](https://github.com/ethyca/fides/pull/5518)
- Fixed issue with long-running privacy request tasks losing their connection to the database [#5500](https://github.com/ethyca/fides/pull/5500)

### Docs
- Added docs for PrivacyNoticeRegion type [#5488](https://github.com/ethyca/fides/pull/5488)

### Security
- Password Policy is now Enforced in Accept Invite API [CVE-2024-52008](https://github.com/ethyca/fides/security/advisories/GHSA-v7vm-rhmg-8j2r)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { AntButton as Button, Flex, Text, Tooltip } from "fidesui";
import { AntButton as Button, Flex, Text, Tooltip, useToast } from "fidesui";

import FidesSpinner from "~/features/common/FidesSpinner";
import { usePaginatedPicker } from "~/features/common/hooks/usePicker";
import QuestionTooltip from "~/features/common/QuestionTooltip";
import { DEFAULT_TOAST_PARAMS } from "~/features/common/toast";
import MonitorDatabasePicker from "~/features/integrations/configure-monitor/MonitorDatabasePicker";
import useCumulativeGetDatabases from "~/features/integrations/configure-monitor/useCumulativeGetDatabases";
import { MonitorConfig } from "~/types/api";

const TOOLTIP_COPY =
"Selecting a project will monitor all current and future datasets within that project.";
const TIMEOUT_COPY =
"Loading resources is taking longer than expected. The monitor has been saved and is tracking all available resources. You can return later to limit its scope if needed";

const ConfigureMonitorDatabasesForm = ({
monitor,
isEditing,
Expand All @@ -23,13 +28,26 @@ const ConfigureMonitorDatabasesForm = ({
onSubmit: (monitor: MonitorConfig) => void;
onClose: () => void;
}) => {
const toast = useToast();

const handleTimeout = () => {
onSubmit({ ...monitor, databases: [] });
toast({
...DEFAULT_TOAST_PARAMS,
status: "info",
description: TIMEOUT_COPY,
});
onClose();
};

const {
databases,
totalDatabases: totalRows,
fetchMore,
reachedEnd,
isLoading: refetchPending,
} = useCumulativeGetDatabases(integrationKey);
initialIsLoading,
} = useCumulativeGetDatabases(integrationKey, handleTimeout);

const initialSelected = monitor?.databases ?? [];

Expand Down Expand Up @@ -58,6 +76,10 @@ const ConfigureMonitorDatabasesForm = ({

const saveIsDisabled = !allSelected && selected.length === 0;

if (initialIsLoading) {
return <FidesSpinner my={12} />;
}

return (
<>
<Flex p={4} direction="column">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { UseDisclosureReturn } from "fidesui";
import { UseDisclosureReturn, useToast } from "fidesui";

import FidesSpinner from "~/features/common/FidesSpinner";
import useQueryResultToast from "~/features/common/form/useQueryResultToast";
import FormModal from "~/features/common/modals/FormModal";
import { DEFAULT_TOAST_PARAMS } from "~/features/common/toast";
import {
useGetDatabasesByConnectionQuery,
usePutDiscoveryMonitorMutation,
Expand All @@ -14,7 +15,11 @@ import {
ConnectionSystemTypeMap,
MonitorConfig,
} from "~/types/api";
import { isErrorResult } from "~/types/errors";
import { isErrorResult, RTKResult } from "~/types/errors";

const TIMEOUT_DELAY = 5000;
const TIMEOUT_COPY =
"Saving this monitor is taking longer than expected. Fides will continue processing it in the background, and you can check back later to view the updates.";

const ConfigureMonitorModal = ({
isOpen,
Expand Down Expand Up @@ -44,6 +49,8 @@ const ConfigureMonitorModal = ({

const databasesAvailable = !!databases && !!databases.total;

const toast = useToast();

const { toastResult } = useQueryResultToast({
defaultSuccessMsg: `Monitor ${
isEditing ? "updated" : "created"
Expand All @@ -54,10 +61,24 @@ const ConfigureMonitorModal = ({
});

const handleSubmit = async (values: MonitorConfig) => {
const result = await putMonitorMutationTrigger(values);
toastResult(result);
if (!isErrorResult(result)) {
onClose();
let result: RTKResult | undefined;
const timeout = setTimeout(() => {
if (!result) {
toast({
...DEFAULT_TOAST_PARAMS,
status: "info",
description: TIMEOUT_COPY,
});
onClose();
}
}, TIMEOUT_DELAY);
result = await putMonitorMutationTrigger(values);
if (result) {
clearTimeout(timeout);
toastResult(result);
if (!isErrorResult(result)) {
onClose();
}
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useState } from "react";
import { useEffect, useRef, useState } from "react";

import {
useGetDatabasesByConnectionQuery,
useLazyGetDatabasesByConnectionQuery,
} from "~/features/data-discovery-and-detection/discovery-detection.slice";

const TIMEOUT_DELAY = 5000;

const EMPTY_RESPONSE = {
items: [] as string[],
total: 1,
Expand All @@ -13,22 +15,45 @@ const EMPTY_RESPONSE = {
pages: 0,
};

const useCumulativeGetDatabases = (integrationKey: string) => {
const useCumulativeGetDatabases = (
integrationKey: string,
onTimeout?: () => void,
) => {
const [nextPage, setNextPage] = useState(2);

const { data: initialResult, isLoading: initialIsLoading } =
useGetDatabasesByConnectionQuery({
page: 1,
size: 25,
connection_config_key: integrationKey,
});

const initialLoadingRef = useRef(initialIsLoading);

const { items: initialDatabases, total: totalDatabases } =
initialResult ?? EMPTY_RESPONSE;

const reachedEnd = !!initialResult?.pages && nextPage > initialResult.pages;

const [databases, setDatabases] = useState<string[]>(initialDatabases);

useEffect(() => {
initialLoadingRef.current = initialIsLoading;
// this needs to be in this hook or else it will be set to [] instead of the actual result
setDatabases(initialDatabases);
}, [initialIsLoading, initialDatabases]);

useEffect(() => {
const t = setTimeout(() => {
if (initialLoadingRef.current && onTimeout) {
onTimeout();
}
}, TIMEOUT_DELAY);
return () => clearTimeout(t);
// this should only run once on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const [
refetchTrigger,
{ isLoading: refetchIsLoading, isFetching: refetchIsFetching },
Expand All @@ -52,7 +77,14 @@ const useCumulativeGetDatabases = (integrationKey: string) => {
setDatabases([...databases, ...(result.data?.items ?? [])]);
};

return { databases, totalDatabases, fetchMore, isLoading, reachedEnd };
return {
databases,
totalDatabases,
fetchMore,
initialIsLoading,
isLoading,
reachedEnd,
};
};

export default useCumulativeGetDatabases;

0 comments on commit 3480266

Please sign in to comment.