mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
Fleet UI: Disable save button for invalid sql or name (#12994)
This commit is contained in:
parent
ab159edcc9
commit
e8889a4d61
9 changed files with 543 additions and 282 deletions
1
changes/12751-cannot-save-invalid-sql
Normal file
1
changes/12751-cannot-save-invalid-sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Disable save button for invalid query or policy sql & missing name
|
||||
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ const initTable =
|
|||
osqueryTables.find((table) => table.name === "users") ||
|
||||
DEFAULT_OSQUERY_TABLE;
|
||||
|
||||
export type IPolicyContext = InitialStateType;
|
||||
|
||||
const initialState = {
|
||||
lastEditedQueryId: null,
|
||||
lastEditedQueryName: "",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ type InitialStateType = {
|
|||
setSelectedOsqueryTable: (tableName: string) => void;
|
||||
};
|
||||
|
||||
export type IQueryContext = InitialStateType;
|
||||
|
||||
const initialState = {
|
||||
selectedOsqueryTable:
|
||||
find(osqueryTables, { name: "users" }) || DEFAULT_OSQUERY_TABLE,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue