Fleet UI: Disable save button for invalid sql or name (#12994)

This commit is contained in:
RachelElysia 2023-08-09 16:24:46 -04:00 committed by GitHub
parent ab159edcc9
commit e8889a4d61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 543 additions and 282 deletions

View file

@ -0,0 +1 @@
- Disable save button for invalid query or policy sql & missing name

View file

@ -1,6 +1,6 @@
import { IQuery } from "interfaces/query";
import { ISchedulableQuery } from "interfaces/schedulable_query";
const DEFAULT_QUERY_MOCK: IQuery = {
const DEFAULT_QUERY_MOCK: ISchedulableQuery = {
created_at: "2022-11-03T17:22:14Z",
updated_at: "2022-11-03T17:22:14Z",
id: 1,
@ -12,10 +12,25 @@ const DEFAULT_QUERY_MOCK: IQuery = {
author_name: "Test User",
author_email: "test@example.com",
observer_can_run: false,
interval: 300,
packs: [],
team_id: null,
platform: "",
min_osquery_version: "",
automations_enabled: false,
logging: "snapshot",
stats: {
user_time_p50: 0,
user_time_p95: 2,
system_time_p50: 0,
system_time_p95: 1,
total_executions: 6,
},
};
const createMockQuery = (overrides?: Partial<IQuery>): IQuery => {
const createMockQuery = (
overrides?: Partial<ISchedulableQuery>
): ISchedulableQuery => {
return { ...DEFAULT_QUERY_MOCK, ...overrides };
};

View file

@ -75,6 +75,8 @@ const initTable =
osqueryTables.find((table) => table.name === "users") ||
DEFAULT_OSQUERY_TABLE;
export type IPolicyContext = InitialStateType;
const initialState = {
lastEditedQueryId: null,
lastEditedQueryName: "",

View file

@ -34,6 +34,8 @@ type InitialStateType = {
setSelectedOsqueryTable: (tableName: string) => void;
};
export type IQueryContext = InitialStateType;
const initialState = {
selectedOsqueryTable:
find(osqueryTables, { name: "users" }) || DEFAULT_OSQUERY_TABLE,

View file

@ -0,0 +1,132 @@
import React from "react";
import { screen } from "@testing-library/react";
import { createCustomRenderer } from "test/test-utils";
import createMockPolicy from "__mocks__/policyMock";
import createMockUser from "__mocks__/userMock";
import PolicyForm from "./PolicyForm";
const mockPolicy = createMockPolicy();
describe("PolicyForm - component", () => {
it("disables save button for missing policy name", async () => {
const render = createCustomRenderer({
context: {
policy: {
policyTeamId: undefined,
lastEditedQueryId: mockPolicy.id,
lastEditedQueryName: "", // missing policy name
lastEditedQueryDescription: mockPolicy.description,
lastEditedQueryBody: mockPolicy.query,
lastEditedQueryResolution: mockPolicy.resolution,
lastEditedQueryCritical: mockPolicy.critical,
lastEditedQueryPlatform: mockPolicy.platform,
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,
},
},
});
render(
<PolicyForm
policyIdForEdit={mockPolicy.id}
showOpenSchemaActionText={false}
storedPolicy={createMockPolicy({ name: "" })}
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: "Save" })).toBeDisabled();
});
it("disables save and run button with tooltip for missing policy platforms", 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,
},
},
});
const { container, user } = render(
<PolicyForm
policyIdForEdit={mockPolicy.id}
showOpenSchemaActionText={false}
storedPolicy={createMockPolicy({ platform: undefined })}
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: "Save" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Run" })).toBeDisabled();
await user.hover(screen.getByRole("button", { name: "Save" }));
expect(
container.querySelector("#policy-form__button-wrap--tooltip")
).toHaveTextContent(/to save or run the policy/i);
});
// TODO: Consider testing save button is disabled for a sql error
// Trickiness is in modifying react-ace using react-testing library
});

View file

@ -466,7 +466,8 @@ const PolicyForm = ({
);
};
const renderRunForObserver = (
// Observers and observer+ of existing query, team role viewing inherited policy
const renderNonEditableForm = (
<form className={`${baseClass}__wrapper`}>
<div className={`${baseClass}__title-bar`}>
<div className="name-description-resolve">
@ -499,127 +500,139 @@ const PolicyForm = ({
</form>
);
const renderForGlobalAdminOrAnyMaintainer = (
<>
<form className={`${baseClass}__wrapper`} autoComplete="off">
<div className={`${baseClass}__title-bar`}>
<div className="name-description-resolve">
{renderName()}
{renderDescription()}
{renderResolution()}
// Admin or maintainer
const renderEditableQueryForm = () => {
// Save disabled for no platforms selected, query name blank on existing query, or sql errors
const disableSaveFormErrors =
(isEditMode && !isAnyPlatformSelected) ||
(lastEditedQueryName === "" && !!lastEditedQueryId) ||
!!size(errors);
return (
<>
<form className={`${baseClass}__wrapper`} autoComplete="off">
<div className={`${baseClass}__title-bar`}>
<div className="name-description-resolve">
{renderName()}
{renderDescription()}
{renderResolution()}
</div>
<div className="author">{isEditMode && renderAuthor()}</div>
</div>
<div className="author">{isEditMode && renderAuthor()}</div>
</div>
<FleetAce
value={lastEditedQueryBody}
error={errors.query}
label="Query"
labelActionComponent={renderLabelComponent()}
name="query editor"
onLoad={onLoad}
wrapperClassName={`${baseClass}__text-editor-wrapper`}
onChange={onChangePolicy}
handleSubmit={promptSavePolicy}
wrapEnabled
focus={!isEditMode}
/>
<span className={`${baseClass}__platform-compatibility`}>
{renderPlatformCompatibility()}
</span>
{(isEditMode || defaultPolicy) && platformSelector.render()}
{isEditMode && isPremiumTier && renderCriticalPolicy()}
{renderLiveQueryWarning()}
<div className={`${baseClass}__button-wrap`}>
{hasSavePermissions && (
<>
<span
className={`${baseClass}__button-wrap--tooltip`}
data-tip
data-for={`${baseClass}__button-wrap--tooltip`}
data-tip-disable={!isEditMode || isAnyPlatformSelected}
>
<Button
variant="brand"
onClick={promptSavePolicy()}
disabled={isEditMode && !isAnyPlatformSelected}
className="save-loading"
isLoading={isUpdatingPolicy}
>
Save
</Button>
</span>
<ReactTooltip
className={`${baseClass}__button-wrap--tooltip`}
place="bottom"
effect="solid"
id={`${baseClass}__button-wrap--tooltip`}
backgroundColor="#3e4771"
>
Select the platform(s) this
<br />
policy will be checked on
<br />
to save or run the policy.
</ReactTooltip>
</>
)}
<span
className={`${baseClass}__button-wrap--tooltip`}
data-tip
data-for={`${baseClass}__button-wrap--tooltip`}
data-tip-disable={!isEditMode || isAnyPlatformSelected}
>
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={goToSelectTargets}
disabled={isEditMode && !isAnyPlatformSelected}
>
Run
</Button>
<FleetAce
value={lastEditedQueryBody}
error={errors.query}
label="Query"
labelActionComponent={renderLabelComponent()}
name="query editor"
onLoad={onLoad}
wrapperClassName={`${baseClass}__text-editor-wrapper`}
onChange={onChangePolicy}
handleSubmit={promptSavePolicy}
wrapEnabled
focus={!isEditMode}
/>
<span className={`${baseClass}__platform-compatibility`}>
{renderPlatformCompatibility()}
</span>
<ReactTooltip
className={`${baseClass}__button-wrap--tooltip`}
place="bottom"
effect="solid"
id={`${baseClass}__button-wrap--tooltip`}
backgroundColor="#3e4771"
>
Select the platform(s) this
<br />
policy will be checked on
<br />
to save or run the policy.
</ReactTooltip>
</div>
</form>
{isSaveNewPolicyModalOpen && (
<SaveNewPolicyModal
baseClass={baseClass}
queryValue={lastEditedQueryBody}
onCreatePolicy={onCreatePolicy}
setIsSaveNewPolicyModalOpen={setIsSaveNewPolicyModalOpen}
backendValidators={backendValidators}
platformSelector={platformSelector}
isUpdatingPolicy={isUpdatingPolicy}
/>
)}
</>
);
{(isEditMode || defaultPolicy) && platformSelector.render()}
{isEditMode && isPremiumTier && renderCriticalPolicy()}
{renderLiveQueryWarning()}
<div className={`${baseClass}__button-wrap`}>
{hasSavePermissions && (
<>
<span
className={`${baseClass}__button-wrap--tooltip`}
data-tip
data-for={`${baseClass}__button-wrap--tooltip`}
data-tip-disable={!isEditMode || isAnyPlatformSelected}
>
<Button
variant="brand"
onClick={promptSavePolicy()}
disabled={disableSaveFormErrors}
className="save-loading"
isLoading={isUpdatingPolicy}
>
Save
</Button>
</span>
<ReactTooltip
className={`${baseClass}__button-wrap--tooltip`}
place="bottom"
effect="solid"
id={`${baseClass}__button-wrap--tooltip`}
backgroundColor="#3e4771"
>
Select the platform(s) this
<br />
policy will be checked on
<br />
to save or run the policy.
</ReactTooltip>
</>
)}
<span
className={`${baseClass}__button-wrap--tooltip`}
data-tip
data-for={`${baseClass}__button-wrap--tooltip`}
data-tip-disable={!isEditMode || isAnyPlatformSelected}
>
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={goToSelectTargets}
disabled={isEditMode && !isAnyPlatformSelected}
>
Run
</Button>
</span>
<ReactTooltip
className={`${baseClass}__button-wrap--tooltip`}
place="bottom"
effect="solid"
id={`${baseClass}__button-wrap--tooltip`}
backgroundColor="#3e4771"
>
Select the platform(s) this
<br />
policy will be checked on
<br />
to save or run the policy.
</ReactTooltip>
</div>
</form>
{isSaveNewPolicyModalOpen && (
<SaveNewPolicyModal
baseClass={baseClass}
queryValue={lastEditedQueryBody}
onCreatePolicy={onCreatePolicy}
setIsSaveNewPolicyModalOpen={setIsSaveNewPolicyModalOpen}
backendValidators={backendValidators}
platformSelector={platformSelector}
isUpdatingPolicy={isUpdatingPolicy}
/>
)}
</>
);
};
if (isStoredPolicyLoading) {
return <Spinner />;
}
if (
const noEditPermissions =
isTeamObserver ||
isGlobalObserver ||
(policyTeamId === 0 && !isOnGlobalTeam)
) {
return renderRunForObserver;
(policyTeamId === 0 && !isOnGlobalTeam); // Team user viewing inherited policy
// Render non-editable form only
if (noEditPermissions) {
return renderNonEditableForm;
}
return renderForGlobalAdminOrAnyMaintainer;
// Render default editable form
return renderEditableQueryForm();
};
export default PolicyForm;

View file

@ -0,0 +1,84 @@
import React from "react";
import { screen } from "@testing-library/react";
import { createCustomRenderer } from "test/test-utils";
import createMockQuery from "__mocks__/queryMock";
import createMockUser from "__mocks__/userMock";
import QueryForm from "./QueryForm";
const mockQuery = createMockQuery();
const mockRouter = {
push: jest.fn(),
replace: jest.fn(),
goBack: jest.fn(),
goForward: jest.fn(),
go: jest.fn(),
setRouteLeaveHook: jest.fn(),
isActive: jest.fn(),
createHref: jest.fn(),
createPath: jest.fn(),
};
describe("QueryForm - component", () => {
it("disables save button for missing query name", 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,
},
},
});
render(
<QueryForm
router={mockRouter}
queryIdForEdit={1}
apiTeamIdForQuery={1}
teamNameForQuery={"Apples"}
showOpenSchemaActionText
storedQuery={createMockQuery({ name: "" })} // empty name
isStoredQueryLoading={false}
isQuerySaving={false}
isQueryUpdating={false}
saveQuery={jest.fn()}
onOsqueryTableSelect={jest.fn()}
goToSelectTargets={jest.fn()}
onUpdate={jest.fn()}
onOpenSchemaSidebar={jest.fn()}
renderLiveQueryWarning={jest.fn()}
backendValidators={{}}
/>
);
expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
});
// TODO: Consider testing save button is disabled for a sql error
// Trickiness is in modifying react-ace using react-testing library
});

View file

@ -533,6 +533,7 @@ const QueryForm = ({
}
labelActionComponent={isObserverPlus && renderLabelComponent()}
wrapEnabled
data-testid="ace-editor"
/>
)}
<span className={`${baseClass}__platform-compatibility`}>
@ -560,182 +561,187 @@ const QueryForm = ({
const hasSavePermissions = isGlobalAdmin || isGlobalMaintainer;
// Global admin, any maintainer, any observer+ on new query
const renderEditableQueryForm = (
<>
<form className={`${baseClass}__wrapper`} autoComplete="off">
<div className={`${baseClass}__title-bar`}>
<div className="name-description">
{renderName()}
{renderDescription()}
const renderEditableQueryForm = () => {
// Save disabled for team maintainer/admins viewing global queries
const disableSavePermissionDenied =
isAnyTeamMaintainerOrTeamAdmin &&
!storedQuery?.team_id &&
!!queryIdForEdit;
// Save and save as new disabled for query name blank on existing query or sql errors
const disableSaveFormErrors =
(lastEditedQueryName === "" && !!lastEditedQueryId) || !!size(errors);
return (
<>
<form className={`${baseClass}__wrapper`} autoComplete="off">
<div className={`${baseClass}__title-bar`}>
<div className="name-description">
{renderName()}
{renderDescription()}
</div>
<div className="author">{savedQueryMode && renderAuthor()}</div>
</div>
<div className="author">{savedQueryMode && renderAuthor()}</div>
</div>
<FleetAce
value={lastEditedQueryBody}
error={errors.query}
label="Query"
labelActionComponent={renderLabelComponent()}
name="query editor"
onLoad={onLoad}
wrapperClassName={`${baseClass}__text-editor-wrapper`}
onChange={onChangeQuery}
handleSubmit={promptSaveQuery}
wrapEnabled
focus={!savedQueryMode}
/>
<span className={`${baseClass}__platform-compatibility`}>
{renderPlatformCompatibility()}
</span>
{savedQueryMode && (
<div className={`${baseClass}__edit-options`}>
<div className={`${baseClass}__frequency`}>
<Dropdown
searchable={false}
options={FREQUENCY_DROPDOWN_OPTIONS}
onChange={onChangeSelectFrequency}
placeholder={"Every day"}
value={lastEditedQueryFrequency}
label={"Frequency"}
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--frequency`}
/>
If automations are on, this is how often your query collects data.
</div>
<div className={`${baseClass}__observers-can-run`}>
<Checkbox
value={lastEditedQueryObserverCanRun}
onChange={(value: boolean) =>
setLastEditedQueryObserverCanRun(value)
}
wrapperClassName={`${baseClass}__query-observer-can-run-wrapper`}
>
Observers can run
</Checkbox>
<p>
Users with the observer role will be able to run this query on
hosts where they have access.
</p>
</div>
<RevealButton
isShowing={showAdvancedOptions}
className={baseClass}
hideText={"Hide advanced options"}
showText={"Show advanced options"}
caretPosition={"after"}
onClick={toggleAdvancedOptions}
/>
{showAdvancedOptions && (
<div className={`${baseClass}__advanced-options`}>
<FleetAce
value={lastEditedQueryBody}
error={errors.query}
label="Query"
labelActionComponent={renderLabelComponent()}
name="query editor"
onLoad={onLoad}
wrapperClassName={`${baseClass}__text-editor-wrapper`}
onChange={onChangeQuery}
handleSubmit={promptSaveQuery}
wrapEnabled
focus={!savedQueryMode}
/>
<span className={`${baseClass}__platform-compatibility`}>
{renderPlatformCompatibility()}
</span>
{savedQueryMode && (
<div className={`${baseClass}__edit-options`}>
<div className={`${baseClass}__frequency`}>
<Dropdown
options={SCHEDULE_PLATFORM_DROPDOWN_OPTIONS}
placeholder="Select"
label="Platform"
onChange={onChangeSelectPlatformOptions}
value={lastEditedQueryPlatforms}
multi
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--platform`}
/>
<Dropdown
options={MIN_OSQUERY_VERSION_OPTIONS}
onChange={onChangeMinOsqueryVersionOptions}
placeholder="Select"
value={lastEditedQueryMinOsqueryVersion}
label="Minimum osquery version"
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--osquer-vers`}
/>
<Dropdown
options={LOGGING_TYPE_OPTIONS}
onChange={onChangeSelectLoggingType}
placeholder="Select"
value={lastEditedQueryLoggingType}
label="Logging"
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--logging`}
searchable={false}
options={FREQUENCY_DROPDOWN_OPTIONS}
onChange={onChangeSelectFrequency}
placeholder={"Every day"}
value={lastEditedQueryFrequency}
label={"Frequency"}
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--frequency`}
/>
If automations are on, this is how often your query collects
data.
</div>
)}
</div>
)}
{renderLiveQueryWarning()}
<div
className={`${baseClass}__button-wrap ${baseClass}__button-wrap--new-query`}
>
{(hasSavePermissions || isAnyTeamMaintainerOrTeamAdmin) && (
<>
{savedQueryMode && (
<Button
variant="text-link"
onClick={promptSaveAsNewQuery()}
disabled={false}
className="save-as-new-loading"
isLoading={isSaveAsNewLoading}
>
Save as new
</Button>
)}
<div className="query-form__button-wrap--save-query-button">
<div
data-tip
data-for="save-query-button"
// Tooltip shows for team maintainer/admins viewing global queries
data-tip-disable={
!(
isAnyTeamMaintainerOrTeamAdmin &&
!storedQuery?.team_id &&
!!queryIdForEdit
)
<div className={`${baseClass}__observers-can-run`}>
<Checkbox
value={lastEditedQueryObserverCanRun}
onChange={(value: boolean) =>
setLastEditedQueryObserverCanRun(value)
}
wrapperClassName={`${baseClass}__query-observer-can-run-wrapper`}
>
<Button
className="save-loading"
variant="brand"
onClick={promptSaveQuery()}
// Button disabled for team maintainer/admins viewing global queries
disabled={
isAnyTeamMaintainerOrTeamAdmin &&
!storedQuery?.team_id &&
!!queryIdForEdit
}
isLoading={isQueryUpdating}
>
Save
</Button>
</div>{" "}
<ReactTooltip
className={`save-query-button-tooltip`}
place="top"
effect="solid"
backgroundColor={COLORS["tooltip-bg"]}
id="save-query-button"
data-html
>
<>
You can only save changes
<br /> to a team level query.
</>
</ReactTooltip>
Observers can run
</Checkbox>
<p>
Users with the observer role will be able to run this query on
hosts where they have access.
</p>
</div>
</>
<RevealButton
isShowing={showAdvancedOptions}
className={baseClass}
hideText={"Hide advanced options"}
showText={"Show advanced options"}
caretPosition={"after"}
onClick={toggleAdvancedOptions}
/>
{showAdvancedOptions && (
<div className={`${baseClass}__advanced-options`}>
<Dropdown
options={SCHEDULE_PLATFORM_DROPDOWN_OPTIONS}
placeholder="Select"
label="Platform"
onChange={onChangeSelectPlatformOptions}
value={lastEditedQueryPlatforms}
multi
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--platform`}
/>
<Dropdown
options={MIN_OSQUERY_VERSION_OPTIONS}
onChange={onChangeMinOsqueryVersionOptions}
placeholder="Select"
value={lastEditedQueryMinOsqueryVersion}
label="Minimum osquery version"
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--osquer-vers`}
/>
<Dropdown
options={LOGGING_TYPE_OPTIONS}
onChange={onChangeSelectLoggingType}
placeholder="Select"
value={lastEditedQueryLoggingType}
label="Logging"
wrapperClassName={`${baseClass}__form-field ${baseClass}__form-field--logging`}
/>
</div>
)}
</div>
)}
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={goToSelectTargets}
{renderLiveQueryWarning()}
<div
className={`${baseClass}__button-wrap ${baseClass}__button-wrap--new-query`}
>
Live query
</Button>
</div>
</form>
{showSaveQueryModal && (
<SaveQueryModal
queryValue={lastEditedQueryBody}
apiTeamIdForQuery={apiTeamIdForQuery}
saveQuery={saveQuery}
toggleSaveQueryModal={toggleSaveQueryModal}
backendValidators={backendValidators}
isLoading={isQuerySaving}
/>
)}
</>
);
{(hasSavePermissions || isAnyTeamMaintainerOrTeamAdmin) && (
<>
{savedQueryMode && (
<Button
variant="text-link"
onClick={promptSaveAsNewQuery()}
disabled={disableSaveFormErrors}
className="save-as-new-loading"
isLoading={isSaveAsNewLoading}
>
Save as new
</Button>
)}
<div className="query-form__button-wrap--save-query-button">
<div
data-tip
data-for="save-query-button"
// Tooltip shows for team maintainer/admins viewing global queries
data-tip-disable={!disableSavePermissionDenied}
>
<Button
className="save-loading"
variant="brand"
onClick={promptSaveQuery()}
// Button disabled for team maintainer/admins viewing global queries
disabled={
disableSavePermissionDenied || disableSaveFormErrors
}
isLoading={isQueryUpdating}
>
Save
</Button>
</div>{" "}
<ReactTooltip
className={`save-query-button-tooltip`}
place="top"
effect="solid"
backgroundColor={COLORS["tooltip-bg"]}
id="save-query-button"
data-html
>
<>
You can only save changes
<br /> to a team level query.
</>
</ReactTooltip>
</div>
</>
)}
<Button
className={`${baseClass}__run`}
variant="blue-green"
onClick={goToSelectTargets}
>
Live query
</Button>
</div>
</form>
{showSaveQueryModal && (
<SaveQueryModal
queryValue={lastEditedQueryBody}
apiTeamIdForQuery={apiTeamIdForQuery}
saveQuery={saveQuery}
toggleSaveQueryModal={toggleSaveQueryModal}
backendValidators={backendValidators}
isLoading={isQuerySaving}
/>
)}
</>
);
};
if (isStoredQueryLoading) {
return <Spinner />;
@ -755,7 +761,7 @@ const QueryForm = ({
}
// Render default editable form
return renderEditableQueryForm;
return renderEditableQueryForm();
};
export default QueryForm;

View file

@ -9,6 +9,8 @@ import {
INotificationContext,
NotificationContext,
} from "context/notification";
import { IPolicyContext, PolicyContext } from "context/policy";
import { IQueryContext, QueryContext } from "context/query";
export const baseUrl = (path: string) => {
return `/api/latest/fleet${path}`;
@ -36,6 +38,8 @@ export const renderWithAppContext = (
interface IContextOptions {
app?: Partial<IAppContext>;
notification?: Partial<INotificationContext>;
policy?: Partial<IPolicyContext>;
query?: Partial<IQueryContext>;
}
interface ICustomRenderOptions {
@ -55,6 +59,8 @@ interface ICustomRenderOptions {
const CONTEXT_PROVIDER_MAP = {
app: AppContext,
notification: NotificationContext,
policy: PolicyContext,
query: QueryContext,
};
type ContextProviderKeys = keyof typeof CONTEXT_PROVIDER_MAP;