Fleet UI: Disable all live query/policy buttons when globally disabled (#14821)

This commit is contained in:
RachelElysia 2023-11-01 09:25:39 -04:00 committed by GitHub
parent ae6c387059
commit 9ad0962de2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 275 additions and 53 deletions

View File

@ -0,0 +1 @@
- If live queries are disabled globally, all UI buttons to run a live query or policy are disabled with a tooltip explanation

View File

@ -55,6 +55,7 @@ const PolicyPage = ({
isGlobalAdmin,
isGlobalMaintainer,
isAnyTeamMaintainerOrTeamAdmin,
config,
} = useContext(AppContext);
const {
lastEditedQueryBody,
@ -225,7 +226,7 @@ const PolicyPage = ({
};
const renderLiveQueryWarning = (): JSX.Element | null => {
if (isLiveQueryRunnable) {
if (isLiveQueryRunnable || config?.server_settings.live_query_disabled) {
return null;
}

View File

@ -4,6 +4,7 @@ import { createCustomRenderer } from "test/test-utils";
import createMockPolicy from "__mocks__/policyMock";
import createMockUser from "__mocks__/userMock";
import createMockConfig from "__mocks__/configMock";
import PolicyForm from "./PolicyForm";
@ -38,6 +39,7 @@ describe("PolicyForm - component", () => {
isOnGlobalTeam: true,
isPremiumTier: true,
isSandboxMode: false,
config: createMockConfig(),
},
},
});
@ -93,6 +95,7 @@ describe("PolicyForm - component", () => {
isOnGlobalTeam: true,
isPremiumTier: true,
isSandboxMode: false,
config: createMockConfig(),
},
},
});
@ -122,9 +125,76 @@ describe("PolicyForm - component", () => {
await user.hover(screen.getByRole("button", { name: "Save" }));
expect(
container.querySelector("#policy-form__button-wrap--tooltip")
).toHaveTextContent(/to save or run the policy/i);
expect(container.querySelector("#save-policy-button")).toHaveTextContent(
/to save or run the policy/i
);
});
it("disables run button with tooltip for globally disabled queries", async () => {
const render = createCustomRenderer({
context: {
policy: {
policyTeamId: undefined,
lastEditedQueryId: mockPolicy.id,
lastEditedQueryName: mockPolicy.name,
lastEditedQueryDescription: mockPolicy.description,
lastEditedQueryBody: mockPolicy.query,
lastEditedQueryResolution: mockPolicy.resolution,
lastEditedQueryCritical: mockPolicy.critical,
lastEditedQueryPlatform: undefined, // missing policy platforms
defaultPolicy: false,
setLastEditedQueryName: jest.fn(),
setLastEditedQueryDescription: jest.fn(),
setLastEditedQueryBody: jest.fn(),
setLastEditedQueryResolution: jest.fn(),
setLastEditedQueryCritical: jest.fn(),
setLastEditedQueryPlatform: jest.fn(),
},
app: {
currentUser: createMockUser(),
isGlobalObserver: false,
isGlobalAdmin: true,
isGlobalMaintainer: false,
isOnGlobalTeam: true,
isPremiumTier: true,
isSandboxMode: false,
config: createMockConfig({
server_settings: {
...createMockConfig().server_settings,
live_query_disabled: true, // Live query disabled
},
}),
},
},
});
const { container, user } = render(
<PolicyForm
policyIdForEdit={mockPolicy.id}
showOpenSchemaActionText={false}
storedPolicy={createMockPolicy()}
isStoredPolicyLoading={false}
isTeamAdmin={false}
isTeamMaintainer={false}
isTeamObserver={false}
isUpdatingPolicy={false}
onCreatePolicy={jest.fn()}
onOsqueryTableSelect={jest.fn()}
goToSelectTargets={jest.fn()}
onUpdate={jest.fn()}
onOpenSchemaSidebar={jest.fn()}
renderLiveQueryWarning={jest.fn()}
backendValidators={{}}
/>
);
expect(screen.getByRole("button", { name: "Run" })).toBeDisabled();
await user.hover(screen.getByRole("button", { name: "Run" }));
expect(container.querySelector("#run-policy-button")).toHaveTextContent(
/live queries are disabled/i
);
});
// TODO: Consider testing save button is disabled for a sql error

View File

@ -6,6 +6,7 @@ import ReactTooltip from "react-tooltip";
import { useDebouncedCallback } from "use-debounce";
import { size } from "lodash";
import classnames from "classnames";
import { COLORS } from "styles/var/colors";
import { addGravatarUrlToResource } from "utilities/helpers";
import { AppContext } from "context/app";
@ -117,6 +118,7 @@ const PolicyForm = ({
isOnGlobalTeam,
isPremiumTier,
isSandboxMode,
config,
} = useContext(AppContext);
const debounceSQL = useDebouncedCallback((sql: string) => {
@ -152,6 +154,8 @@ const PolicyForm = ({
!policyIdForEdit &&
DEFAULT_POLICIES.find((p) => p.name === lastEditedQueryName);
const disabledLiveQuery = config?.server_settings.live_query_disabled;
useEffect(() => {
if (isNewTemplatePolicy) {
setCompatiblePlatforms(lastEditedQueryBody);
@ -550,7 +554,7 @@ const PolicyForm = ({
<span
className={`${baseClass}__button-wrap--tooltip`}
data-tip
data-for={`${baseClass}__button-wrap--tooltip`}
data-for="save-policy-button"
data-tip-disable={!isEditMode || isAnyPlatformSelected}
>
<Button
@ -567,8 +571,8 @@ const PolicyForm = ({
className={`${baseClass}__button-wrap--tooltip`}
place="bottom"
effect="solid"
id={`${baseClass}__button-wrap--tooltip`}
backgroundColor="#3e4771"
id="save-policy-button"
backgroundColor={COLORS["tooltip-bg"]}
>
Select the platform(s) this
<br />
@ -581,14 +585,18 @@ const PolicyForm = ({
<span
className={`${baseClass}__button-wrap--tooltip`}
data-tip
data-for={`${baseClass}__button-wrap--tooltip`}
data-tip-disable={!isEditMode || isAnyPlatformSelected}
data-for="run-policy-button"
data-tip-disable={
(!isEditMode || isAnyPlatformSelected) && !disabledLiveQuery
}
>
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={goToSelectTargets}
disabled={isEditMode && !isAnyPlatformSelected}
disabled={
(isEditMode && !isAnyPlatformSelected) || disabledLiveQuery
}
>
Run
</Button>
@ -597,14 +605,19 @@ const PolicyForm = ({
className={`${baseClass}__button-wrap--tooltip`}
place="bottom"
effect="solid"
id={`${baseClass}__button-wrap--tooltip`}
backgroundColor="#3e4771"
id="run-policy-button"
backgroundColor={COLORS["tooltip-bg"]}
data-html
>
Select the platform(s) this
<br />
policy will be checked on
<br />
to save or run the policy.
{disabledLiveQuery ? (
<>Live queries are disabled in organization settings</>
) : (
<>
Select the platform(s) this <br />
policy will be checked on <br />
to save or run the policy.
</>
)}
</ReactTooltip>
</div>
</form>

View File

@ -2,6 +2,8 @@ import React, { useContext, useState, useEffect } from "react";
import { useQuery } from "react-query";
import { InjectedRouter, Params } from "react-router/lib/Router";
import { useErrorHandler } from "react-error-boundary";
import ReactTooltip from "react-tooltip";
import { COLORS } from "styles/var/colors";
import PATHS from "router/paths";
import { AppContext } from "context/app";
@ -171,6 +173,7 @@ const QueryDetailsPage = ({
const isApiError = storedQueryError || queryReportError;
const isClipped =
(queryReport?.results?.length ?? 0) >= QUERY_REPORT_RESULTS_LIMIT;
const disabledLiveQuery = config?.server_settings.live_query_disabled;
const renderHeader = () => {
const canEditQuery =
@ -216,15 +219,33 @@ const QueryDetailsPage = ({
<div
className={`${baseClass}__button-wrap ${baseClass}__button-wrap--new-query`}
>
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={() => {
queryId && router.push(PATHS.LIVE_QUERY(queryId));
}}
<div
data-tip
data-for="live-query-button"
// Tooltip shows when live queries are globally disabled
data-tip-disable={!disabledLiveQuery}
>
Live query
</Button>
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={() => {
queryId && router.push(PATHS.LIVE_QUERY(queryId));
}}
disabled={disabledLiveQuery}
>
Live query
</Button>
</div>
<ReactTooltip
className="live-query-button-tooltip"
place="top"
effect="solid"
backgroundColor={COLORS["tooltip-bg"]}
id="live-query-button"
data-html
>
Live queries are disabled in organization settings
</ReactTooltip>
</div>
)}
</div>

View File

@ -68,6 +68,7 @@ const EditQueryPage = ({
isAnyTeamMaintainerOrTeamAdmin,
isObserverPlus,
isAnyTeamObserverPlus,
config,
} = useContext(AppContext);
const {
selectedOsqueryTable,
@ -293,7 +294,7 @@ const EditQueryPage = ({
};
const renderLiveQueryWarning = (): JSX.Element | null => {
if (isLiveQueryRunnable) {
if (isLiveQueryRunnable || config?.server_settings.live_query_disabled) {
return null;
}

View File

@ -4,6 +4,7 @@ import { createCustomRenderer } from "test/test-utils";
import createMockQuery from "__mocks__/queryMock";
import createMockUser from "__mocks__/userMock";
import createMockConfig from "__mocks__/configMock";
import EditQueryForm from "./EditQueryForm";
@ -51,6 +52,7 @@ describe("EditQueryForm - component", () => {
isOnGlobalTeam: true,
isPremiumTier: true,
isSandboxMode: false,
config: createMockConfig(),
},
},
});
@ -80,6 +82,76 @@ describe("EditQueryForm - component", () => {
expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
});
it("disables live query button for globally disabled live queries", async () => {
const render = createCustomRenderer({
context: {
query: {
lastEditedQueryId: mockQuery.id,
lastEditedQueryName: "", // missing query name
lastEditedQueryDescription: mockQuery.description,
lastEditedQueryBody: mockQuery.query,
lastEditedQueryObserverCanRun: mockQuery.observer_can_run,
lastEditedQueryFrequency: mockQuery.interval,
lastEditedQueryPlatforms: mockQuery.platform,
lastEditedQueryMinOsqueryVersion: mockQuery.min_osquery_version,
lastEditedQueryLoggingType: mockQuery.logging,
setLastEditedQueryName: jest.fn(),
setLastEditedQueryDescription: jest.fn(),
setLastEditedQueryBody: jest.fn(),
setLastEditedQueryObserverCanRun: jest.fn(),
setLastEditedQueryFrequency: jest.fn(),
setLastEditedQueryPlatforms: jest.fn(),
setLastEditedQueryMinOsqueryVersion: jest.fn(),
setLastEditedQueryLoggingType: jest.fn(),
},
app: {
currentUser: createMockUser(),
isGlobalObserver: false,
isGlobalAdmin: true,
isGlobalMaintainer: false,
isOnGlobalTeam: true,
isPremiumTier: true,
isSandboxMode: false,
config: createMockConfig({
server_settings: {
...createMockConfig().server_settings,
live_query_disabled: true, // Live query disabled
},
}),
},
},
});
const { container, user } = render(
<EditQueryForm
router={mockRouter}
queryIdForEdit={1}
apiTeamIdForQuery={1}
teamNameForQuery={"Apples"}
showOpenSchemaActionText
storedQuery={createMockQuery({ name: "Mock query" })}
isStoredQueryLoading={false}
isQuerySaving={false}
isQueryUpdating={false}
onSubmitNewQuery={jest.fn()}
onOsqueryTableSelect={jest.fn()}
onUpdate={jest.fn()}
onOpenSchemaSidebar={jest.fn()}
renderLiveQueryWarning={jest.fn()}
backendValidators={{}}
showConfirmSaveChangesModal={false}
setShowConfirmSaveChangesModal={jest.fn()}
/>
);
expect(screen.getByRole("button", { name: "Live query" })).toBeDisabled();
await user.hover(screen.getByRole("button", { name: "Live query" }));
expect(container.querySelector("#live-query-button")).toHaveTextContent(
/live queries are disabled/i
);
});
// TODO: Consider testing save button is disabled for a sql error
// Trickiness is in modifying react-ace using react-testing library
});

View File

@ -161,10 +161,12 @@ const EditQueryForm = ({
isGlobalMaintainer,
isObserverPlus,
isAnyTeamObserverPlus,
config,
} = useContext(AppContext);
const { renderFlash } = useContext(NotificationContext);
const savedQueryMode = !!queryIdForEdit;
const disabledLiveQuery = config?.server_settings.live_query_disabled;
const [errors, setErrors] = useState<{ [key: string]: any }>({}); // string | null | undefined or boolean | undefined
// NOTE: SaveQueryModal is only being used to create a new query in this component.
// It's easy to confuse with other names like promptSaveQuery, promptSaveAsNewQuery, etc.,
@ -589,18 +591,36 @@ const EditQueryForm = ({
<div
className={`${baseClass}__button-wrap ${baseClass}__button-wrap--new-query`}
>
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={() => {
router.push(
PATHS.LIVE_QUERY(queryIdForEdit) +
TAGGED_TEMPLATES.queryByHostRoute(hostId)
);
}}
<div
data-tip
data-for="live-query-button"
// Tooltip shows when live queries are globally disabled
data-tip-disable={!disabledLiveQuery}
>
Live query
</Button>
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={() => {
router.push(
PATHS.LIVE_QUERY(queryIdForEdit) +
TAGGED_TEMPLATES.queryByHostRoute(hostId)
);
}}
disabled={disabledLiveQuery}
>
Live query
</Button>
</div>
<ReactTooltip
className={`live-query-button-tooltip`}
place="top"
effect="solid"
backgroundColor={COLORS["tooltip-bg"]}
id="live-query-button"
data-html
>
Live queries are disabled in organization settings
</ReactTooltip>
</div>
)}
</form>
@ -808,18 +828,36 @@ const EditQueryForm = ({
</div>
</>
)}
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={() => {
router.push(
PATHS.LIVE_QUERY(queryIdForEdit) +
TAGGED_TEMPLATES.queryByHostRoute(hostId)
);
}}
<div
data-tip
data-for="live-query-button"
// Tooltip shows when live queries are globally disabled
data-tip-disable={!disabledLiveQuery}
>
Live query
</Button>
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={() => {
router.push(
PATHS.LIVE_QUERY(queryIdForEdit) +
TAGGED_TEMPLATES.queryByHostRoute(hostId)
);
}}
disabled={disabledLiveQuery}
>
Live query
</Button>
</div>
<ReactTooltip
className={`live-query-button-tooltip`}
place="top"
effect="solid"
backgroundColor={COLORS["tooltip-bg"]}
id="live-query-button"
data-html
>
Live queries are disabled in organization settings
</ReactTooltip>
</div>
</form>
{showSaveQueryModal && (

View File

@ -59,6 +59,7 @@ const RunQueryPage = ({
isAnyTeamMaintainerOrTeamAdmin,
isObserverPlus,
isAnyTeamObserverPlus,
config,
} = useContext(AppContext);
const {
selectedQueryTargets,
@ -90,6 +91,15 @@ const RunQueryPage = ({
const [targetsTotalCount, setTargetsTotalCount] = useState(0);
const [isLiveQueryRunnable, setIsLiveQueryRunnable] = useState(true);
const disabledLiveQuery = config?.server_settings.live_query_disabled;
// Reroute users out of live flow when live queries are globally disabled
if (disabledLiveQuery) {
queryId
? router.push(PATHS.QUERY(queryId))
: router.push(PATHS.NEW_QUERY());
}
// disabled on page load so we can control the number of renders
// else it will re-populate the context on occasion
const { data: storedQuery } = useQuery<
@ -155,11 +165,6 @@ const RunQueryPage = ({
});
}, [targetedLabels, targetedHosts, targetedTeams]);
console.log(
"LiveQueryPage.tsx: selectedQueryTargetsByType",
selectedQueryTargetsByType
);
// Updates title that shows up on browser tabs
useEffect(() => {
// e.g., Run live query | Discover TLS certificates | Fleet for osquery