Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fleet UI: Add enable automations slider to query forms #23562

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/22224-query-log-destinations
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Creating a query allow users to turn on/off automations while being transparent of the current log destination
5 changes: 3 additions & 2 deletions frontend/components/forms/fields/Slider/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { IFormFieldProps } from "components/forms/FormField/FormField";
interface ISliderProps {
onChange: () => void;
value: boolean;
inactiveText: string;
activeText: string;
inactiveText: JSX.Element | string;
activeText: JSX.Element | string;
className?: string;
helpText?: JSX.Element | string;
}

const baseClass = "fleet-slider";
Expand Down
10 changes: 8 additions & 2 deletions frontend/components/forms/fields/Slider/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
&__wrapper {
display: flex;
align-items: center;
height: 40px;
height: 24px; // Noticeable with help text below slider
}

&__dot {
Expand All @@ -40,10 +40,16 @@
}

&__label {
display: flex;
vertical-align: middle;
gap: $pad-small; // For supporting icons
font-size: $x-small;
font-weight: $regular;
text-align: left;
vertical-align: text-bottom;
margin-left: $pad-small;
}

&__help-text {
@include help-text;
}
}
15 changes: 15 additions & 0 deletions frontend/context/query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type InitialStateType = {
lastEditedQueryBody: string;
lastEditedQueryObserverCanRun: boolean;
lastEditedQueryFrequency: number;
lastEditedQueryAutomation: boolean;
lastEditedQueryPlatforms: SelectedPlatformString;
lastEditedQueryMinOsqueryVersion: string;
lastEditedQueryLoggingType: QueryLoggingOption;
Expand All @@ -38,6 +39,7 @@ type InitialStateType = {
setLastEditedQueryBody: (value: string) => void;
setLastEditedQueryObserverCanRun: (value: boolean) => void;
setLastEditedQueryFrequency: (value: number) => void;
setLastEditedQueryAutomation: (value: boolean) => void;
setLastEditedQueryPlatforms: (value: SelectedPlatformString) => void;
setLastEditedQueryMinOsqueryVersion: (value: string) => void;
setLastEditedQueryLoggingType: (value: string) => void;
Expand All @@ -59,6 +61,7 @@ const initialState = {
lastEditedQueryBody: DEFAULT_QUERY.query,
lastEditedQueryObserverCanRun: DEFAULT_QUERY.observer_can_run,
lastEditedQueryFrequency: DEFAULT_QUERY.interval,
lastEditedQueryAutomation: DEFAULT_QUERY.automations_enabled,
lastEditedQueryPlatforms: DEFAULT_QUERY.platform,
lastEditedQueryMinOsqueryVersion: DEFAULT_QUERY.min_osquery_version,
lastEditedQueryLoggingType: DEFAULT_QUERY.logging,
Expand All @@ -72,6 +75,7 @@ const initialState = {
setLastEditedQueryBody: () => null,
setLastEditedQueryObserverCanRun: () => null,
setLastEditedQueryFrequency: () => null,
setLastEditedQueryAutomation: () => null,
setLastEditedQueryPlatforms: () => null,
setLastEditedQueryMinOsqueryVersion: () => null,
setLastEditedQueryLoggingType: () => null,
Expand Down Expand Up @@ -125,6 +129,10 @@ const reducer = (state: InitialStateType, action: any) => {
typeof action.lastEditedQueryFrequency === "undefined"
? state.lastEditedQueryFrequency
: action.lastEditedQueryFrequency,
lastEditedQueryAutomation:
typeof action.lastEditedQueryAutomation === "undefined"
? state.lastEditedQueryAutomation
: action.lastEditedQueryAutomation,
lastEditedQueryPlatforms:
typeof action.lastEditedQueryPlatforms === "undefined"
? state.lastEditedQueryPlatforms
Expand Down Expand Up @@ -180,6 +188,7 @@ const QueryProvider = ({ children }: Props) => {
lastEditedQueryBody: state.lastEditedQueryBody,
lastEditedQueryObserverCanRun: state.lastEditedQueryObserverCanRun,
lastEditedQueryFrequency: state.lastEditedQueryFrequency,
lastEditedQueryAutomation: state.lastEditedQueryAutomation,
lastEditedQueryPlatforms: state.lastEditedQueryPlatforms,
lastEditedQueryMinOsqueryVersion: state.lastEditedQueryMinOsqueryVersion,
lastEditedQueryLoggingType: state.lastEditedQueryLoggingType,
Expand Down Expand Up @@ -225,6 +234,12 @@ const QueryProvider = ({ children }: Props) => {
lastEditedQueryFrequency,
});
},
setLastEditedQueryAutomation: (lastEditedQueryAutomation: boolean) => {
dispatch({
type: actions.SET_LAST_EDITED_QUERY_INFO,
lastEditedQueryAutomation,
});
},
setLastEditedQueryPlatforms: (lastEditedQueryPlatforms: string) => {
dispatch({
type: actions.SET_LAST_EDITED_QUERY_INFO,
Expand Down
2 changes: 2 additions & 0 deletions frontend/interfaces/schedulable_query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export interface IModifyQueryRequestBody
frequency?: number;
platform?: SelectedPlatformString;
min_osquery_version?: string;
automations_enabled?: boolean;
}

// response is ISchedulableQuery // better way to indicate this?
Expand Down Expand Up @@ -131,6 +132,7 @@ export interface IEditQueryFormFields {
observer_can_run: IFormField<boolean>;
discard_data: IFormField<boolean>;
frequency: IFormField<number>;
automations_enabled: IFormField<boolean>;
platforms: IFormField<SelectedPlatformString>;
min_osquery_version: IFormField<string>;
logging: IFormField<QueryLoggingOption>;
Expand Down
5 changes: 5 additions & 0 deletions frontend/pages/queries/edit/EditQueryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const EditQueryPage = ({
lastEditedQueryBody,
lastEditedQueryObserverCanRun,
lastEditedQueryFrequency,
lastEditedQueryAutomation,
lastEditedQueryPlatforms,
lastEditedQueryLoggingType,
lastEditedQueryMinOsqueryVersion,
Expand All @@ -101,6 +102,7 @@ const EditQueryPage = ({
setLastEditedQueryBody,
setLastEditedQueryObserverCanRun,
setLastEditedQueryFrequency,
setLastEditedQueryAutomation,
setLastEditedQueryLoggingType,
setLastEditedQueryMinOsqueryVersion,
setLastEditedQueryPlatforms,
Expand Down Expand Up @@ -150,6 +152,7 @@ const EditQueryPage = ({
setLastEditedQueryBody(returnedQuery.query);
setLastEditedQueryObserverCanRun(returnedQuery.observer_can_run);
setLastEditedQueryFrequency(returnedQuery.interval);
setLastEditedQueryAutomation(returnedQuery.automations_enabled);
setLastEditedQueryPlatforms(returnedQuery.platform);
setLastEditedQueryLoggingType(returnedQuery.logging);
setLastEditedQueryMinOsqueryVersion(returnedQuery.min_osquery_version);
Expand Down Expand Up @@ -228,6 +231,7 @@ const EditQueryPage = ({
// Persist lastEditedQueryBody through live query flow instead of resetting to DEFAULT_QUERY.query
setLastEditedQueryObserverCanRun(DEFAULT_QUERY.observer_can_run);
setLastEditedQueryFrequency(DEFAULT_QUERY.interval);
setLastEditedQueryAutomation(DEFAULT_QUERY.automations_enabled);
setLastEditedQueryLoggingType(DEFAULT_QUERY.logging);
setLastEditedQueryMinOsqueryVersion(DEFAULT_QUERY.min_osquery_version);
setLastEditedQueryPlatforms(DEFAULT_QUERY.platform);
Expand Down Expand Up @@ -298,6 +302,7 @@ const EditQueryPage = ({
lastEditedQueryBody,
lastEditedQueryObserverCanRun,
lastEditedQueryFrequency,
lastEditedQueryAutomation,
lastEditedQueryPlatforms,
lastEditedQueryLoggingType,
lastEditedQueryMinOsqueryVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ describe("DiscardDataOption component", () => {
);

expect(screen.getByText(/Discard data/)).toBeInTheDocument();
expect(screen.getByText(/Data will still be sent/)).toBeInTheDocument();
expect(
screen.getByText(
/The most recent results for each host will not be available in Fleet./
)
).toBeInTheDocument();
});

it('Renders the "disabled" help text with tooltip when the global option is disabled', async () => {
Expand Down Expand Up @@ -48,7 +52,11 @@ describe("DiscardDataOption component", () => {
await fireEvent.click(screen.getByText(/Edit anyway/));

// normal text
expect(screen.getByText(/Data will still be sent/)).toBeInTheDocument();
expect(
screen.getByText(
/The most recent results for each host will not be available in Fleet./
)
).toBeInTheDocument();
});
it('Renders the info banner when "Differential" logging option is selected', () => {
render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ interface IDiscardDataOptionProps {
selectedLoggingType: QueryLoggingOption;
discardData: boolean;
setDiscardData: (value: boolean) => void;
breakHelpText?: boolean;
}

const DiscardDataOption = ({
queryReportsDisabled,
selectedLoggingType,
discardData,
setDiscardData,
breakHelpText = false,
}: IDiscardDataOptionProps) => {
const [forceEditDiscardData, setForceEditDiscardData] = useState(false);
const disable = queryReportsDisabled && !forceEditDiscardData;
Expand Down Expand Up @@ -60,14 +58,7 @@ const DiscardDataOption = ({
</Link>
</>
) : (
<>
The most recent results for each host will not be available in Fleet.
{breakHelpText ? <br /> : " "}
Data will still be sent to your log destination if <b>
automations
</b>{" "}
are <b>on</b>.
</>
"The most recent results for each host will not be available in Fleet."
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { screen } from "@testing-library/react";
import { screen, within } from "@testing-library/react";
import { createCustomRenderer } from "test/test-utils";

import createMockQuery from "__mocks__/queryMock";
Expand Down Expand Up @@ -32,6 +32,7 @@ describe("EditQueryForm - component", () => {
lastEditedQueryBody: mockQuery.query,
lastEditedQueryObserverCanRun: mockQuery.observer_can_run,
lastEditedQueryFrequency: mockQuery.interval,
lastEditedQueryAutomation: mockQuery.automations_enabled,
lastEditedQueryPlatforms: mockQuery.platform,
lastEditedQueryMinOsqueryVersion: mockQuery.min_osquery_version,
lastEditedQueryLoggingType: mockQuery.logging,
Expand All @@ -40,6 +41,7 @@ describe("EditQueryForm - component", () => {
setLastEditedQueryBody: jest.fn(),
setLastEditedQueryObserverCanRun: jest.fn(),
setLastEditedQueryFrequency: jest.fn(),
setLastEditedQueryAutomation: jest.fn(),
setLastEditedQueryPlatforms: jest.fn(),
setLastEditedQueryMinOsqueryVersion: jest.fn(),
setLastEditedQueryLoggingType: jest.fn(),
Expand Down Expand Up @@ -92,6 +94,7 @@ describe("EditQueryForm - component", () => {
lastEditedQueryBody: mockQuery.query,
lastEditedQueryObserverCanRun: mockQuery.observer_can_run,
lastEditedQueryFrequency: mockQuery.interval,
lastEditedQueryAutomation: mockQuery.automations_enabled,
lastEditedQueryPlatforms: mockQuery.platform,
lastEditedQueryMinOsqueryVersion: mockQuery.min_osquery_version,
lastEditedQueryLoggingType: mockQuery.logging,
Expand All @@ -100,6 +103,7 @@ describe("EditQueryForm - component", () => {
setLastEditedQueryBody: jest.fn(),
setLastEditedQueryObserverCanRun: jest.fn(),
setLastEditedQueryFrequency: jest.fn(),
setLastEditedQueryAutomation: jest.fn(),
setLastEditedQueryPlatforms: jest.fn(),
setLastEditedQueryMinOsqueryVersion: jest.fn(),
setLastEditedQueryLoggingType: jest.fn(),
Expand Down Expand Up @@ -152,6 +156,91 @@ describe("EditQueryForm - component", () => {
/live queries are disabled/i
);
});

it("shows automations warning icon when query frequency is set to 0", async () => {
const render = createCustomRenderer({
context: {
query: {
lastEditedQueryId: mockQuery.id,
lastEditedQueryName: "Test Query",
lastEditedQueryDescription: mockQuery.description,
lastEditedQueryBody: mockQuery.query,
lastEditedQueryObserverCanRun: mockQuery.observer_can_run,
lastEditedQueryFrequency: 0, // Set frequency to 0
lastEditedQueryAutomation: true, // Enable automations
lastEditedQueryPlatforms: mockQuery.platform,
lastEditedQueryMinOsqueryVersion: mockQuery.min_osquery_version,
lastEditedQueryLoggingType: mockQuery.logging,
setLastEditedQueryName: jest.fn(),
setLastEditedQueryDescription: jest.fn(),
setLastEditedQueryBody: jest.fn(),
setLastEditedQueryObserverCanRun: jest.fn(),
setLastEditedQueryAutomation: 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(),
},
},
});

const { user } = render(
<EditQueryForm
router={mockRouter}
queryIdForEdit={1}
apiTeamIdForQuery={1}
teamNameForQuery="Apples"
showOpenSchemaActionText
storedQuery={createMockQuery({ interval: 0 })}
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()}
/>
);

// Find the frequency dropdown
const frequencyDropdown = screen
.getByText("Frequency")
.closest(".form-field--dropdown") as HTMLElement;
expect(frequencyDropdown).toBeInTheDocument();

// Check if the frequency is set to "Never"
const selectedFrequency = within(frequencyDropdown).getByText("Never");
expect(selectedFrequency).toBeInTheDocument();

// Find the automations slider
const automationsSlider = screen
.getByText("Automations on")
.closest(".fleet-slider__wrapper") as HTMLElement;
expect(automationsSlider).toBeInTheDocument();

// Check if the automations are enabled
const automationsButton = within(automationsSlider).getByRole("button");
expect(automationsButton).toHaveClass("fleet-slider--active");

// Check if the warning icon is present
const warningIcon = within(automationsSlider).getByTestId("warning-icon");
expect(warningIcon).toBeInTheDocument();
});

// TODO: Consider testing save button is disabled for a sql error
// Trickiness is in modifying react-ace using react-testing library
});
Loading
Loading