diff --git a/changes/issue-7080-new-query-sidebar-docs b/changes/issue-7080-new-query-sidebar-docs
new file mode 100644
index 0000000000..25cd59693d
--- /dev/null
+++ b/changes/issue-7080-new-query-sidebar-docs
@@ -0,0 +1 @@
+- add new query sidebar with updated and improved docs
diff --git a/cypress/integration/all/app/labelflow.spec.ts b/cypress/integration/all/app/labelflow.spec.ts
index 48be5366f9..0246722164 100644
--- a/cypress/integration/all/app/labelflow.spec.ts
+++ b/cypress/integration/all/app/labelflow.spec.ts
@@ -21,7 +21,7 @@ describe("Labels flow", () => {
it("creates a custom label", () => {
cy.getAttached(".label-filter-select__control").click();
cy.findByRole("button", { name: /add label/i }).click();
- cy.getAttached(".ace_content").type(
+ cy.getAttached(".label-form__text-editor-wrapper .ace_content").type(
"{selectall}{backspace}SELECT * FROM users;"
);
cy.findByLabelText(/name/i).click().type("Show all MAC users");
@@ -62,7 +62,7 @@ describe("Labels flow", () => {
it("creates labels with special characters", () => {
cy.getAttached(".label-filter-select__control").click();
cy.findByRole("button", { name: /add label/i }).click();
- cy.getAttached(".ace_content").type(
+ cy.getAttached(".label-form__text-editor-wrapper .ace_content").type(
"{selectall}{backspace}SELECT * FROM users;"
);
cy.findByLabelText(/name/i)
diff --git a/cypress/integration/all/app/policiesflow.spec.ts b/cypress/integration/all/app/policiesflow.spec.ts
index 61751162cc..b2efc1b570 100644
--- a/cypress/integration/all/app/policiesflow.spec.ts
+++ b/cypress/integration/all/app/policiesflow.spec.ts
@@ -147,7 +147,7 @@ describe("Policies flow (empty)", () => {
cy.findByText(/add a policy/i).click();
});
cy.findByText(/create your own policy/i).click();
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".policy-page__form .ace_scroller")
.click({ force: true })
.type(
"{selectall}SELECT 1 FROM users WHERE username = 'backup' LIMIT 1;"
@@ -217,7 +217,7 @@ describe("Policies flow (empty)", () => {
});
// Query with unknown table name displays error message
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".policy-page__form .ace_scroller")
.first()
.click({ force: true })
.type("{selectall}SELECT 1 FROM foo WHERE start_time > 1;");
@@ -230,7 +230,7 @@ describe("Policies flow (empty)", () => {
});
// Query with syntax error displays error message
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".policy-page__form .ace_scroller")
.first()
.click({ force: true })
.type("{selectall}SELEC 1 FRO osquery_info WHER start_time > 1;");
@@ -243,7 +243,7 @@ describe("Policies flow (empty)", () => {
});
// Query with no tables treated as compatible with all platforms
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".policy-page__form .ace_scroller")
.first()
.click({ force: true })
.type("{selectall}SELECT * WHERE 1 = 1;");
@@ -254,7 +254,7 @@ describe("Policies flow (empty)", () => {
});
// Tables defined in common table expression not factored into compatibility check
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".policy-page__form .ace_scroller")
.first()
.click({ force: true })
.type("{selectall} ")
@@ -270,7 +270,7 @@ describe("Policies flow (empty)", () => {
// Query with only macOS tables treated as compatible only with macOS
// eslint-disable-next-line cypress/no-unnecessary-waiting
- cy.getAttached(".ace_scroller")
+ cy.getAttached(" .policy-page__form .ace_scroller")
.first()
.click({ force: true })
.type("{selectall} ")
@@ -285,7 +285,7 @@ describe("Policies flow (empty)", () => {
});
// Query with macadmins extension table is not treated as incompatible
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".policy-page__form .ace_scroller")
.first()
.click({ force: true })
.type("{selectall}SELECT 1 FROM mdm WHERE enrolled='true';");
@@ -477,7 +477,7 @@ describe("Policies flow (seeded)", () => {
cy.getAttached("tbody").within(() => {
cy.getAttached(".name__cell .button--text-link").first().click();
});
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".policy-page__form .ace_scroller")
.click({ force: true })
.type(
"{selectall}SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;"
diff --git a/cypress/integration/pages/manageQueriesPage.ts b/cypress/integration/pages/manageQueriesPage.ts
index 6767e7cf53..a5ff7854ae 100644
--- a/cypress/integration/pages/manageQueriesPage.ts
+++ b/cypress/integration/pages/manageQueriesPage.ts
@@ -13,7 +13,7 @@ const manageQueriesPage = {
allowsCreateNewQuery: () => {
cy.getAttached(".button--brand"); // ensures cta button loads
cy.findByRole("button", { name: /new query/i }).click();
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".query-page__form .ace_scroller")
.click({ force: true })
.type("{selectall}SELECT * FROM windows_crashes;");
cy.findByRole("button", { name: /save/i }).click();
@@ -52,7 +52,7 @@ const manageQueriesPage = {
cy.getAttached(".name__cell .button--text-link")
.first()
.click({ force: true });
- cy.getAttached(".ace_text-input")
+ cy.getAttached(".query-page__form .ace_text-input")
.click({ force: true })
.clear({ force: true })
.type("SELECT 1 FROM cypress;", {
@@ -72,7 +72,7 @@ const manageQueriesPage = {
cy.findByText(/get authorized/i).click();
});
cy.findByRole("button", { name: /run query/i }).should("exist");
- cy.getAttached(".ace_scroller")
+ cy.getAttached(".query-page__form .ace_scroller")
.click()
.type("{selectall}SELECT datetime, username FROM windows_crashes;");
cy.findByRole("button", { name: /save as new/i }).should("be.enabled");
diff --git a/frontend/components/FleetAce/FleetAce.tsx b/frontend/components/FleetAce/FleetAce.tsx
index 6d4209b41f..0806fc2ea8 100644
--- a/frontend/components/FleetAce/FleetAce.tsx
+++ b/frontend/components/FleetAce/FleetAce.tsx
@@ -23,6 +23,8 @@ export interface IFleetAceProps {
wrapperClassName?: string;
hint?: string;
labelActionComponent?: React.ReactNode;
+ style?: React.CSSProperties;
+ onBlur?: (editor?: IAceEditor) => void;
onLoad?: (editor: IAceEditor) => void;
onChange?: (value: string) => void;
handleSubmit?: () => void;
@@ -42,6 +44,8 @@ const FleetAce = ({
wrapEnabled = false,
wrapperClassName,
hint,
+ style,
+ onBlur,
onLoad,
onChange,
handleSubmit = noop,
@@ -54,9 +58,17 @@ const FleetAce = ({
const fixHotkeys = (editor: IAceEditor) => {
editor.commands.removeCommand("gotoline");
editor.commands.removeCommand("find");
+ };
+
+ const onLoadHandler = (editor: IAceEditor) => {
+ fixHotkeys(editor);
onLoad && onLoad(editor);
};
+ const onBlurHandler = (event: any, editor?: IAceEditor): void => {
+ onBlur && onBlur(editor);
+ };
+
const handleDelete = (deleteCommand: string) => {
const currentText = editorRef.current?.editor.getValue();
const selectedText = editorRef.current?.editor.getSelectedText();
@@ -114,7 +126,8 @@ const FleetAce = ({
maxLines={20}
name={name}
onChange={onChange}
- onLoad={fixHotkeys}
+ onBlur={onBlurHandler}
+ onLoad={onLoadHandler}
readOnly={readOnly}
setOptions={{ enableLinking: true }}
showGutter={showGutter}
@@ -123,6 +136,7 @@ const FleetAce = ({
value={value}
width="100%"
wrapEnabled={wrapEnabled}
+ style={style}
commands={[
{
name: "commandName",
diff --git a/frontend/components/FleetMarkdown/FleetMarkdown.tsx b/frontend/components/FleetMarkdown/FleetMarkdown.tsx
new file mode 100644
index 0000000000..4825091740
--- /dev/null
+++ b/frontend/components/FleetMarkdown/FleetMarkdown.tsx
@@ -0,0 +1,87 @@
+import React from "react";
+import ReactMarkdown from "react-markdown";
+import remarkGfm from "remark-gfm";
+import classnames from "classnames";
+import { IAceEditor } from "react-ace/lib/types";
+import { noop } from "lodash";
+
+import FleetAce from "components/FleetAce";
+
+import ExternalLinkIcon from "../../../assets/images/icon-external-link-12x12@2x.png";
+
+interface ICustomLinkProps {
+ text: React.ReactNode;
+ href: string;
+ newTab?: boolean;
+}
+
+const CustomLink = ({ text, href, newTab = false }: ICustomLinkProps) => {
+ const target = newTab ? "__blank" : "";
+ return (
+
+ {text}
+
+
+ );
+};
+
+interface IFleetMarkdownProps {
+ markdown: string;
+ className?: string;
+}
+
+const baseClass = "fleet-markdown";
+
+/** This will give us sensible defaults for how we render markdown across the fleet application.
+ * NOTE: can be extended later to take custom components, but dont need that at the moment.
+ */
+const FleetMarkdown = ({ markdown, className }: IFleetMarkdownProps) => {
+ const classNames = classnames(baseClass, className);
+
+ return (
+
{description}