Fixed Android Enterprise page not refreshing (#45914)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #45862 Speculative fixes since I wasn't able to repro issue locally. Refresh AppContext and the React Query ["config"] cache directly after the SSE/DELETE response so AndroidMdmCard and AndroidMdmPage flip without a manual page reload, with a bounded retry to defeat the 1s cached_mysql.AppConfig TTL. Also harden startSSE to detect the success signal across chunk boundaries and reject (rather than hang) when the stream ends without it. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Timeouts are implemented and retries are limited to avoid infinite loops ## Testing - [x] QA'd all new/changed functionality manually For unreleased bug fixes in a release candidate, one of: - [x] Confirmed that the fix is not expected to adversely impact load test results <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed Android Enterprise page not refreshing after connecting or disconnecting Android MDM. The Enterprise ID and card state now update automatically without requiring a manual page reload. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45914?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- Fixed Android Enterprise page not refreshing after connecting or disconnecting Android MDM, so the Enterprise ID and card state are visible without a manual page reload.
|
||||
+26
-8
@@ -6,11 +6,12 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
import { InjectedRouter } from "react-router";
|
||||
import { useQuery } from "react-query";
|
||||
import { useQuery, useQueryClient } from "react-query";
|
||||
|
||||
import PATHS from "router/paths";
|
||||
import { AppContext } from "context/app";
|
||||
import { NotificationContext } from "context/notification";
|
||||
import { IConfig } from "interfaces/config";
|
||||
import { getErrorReason } from "interfaces/errors";
|
||||
import mdmAndroidAPI from "services/entities/mdm_android";
|
||||
import { DEFAULT_USE_QUERY_OPTIONS, SUPPORT_LINK } from "utilities/constants";
|
||||
@@ -37,6 +38,8 @@ interface ITurnOnAndroidMdmProps {
|
||||
|
||||
const TurnOnAndroidMdm = ({ router }: ITurnOnAndroidMdmProps) => {
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
const { setConfig } = useContext(AppContext);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// TODO: figure out issue with aborting the SSE fetch when the window is closed
|
||||
const newWindow = useRef<Window | null>(null);
|
||||
@@ -48,18 +51,33 @@ const TurnOnAndroidMdm = ({ router }: ITurnOnAndroidMdmProps) => {
|
||||
async (abortController: AbortController) => {
|
||||
try {
|
||||
await mdmAndroidAPI.startSSE(abortController.signal);
|
||||
abortController.abort();
|
||||
renderFlash("success", "Android MDM turned on successfully.", {
|
||||
persistOnPageChange: true,
|
||||
});
|
||||
setSetupSse(false);
|
||||
router.push(PATHS.ADMIN_INTEGRATIONS_MDM);
|
||||
} catch {
|
||||
renderFlash("error", "Couldn't turn on Android MDM. Please try again.");
|
||||
setSetupSse(false);
|
||||
return;
|
||||
}
|
||||
abortController.abort();
|
||||
// SSE success means the backend has already set
|
||||
// android_enabled_and_configured=true. Patch the in-memory config so
|
||||
// AppContext.isAndroidMdmEnabledAndConfigured flips immediately and
|
||||
// AndroidMdmCard renders correctly on redirect, without a synchronous
|
||||
// round-trip.
|
||||
const prevConfig = queryClient.getQueryData<IConfig>(["config"]);
|
||||
if (prevConfig) {
|
||||
const patched: IConfig = {
|
||||
...prevConfig,
|
||||
mdm: { ...prevConfig.mdm, android_enabled_and_configured: true },
|
||||
};
|
||||
setConfig(patched);
|
||||
queryClient.setQueryData(["config"], patched);
|
||||
}
|
||||
renderFlash("success", "Android MDM turned on successfully.", {
|
||||
persistOnPageChange: true,
|
||||
});
|
||||
setSetupSse(false);
|
||||
router.push(PATHS.ADMIN_INTEGRATIONS_MDM);
|
||||
},
|
||||
[renderFlash, router]
|
||||
[queryClient, renderFlash, router, setConfig]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
+23
-5
@@ -1,9 +1,12 @@
|
||||
import React, { useCallback, useContext, useState } from "react";
|
||||
import { InjectedRouter } from "react-router";
|
||||
import { useQueryClient } from "react-query";
|
||||
|
||||
import PATHS from "router/paths";
|
||||
import mdmAndroidAPI from "services/entities/mdm_android";
|
||||
import { AppContext } from "context/app";
|
||||
import { NotificationContext } from "context/notification";
|
||||
import { IConfig } from "interfaces/config";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
import Button from "components/buttons/Button";
|
||||
@@ -20,6 +23,8 @@ const TurnOffAndroidMdmModal = ({
|
||||
router,
|
||||
}: ITurnOffAndroidMdmModalProps) => {
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
const { setConfig } = useContext(AppContext);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
@@ -27,15 +32,28 @@ const TurnOffAndroidMdmModal = ({
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
await mdmAndroidAPI.turnOffAndroidMdm();
|
||||
renderFlash("success", "Android MDM turned off successfully.", {
|
||||
persistOnPageChange: true,
|
||||
});
|
||||
router.push(PATHS.ADMIN_INTEGRATIONS_MDM);
|
||||
} catch (e) {
|
||||
onExit();
|
||||
renderFlash("error", "Couldn't turn off Android MDM. Please try again.");
|
||||
return;
|
||||
}
|
||||
}, [onExit, renderFlash, router]);
|
||||
// DELETE success means the backend has already cleared
|
||||
// android_enabled_and_configured. Patch the in-memory config so the
|
||||
// parent MDM page's card flips immediately on redirect.
|
||||
const prevConfig = queryClient.getQueryData<IConfig>(["config"]);
|
||||
if (prevConfig) {
|
||||
const patched: IConfig = {
|
||||
...prevConfig,
|
||||
mdm: { ...prevConfig.mdm, android_enabled_and_configured: false },
|
||||
};
|
||||
setConfig(patched);
|
||||
queryClient.setQueryData(["config"], patched);
|
||||
}
|
||||
renderFlash("success", "Android MDM turned off successfully.", {
|
||||
persistOnPageChange: true,
|
||||
});
|
||||
router.push(PATHS.ADMIN_INTEGRATIONS_MDM);
|
||||
}, [onExit, queryClient, renderFlash, router, setConfig]);
|
||||
|
||||
return (
|
||||
<Modal title="Turn off Android MDM" className={baseClass} onExit={onExit}>
|
||||
|
||||
@@ -44,18 +44,32 @@ export default {
|
||||
});
|
||||
|
||||
const reader = response?.body?.getReader();
|
||||
if (!reader) {
|
||||
reject(new Error("Android MDM SSE stream unavailable"));
|
||||
return;
|
||||
}
|
||||
const decoder = new TextDecoder();
|
||||
const successSignal = "Android Enterprise successfully connected";
|
||||
// Buffer accumulates decoded text so a success message split across
|
||||
// multiple chunks (valid with chunked transfer encoding) is still
|
||||
// detected.
|
||||
let buffer = "";
|
||||
|
||||
while (true) {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const { done, value } = await reader?.read();
|
||||
if (done) break;
|
||||
const text = decoder.decode(value);
|
||||
if (text === "Android Enterprise successfully connected") {
|
||||
resolve();
|
||||
break;
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
// Server closed the stream without ever sending success.
|
||||
// Reject so callers don't await forever on unmount or backend hiccup.
|
||||
reject(new Error("Android MDM SSE ended before success signal"));
|
||||
return;
|
||||
}
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
if (buffer.includes(successSignal)) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
buffer = buffer.slice(-successSignal.length);
|
||||
}
|
||||
} catch (error) {
|
||||
if ((error as Error).name === "AbortError") {
|
||||
|
||||
Reference in New Issue
Block a user