From eab1e03666ac42080f200cfb3b09a40a95276d30 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Tue, 12 Mar 2024 13:48:18 -0500 Subject: [PATCH 01/22] remove 'automations' column --- .../components/PoliciesTable/PoliciesTableConfig.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx index 4fc8f7973d59..c2c1c505a9f7 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx @@ -284,16 +284,6 @@ const generateTableHeaders = ( ]; if (tableType !== "inheritedPolicies") { - tableHeaders.push({ - title: "Automations", - Header: "Automations", - disableSortBy: true, - accessor: "webhook", - Cell: (cellProps: ICellProps): JSX.Element => ( - - ), - }); - if (!canAddOrDeletePolicy) { return tableHeaders; } From f4bf951a4a472700dd5c91082b186de8e9d67a73 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Tue, 12 Mar 2024 14:47:52 -0500 Subject: [PATCH 02/22] Create modal; prototype not-configured state; --- .../CalendarIntegrationNotConfigured.tsx | 529 ++++++++++++++++++ frontend/components/graphics/index.ts | 2 + .../HostDetailsPage/HostDetailsPage.tsx | 1 - .../CalendarEventsModal.tests.tsx | 11 + .../CalendarEventsModal.tsx | 59 ++ .../CalendarEventsModal/_styles.scss | 3 + .../components/CalendarEventsModal/index.ts | 1 + 7 files changed, 605 insertions(+), 1 deletion(-) create mode 100644 frontend/components/graphics/CalendarIntegrationNotConfigured.tsx create mode 100644 frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tests.tsx create mode 100644 frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx create mode 100644 frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss create mode 100644 frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/index.ts diff --git a/frontend/components/graphics/CalendarIntegrationNotConfigured.tsx b/frontend/components/graphics/CalendarIntegrationNotConfigured.tsx new file mode 100644 index 000000000000..60d42ae8d167 --- /dev/null +++ b/frontend/components/graphics/CalendarIntegrationNotConfigured.tsx @@ -0,0 +1,529 @@ +import React from "react"; + +const CalIntegrationNotConfigured = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default CalIntegrationNotConfigured; diff --git a/frontend/components/graphics/index.ts b/frontend/components/graphics/index.ts index 84e10a37113c..876cef9db2e9 100644 --- a/frontend/components/graphics/index.ts +++ b/frontend/components/graphics/index.ts @@ -17,6 +17,7 @@ import EmptyTeams from "./EmptyTeams"; import EmptyPacks from "./EmptyPacks"; import EmptySchedule from "./EmptySchedule"; import CollectingResults from "./CollectingResults"; +import CalIntegrationNotConfigured from "./CalendarIntegrationNotConfigured"; export const GRAPHIC_MAP = { // Empty state graphics @@ -41,6 +42,7 @@ export const GRAPHIC_MAP = { "file-pem": FilePem, // Other graphics "collecting-results": CollectingResults, + "calendar-integration-not-configured": CalIntegrationNotConfigured, }; export type GraphicNames = keyof typeof GRAPHIC_MAP; diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx index 99faef93dda8..a117974fa2d6 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx @@ -13,7 +13,6 @@ import { QueryContext } from "context/query"; import { NotificationContext } from "context/notification"; import activitiesAPI, { - IActivitiesResponse, IPastActivitiesResponse, IUpcomingActivitiesResponse, } from "services/entities/activities"; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tests.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tests.tsx new file mode 100644 index 000000000000..b7622a67ad03 --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tests.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +import { render, screen } from "@testing-library/react"; + +import CalendarEventsModal from "./CalendarEventsModal"; + +describe("CalendarEventsModal component", () => { + it("", () => { + + }); +}); diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx new file mode 100644 index 000000000000..e9336d79f6df --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -0,0 +1,59 @@ +import Button from "components/buttons/Button"; +import CustomLink from "components/CustomLink"; +import Graphic from "components/Graphic"; +import Modal from "components/Modal"; +import React from "react"; + +const baseClass = "calendar-events-modal"; + +interface ICalendarEventsModal { + onExit: () => void; + onSubmit: () => void; + configured: boolean; +} + +const CalendarEventsModal = ({ + onExit, + onSubmit, + configured, +}: ICalendarEventsModal) => { + const renderPlaceholderModal = () => { + return ( + <> + + + + To create calendar events for end users if their hosts fail policies, + you must first connect Fleet to your Google Workspace service account. +
+ This can be configured in{" "} + Settings > Integrations > Calendars. + +
+ +
+ + ); + }; + const renderConfiguredModal = () => { + return <>; + }; + return ( + + {configured ? renderConfiguredModal() : renderPlaceholderModal()} + + ); +}; + +export default CalendarEventsModal; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss new file mode 100644 index 000000000000..165ff47dae24 --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss @@ -0,0 +1,3 @@ +.calendar-events-modal { + +} diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/index.ts b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/index.ts new file mode 100644 index 000000000000..b08ecf1063e4 --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/index.ts @@ -0,0 +1 @@ +export { default } from "./CalendarEventsModal"; From 901fb77ab8221a3ed590d02684f6ab13f0015e52 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Tue, 12 Mar 2024 15:37:13 -0500 Subject: [PATCH 03/22] wip - scaffold --- .../CalendarEventsModal.tsx | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index e9336d79f6df..5c2265a4f149 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -1,5 +1,7 @@ import Button from "components/buttons/Button"; +import RevealButton from "components/buttons/RevealButton"; import CustomLink from "components/CustomLink"; +import SelectTargetsDropdownStories from "components/forms/fields/SelectTargetsDropdown/SelectTargetsDropdown.stories"; import Graphic from "components/Graphic"; import Modal from "components/Modal"; import React from "react"; @@ -42,9 +44,51 @@ const CalendarEventsModal = ({ ); }; const renderConfiguredModal = () => { - return <>; + return; +
+ { + setCalEventsEnabled(!calEventsEnabled); + SelectTargetsDropdownStories({}); + }} + /> + + TBD} + helpText="A request will be sent to this URL during the calendar event. Use it to trigger auto-remidiation." + /> + { + setShowExamplePayload(!showExamplePayload); + }} + /> + {showExamplePayload && renderExamplePayload()} + {renderPoliciesList()} +
; }; - return ( + return showPreviewCalendarEvent ? ( + renderPreviewCalendarEventModal() + ) : ( Date: Wed, 13 Mar 2024 12:54:55 -0500 Subject: [PATCH 04/22] move custom hook to its own file --- .../hooks/useCheckboxListStateManagement.tsx | 36 +++++++++++++++++++ .../ManagePolicyAutomationsModal.tsx | 33 ++--------------- 2 files changed, 38 insertions(+), 31 deletions(-) create mode 100644 frontend/hooks/useCheckboxListStateManagement.tsx diff --git a/frontend/hooks/useCheckboxListStateManagement.tsx b/frontend/hooks/useCheckboxListStateManagement.tsx new file mode 100644 index 000000000000..4c1cf9d88d6c --- /dev/null +++ b/frontend/hooks/useCheckboxListStateManagement.tsx @@ -0,0 +1,36 @@ +import { useState } from "react"; + +import { IPolicy } from "interfaces/policy"; + +interface ICheckedPolicy { + name?: string; + id: number; + isChecked: boolean; +} + +const useCheckboxListStateManagement = ( + allPolicies: IPolicy[], + automatedPolicies: number[] | undefined +) => { + const [policyItems, setPolicyItems] = useState(() => { + return allPolicies.map(({ name, id }) => ({ + name, + id, + isChecked: !!automatedPolicies?.includes(id), + })); + }); + + const updatePolicyItems = (policyId: number) => { + setPolicyItems((prevItems) => + prevItems.map((policy) => + policy.id !== policyId + ? policy + : { ...policy, isChecked: !policy.isChecked } + ) + ); + }; + + return { policyItems, updatePolicyItems }; +}; + +export default useCheckboxListStateManagement; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/ManagePolicyAutomationsModal/ManagePolicyAutomationsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/ManagePolicyAutomationsModal/ManagePolicyAutomationsModal.tsx index ae71c22cfecd..d25782e517c7 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/ManagePolicyAutomationsModal/ManagePolicyAutomationsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/ManagePolicyAutomationsModal/ManagePolicyAutomationsModal.tsx @@ -8,6 +8,8 @@ import { IPolicy } from "interfaces/policy"; import { ITeamAutomationsConfig } from "interfaces/team"; import PATHS from "router/paths"; +import useCheckboxListStateManagement from "hooks/useCheckboxListStateManagement"; + import Modal from "components/Modal"; import Button from "components/buttons/Button"; import Slider from "components/forms/fields/Slider"; @@ -37,12 +39,6 @@ interface IManagePolicyAutomationsModalProps { togglePreviewPayloadModal: () => void; } -interface ICheckedPolicy { - name?: string; - id: number; - isChecked: boolean; -} - const findEnabledIntegration = ({ jira, zendesk }: IIntegrations) => { return ( jira?.find((j) => j.enable_failing_policies) || @@ -58,31 +54,6 @@ const getIntegrationType = (integration?: IIntegration) => { ); }; -const useCheckboxListStateManagement = ( - allPolicies: IPolicy[], - automatedPolicies: number[] | undefined -) => { - const [policyItems, setPolicyItems] = useState(() => { - return allPolicies.map(({ name, id }) => ({ - name, - id, - isChecked: !!automatedPolicies?.includes(id), - })); - }); - - const updatePolicyItems = (policyId: number) => { - setPolicyItems((prevItems) => - prevItems.map((policy) => - policy.id !== policyId - ? policy - : { ...policy, isChecked: !policy.isChecked } - ) - ); - }; - - return { policyItems, updatePolicyItems }; -}; - const baseClass = "manage-policy-automations-modal"; const ManagePolicyAutomationsModal = ({ From c929e44e869ec315f671c2ef44dab15beb35f93d Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Wed, 13 Mar 2024 18:14:34 -0500 Subject: [PATCH 05/22] wip2 - it renders --- .../components/forms/fields/Slider/Slider.tsx | 5 +- frontend/interfaces/integration.ts | 17 +++ .../ManagePoliciesPage/ManagePoliciesPage.tsx | 31 ++++ .../CalendarEventsModal.tsx | 136 +++++++++++++++--- frontend/utilities/typeguards.ts | 12 ++ 5 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 frontend/utilities/typeguards.ts diff --git a/frontend/components/forms/fields/Slider/Slider.tsx b/frontend/components/forms/fields/Slider/Slider.tsx index 2b368275f1d9..db21fe6c9aa2 100644 --- a/frontend/components/forms/fields/Slider/Slider.tsx +++ b/frontend/components/forms/fields/Slider/Slider.tsx @@ -6,7 +6,10 @@ import FormField from "components/forms/FormField"; import { IFormFieldProps } from "components/forms/FormField/FormField"; interface ISliderProps { - onChange: () => void; + onChange: (newValue?: { + name: string; + value: string | number | boolean; + }) => void; value: boolean; inactiveText: string; activeText: string; diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index adcbeeb7e786..d9d37638767a 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -60,7 +60,24 @@ export interface IIntegrationFormErrors { enableSoftwareVulnerabilities?: boolean; } +export interface IGlobalCalendarIntegration { + email: string; + private_key: string; + domain: string; +} + +interface ITeamCalendarServiceAccount { + email: string; + enable_calendar_events: boolean; + policies: { name: string; id: number }[]; +} + export interface IIntegrations { zendesk: IZendeskIntegration[]; jira: IJiraIntegration[]; + // global setting may have more than one, team can only have one + google_calendar?: + | IGlobalCalendarIntegration[] + | ITeamCalendarServiceAccount + | null; } diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index be95e222d71d..68a744660e0c 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -44,6 +44,7 @@ import PoliciesTable from "./components/PoliciesTable"; import ManagePolicyAutomationsModal from "./components/ManagePolicyAutomationsModal"; import AddPolicyModal from "./components/AddPolicyModal"; import DeletePolicyModal from "./components/DeletePolicyModal"; +import CalendarEventsModal from "./components/CalendarEventsModal"; interface IManagePoliciesPageProps { router: InjectedRouter; @@ -132,6 +133,7 @@ const ManagePolicyPage = ({ const [showPreviewPayloadModal, setShowPreviewPayloadModal] = useState(false); const [showAddPolicyModal, setShowAddPolicyModal] = useState(false); const [showDeletePolicyModal, setShowDeletePolicyModal] = useState(false); + const [showCalendarEventsModal, setShowCalendarEventsModal] = useState(false); const [teamPolicies, setTeamPolicies] = useState(); const [inheritedPolicies, setInheritedPolicies] = useState(); @@ -353,6 +355,13 @@ const ManagePolicyPage = ({ } ); + // wip.../TODO + // let calendarConfigured = false; + // const googleCalendarGlobalConfig = config?.integrations.google_calendar; + // if (typeof googleCalendarGlobalConfig + // isGlobalCalendarConfig(googleCalendarGlobalConfig) && + // googleCalendarGlobalConfig.length > 0; + const { data: teamConfig, isFetching: isFetchingTeamConfig, @@ -486,6 +495,10 @@ const ManagePolicyPage = ({ const toggleDeletePolicyModal = () => setShowDeletePolicyModal(!showDeletePolicyModal); + const toggleCalendarEventsModal = () => { + setShowCalendarEventsModal(!showCalendarEventsModal); + }; + const toggleShowInheritedPolicies = () => { // URL source of truth const locationPath = getNextLocationPath({ @@ -822,6 +835,24 @@ const ManagePolicyPage = ({ onSubmit={onDeletePolicySubmit} /> )} + {/* {showCalendarEventsModal && ( */} + <>{/* TODO */}} + // configured={config?.integrations.google_calendar} + configured + // TODO - narrow that type! + // enabled={ + // teamConfig?.integrations.google_calendar?.enable_calendar_events + // } + enabled + // TODO + url="https://google.com" + // TODO + policies={[]} + enabledPolicies={[]} + /> + {/* )} */} ); diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index 5c2265a4f149..1a96456ded85 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -1,10 +1,17 @@ +import React, { useCallback, useState } from "react"; + +import { IPolicy } from "interfaces/policy"; + +import validURL from "components/forms/validators/valid_url"; + import Button from "components/buttons/Button"; import RevealButton from "components/buttons/RevealButton"; import CustomLink from "components/CustomLink"; -import SelectTargetsDropdownStories from "components/forms/fields/SelectTargetsDropdown/SelectTargetsDropdown.stories"; +import Slider from "components/forms/fields/Slider"; +// @ts-ignore +import InputField from "components/forms/fields/InputField"; import Graphic from "components/Graphic"; import Modal from "components/Modal"; -import React from "react"; const baseClass = "calendar-events-modal"; @@ -12,13 +19,104 @@ interface ICalendarEventsModal { onExit: () => void; onSubmit: () => void; configured: boolean; + enabled: boolean; + url: string; + policies: IPolicy[]; + enabledPolicies: { name: string; id: number }[]; +} + +interface IFormPolicy { + name: string; + id: number; + checked: boolean; +} +interface ICalendarEventsFormData { + enabled: boolean; + url: string; + policies: IFormPolicy[]; } +// allows any policy name to be the name of a form field, one of the checkboxes +type FormNames = string; + const CalendarEventsModal = ({ onExit, onSubmit, configured, + enabled, + url, + policies, + enabledPolicies, }: ICalendarEventsModal) => { + const [formData, setFormData] = useState({ + enabled, + url, + // TODO - stay udpdated on state of backend approach to syncing policies in the policies table + // and in the new calendar table + // id may change if policy was deleted + // name could change if policy was renamed + policies: policies.map((policy) => ({ + name: policy.name, + id: policy.id, + checked: enabledPolicies.some( + (enabledPolicy) => enabledPolicy.id === policy.id + ), + })), + }); + const [formErrors, setFormErrors] = useState>( + {} + ); + const [showPreviewCalendarEvent, setShowPreviewCalendarEvent] = useState( + false + ); + + const validateCalendarEventsFormData = ( + curFormData: ICalendarEventsFormData + ) => { + const errors: Record = {}; + const { url: curUrl } = curFormData; + if (!validURL({ url: curUrl })) { + const errorPrefix = curUrl ? `${curUrl} is not` : "Please enter"; + errors.resolutionWebhookUrl = `${errorPrefix} a valid resolution webhook URL`; + } + return {}; + }; + + const onInputChange = useCallback( + (newVal: { name: FormNames; value: string | number | boolean }) => { + const { name, value } = newVal; + let newFormData: ICalendarEventsFormData; + if (["enabled", "url"].includes(name)) { + newFormData = { ...formData, [name]: value }; + } else if (typeof value === "boolean") { + const newFormPolicies = formData.policies.map((formPolicy) => { + if (formPolicy.name === name) { + return { ...formPolicy, checked: value }; + } + return formPolicy; + }); + newFormData = { ...formData, policies: newFormPolicies }; + } else { + throw TypeError("Unexpected value type for policy checkbox"); + } + setFormData(newFormData); + setFormErrors(validateCalendarEventsFormData(newFormData)); + }, + [formData] + ); + + const togglePreviewCalendarEvent = () => { + // TODO + }; + + const renderPoliciesList = () => { + // TODO + }; + const renderPreviewCalendarEventModal = () => { + // TODO + return <>; + }; + const renderPlaceholderModal = () => { return ( <> @@ -43,15 +141,16 @@ const CalendarEventsModal = ({ ); }; - const renderConfiguredModal = () => { - return; + + const renderConfiguredModal = () => (
{ - setCalEventsEnabled(!calEventsEnabled); - SelectTargetsDropdownStories({}); + onInputChange({ name: "enabled", value: !formData.enabled }); }} + inactiveText="Disabled" + activeText="Enabled" />
; - }; - return showPreviewCalendarEvent ? ( - renderPreviewCalendarEventModal() - ) : ( + + ); + + if (showPreviewCalendarEvent) { + return renderPreviewCalendarEventModal(); + } + return ( typeof value === "boolean"; + +const isGlobalCalendarConfig = ( + value: any +): value is IGlobalCalendarIntegration[] => { + // if it's an array, it's the global config + return value.length !== undefined; +}; + +export default { isBoolean, isGlobalCalendarConfig }; From 69a0e0f061dee14081b5c05cb6c1416da439b2b5 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Thu, 14 Mar 2024 11:20:14 -0500 Subject: [PATCH 06/22] wip - update interfaces --- frontend/interfaces/integration.ts | 21 +++++----- frontend/interfaces/team.ts | 4 +- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 40 ++++++++++--------- .../CalendarEventsModal.tsx | 6 +-- 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index d9d37638767a..f7b50a7a4016 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -62,22 +62,25 @@ export interface IIntegrationFormErrors { export interface IGlobalCalendarIntegration { email: string; - private_key: string; domain: string; + private_key: string; } -interface ITeamCalendarServiceAccount { - email: string; +interface ITeamCalendarSettings { + resolution_webhook_url: string; enable_calendar_events: boolean; - policies: { name: string; id: number }[]; + policies: number[]; } export interface IIntegrations { zendesk: IZendeskIntegration[]; jira: IJiraIntegration[]; - // global setting may have more than one, team can only have one - google_calendar?: - | IGlobalCalendarIntegration[] - | ITeamCalendarServiceAccount - | null; +} + +export interface IGlobalIntegrations extends IIntegrations { + google_calendar: IGlobalCalendarIntegration[] | null; +} + +export interface ITeamIntegrations extends IIntegrations { + google_calendar: ITeamCalendarSettings | null; } diff --git a/frontend/interfaces/team.ts b/frontend/interfaces/team.ts index 435075902a16..8fa4726022af 100644 --- a/frontend/interfaces/team.ts +++ b/frontend/interfaces/team.ts @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import { IConfigFeatures, IWebhookSettings } from "./config"; import enrollSecretInterface, { IEnrollSecret } from "./enroll_secret"; -import { IIntegrations } from "./integration"; +import { ITeamIntegrations } from "./integration"; import { UserRole } from "./user"; export default PropTypes.shape({ @@ -82,7 +82,7 @@ export type ITeamWebhookSettings = Pick< */ export interface ITeamAutomationsConfig { webhook_settings: ITeamWebhookSettings; - integrations: IIntegrations; + integrations: ITeamIntegrations; } /** diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 68a744660e0c..663ca19d5f12 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -835,24 +835,28 @@ const ManagePolicyPage = ({ onSubmit={onDeletePolicySubmit} /> )} - {/* {showCalendarEventsModal && ( */} - <>{/* TODO */}} - // configured={config?.integrations.google_calendar} - configured - // TODO - narrow that type! - // enabled={ - // teamConfig?.integrations.google_calendar?.enable_calendar_events - // } - enabled - // TODO - url="https://google.com" - // TODO - policies={[]} - enabledPolicies={[]} - /> - {/* )} */} + {showCalendarEventsModal && ( + <>{/* TODO */}} + // TODO - remove dummy prop values + // configured={config?.integrations.google_calendar} + configured + // enabled={ + // teamConfig?.integrations.google_calendar?.enable_calendar_events + // } + enabled + // url={ + // teamConfig?.integrations.google_calendar?.resolution_webhook_url || + // "" + // } + url="https://google.com" + policies={teamPolicies || []} + enabledPolicies={ + teamConfig?.integrations.google_calendar?.policies || [] + } + /> + )} ); diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index 1a96456ded85..d037bd08877f 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -22,7 +22,7 @@ interface ICalendarEventsModal { enabled: boolean; url: string; policies: IPolicy[]; - enabledPolicies: { name: string; id: number }[]; + enabledPolicies: number[]; } interface IFormPolicy { @@ -58,9 +58,7 @@ const CalendarEventsModal = ({ policies: policies.map((policy) => ({ name: policy.name, id: policy.id, - checked: enabledPolicies.some( - (enabledPolicy) => enabledPolicy.id === policy.id - ), + checked: enabledPolicies.includes(policy.id), })), }); const [formErrors, setFormErrors] = useState>( From 4a9950d2ffc5aa40b46c2a70f77750bff186584f Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Thu, 14 Mar 2024 13:26:39 -0500 Subject: [PATCH 07/22] wip - building policies dropdown button --- frontend/interfaces/integration.ts | 3 +- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 70 +++++++++++++++---- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index f7b50a7a4016..cebba938c3d8 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -69,7 +69,8 @@ export interface IGlobalCalendarIntegration { interface ITeamCalendarSettings { resolution_webhook_url: string; enable_calendar_events: boolean; - policies: number[]; + // name required to set - id is returned in response + policies: { name: string; id?: number }[]; } export interface IIntegrations { diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 663ca19d5f12..40adc0d9cc3e 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -20,6 +20,7 @@ import { IPoliciesCountResponse, } from "interfaces/policy"; import { ITeamConfig } from "interfaces/team"; +import { IDropdownOption } from "interfaces/dropdownOption"; import configAPI from "services/entities/config"; import globalPoliciesAPI, { @@ -34,6 +35,8 @@ import teamsAPI, { ILoadTeamResponse } from "services/entities/teams"; import { ITableQueryData } from "components/TableContainer/TableContainer"; import Button from "components/buttons/Button"; +// @ts-ignore +import Dropdown from "components/forms/fields/Dropdown"; import RevealButton from "components/buttons/RevealButton"; import Spinner from "components/Spinner"; import TeamsDropdown from "components/TeamsDropdown"; @@ -499,6 +502,18 @@ const ManagePolicyPage = ({ setShowCalendarEventsModal(!showCalendarEventsModal); }; + const onSelectAutomationOption = (option: string) => { + switch (option) { + case "calendar_events": + toggleCalendarEventsModal(); + break; + case "other_workflows": + toggleManageAutomationsModal(); + break; + default: + } + }; + const toggleShowInheritedPolicies = () => { // URL source of truth const locationPath = getNextLocationPath({ @@ -700,6 +715,21 @@ const ManagePolicyPage = ({ ); }; + const automationsDropdownOptions: IDropdownOption[] = [ + { + label: "Calendar events", + value: "calendar_events", + // TODO - disable and different tooltips for each of below scenarios + disabled: !isPremiumTier || teamIdForApi === -1, // TODO - how does this interact with below setting? + premiumOnly: true, + }, + { + label: "Other workflows", + value: "other_workflows", + disabled: false, // TODO - how does this interact with below setting? + premiumOnly: false, + }, + ]; return (
@@ -727,18 +757,26 @@ const ManagePolicyPage = ({ {showCtaButtons && (
{canManageAutomations && automationsConfig && ( - + + + // )} {canAddOrDeletePolicy && (
@@ -852,9 +890,11 @@ const ManagePolicyPage = ({ // } url="https://google.com" policies={teamPolicies || []} - enabledPolicies={ - teamConfig?.integrations.google_calendar?.policies || [] - } + // TODO - may need to update per updated policy structure + // enabledPolicies={ + // teamConfig?.integrations.google_calendar?.policies || [] + // } + enabledPolicies={[]} /> )}
From a7546291b1b9068fcfdfc41293a6743f7846f12b Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Mon, 18 Mar 2024 13:48:29 -0700 Subject: [PATCH 08/22] finish styling of policies dropdown --- .../HostActionsDropdown/_styles.scss | 105 +----------------- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 16 +-- .../policies/ManagePoliciesPage/_styles.scss | 21 +++- .../CalendarEventsModal.tsx | 58 +++++----- frontend/styles/var/mixins.scss | 100 +++++++++++++++++ 5 files changed, 161 insertions(+), 139 deletions(-) diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/_styles.scss b/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/_styles.scss index 1152dcfb5f80..06bd48a653fd 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/_styles.scss +++ b/frontend/pages/hosts/details/HostDetailsPage/HostActionsDropdown/_styles.scss @@ -1,104 +1,9 @@ .host-actions-dropdown { - .form-field { - margin: 0; + @include button-dropdown; + .Select-multi-value-wrapper { + width: 55px; } - - .Select { - position: relative; - border: 0; - height: auto; - - &.is-focused, - &:hover { - border: 0; - } - - &.is-focused:not(.is-open) { - .Select-control { - background-color: initial; - } - } - - .Select-control { - display: flex; - background-color: initial; - height: auto; - justify-content: space-between; - border: 0; - cursor: pointer; - - &:hover { - box-shadow: none; - } - - &:hover .Select-placeholder { - color: $core-vibrant-blue; - } - - .Select-placeholder { - color: $core-fleet-black; - font-size: 14px; - line-height: normal; - padding-left: 0; - margin-top: 1px; - } - - .Select-input { - height: auto; - } - - .Select-arrow-zone { - display: flex; - } - } - - .Select-multi-value-wrapper { - width: 55px; - } - - .Select-placeholder { - display: flex; - align-items: center; - } - - .Select-menu-outer { - margin-top: $pad-xsmall; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); - border-radius: $border-radius; - z-index: 6; - overflow: hidden; - border: 0; - width: 188px; - left: unset; - top: unset; - max-height: none; - padding: $pad-small; - position: absolute; - left: -120px; - - .Select-menu { - max-height: none; - } - } - - .Select-arrow { - transition: transform 0.25s ease; - } - - &:not(.is-open) { - .Select-control:hover .Select-arrow { - content: url("../assets/images/icon-chevron-blue-16x16@2x.png"); - } - } - - &.is-open { - .Select-control .Select-placeholder { - color: $core-vibrant-blue; - } - - .Select-arrow { - transform: rotate(180deg); - } - } + .Select > .Select-menu-outer { + left: -120px; } } diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 40adc0d9cc3e..b7edd18701d1 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -757,13 +757,15 @@ const ManagePolicyPage = ({ {showCtaButtons && (
{canManageAutomations && automationsConfig && ( - +
+ +
// - - {/* + { + onInputChange({ name: "enabled", value: !formData.enabled }); + }} + inactiveText="Disabled" + activeText="Enabled" + /> + + + {/* {showExamplePayload && renderExamplePayload()} */} - {renderPoliciesList()} + {renderPoliciesList()} +
); diff --git a/frontend/styles/var/mixins.scss b/frontend/styles/var/mixins.scss index a600c5c848ac..a5c2d855466d 100644 --- a/frontend/styles/var/mixins.scss +++ b/frontend/styles/var/mixins.scss @@ -220,3 +220,103 @@ $max-width: 2560px; // compensate in layout for extra clickable area button height margin: -8px 0; } + +@mixin button-dropdown { + .form-field { + margin: 0; + } + + .Select { + position: relative; + border: 0; + height: auto; + + &.is-focused, + &:hover { + border: 0; + } + + &.is-focused:not(.is-open) { + .Select-control { + background-color: initial; + } + } + + .Select-control { + display: flex; + background-color: initial; + height: auto; + justify-content: space-between; + border: 0; + cursor: pointer; + + &:hover { + box-shadow: none; + } + + &:hover .Select-placeholder { + color: $core-vibrant-blue; + } + + .Select-placeholder { + color: $core-fleet-black; + font-size: 14px; + line-height: normal; + padding-left: 0; + margin-top: 1px; + } + + .Select-input { + height: auto; + } + + .Select-arrow-zone { + display: flex; + } + } + + .Select-placeholder { + display: flex; + align-items: center; + } + + .Select-menu-outer { + margin-top: $pad-xsmall; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + border-radius: $border-radius; + z-index: 6; + overflow: hidden; + border: 0; + width: 188px; + left: unset; + top: unset; + max-height: none; + padding: $pad-small; + position: absolute; + + .Select-menu { + max-height: none; + } + } + + .Select-arrow { + transition: transform 0.25s ease; + } + + &:not(.is-open) { + .Select-control:hover .Select-arrow { + content: url("../assets/images/icon-chevron-blue-16x16@2x.png"); + } + } + + &.is-open { + .Select-control .Select-placeholder { + color: $core-vibrant-blue; + } + + .Select-arrow { + transform: rotate(180deg); + } + } + } +} From 8ad6f9a978d3bfba2323d786f7dd052c930ca5f2 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Mon, 18 Mar 2024 15:37:30 -0700 Subject: [PATCH 09/22] wip - render policy checkboxes in modal --- frontend/__mocks__/policyMock.ts | 1 + frontend/interfaces/integration.ts | 6 +-- frontend/interfaces/policy.ts | 1 + .../ManagePoliciesPage/ManagePoliciesPage.tsx | 5 -- .../CalendarEventsModal.tsx | 46 +++++++++++++++---- 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/frontend/__mocks__/policyMock.ts b/frontend/__mocks__/policyMock.ts index 048dd6d49638..c66c58a0bc28 100644 --- a/frontend/__mocks__/policyMock.ts +++ b/frontend/__mocks__/policyMock.ts @@ -22,6 +22,7 @@ const DEFAULT_POLICY_MOCK: IPolicyStats = { webhook: "Off", has_run: true, next_update_ms: 3600000, + calendar_events_enabled: true, }; const createMockPolicy = (overrides?: Partial): IPolicyStats => { diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index cebba938c3d8..2489d4dea43a 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -62,15 +62,13 @@ export interface IIntegrationFormErrors { export interface IGlobalCalendarIntegration { email: string; - domain: string; private_key: string; + domain: string; } interface ITeamCalendarSettings { - resolution_webhook_url: string; enable_calendar_events: boolean; - // name required to set - id is returned in response - policies: { name: string; id?: number }[]; + webhook_url: string; } export interface IIntegrations { diff --git a/frontend/interfaces/policy.ts b/frontend/interfaces/policy.ts index 4858de5f37c7..4a59359fb217 100644 --- a/frontend/interfaces/policy.ts +++ b/frontend/interfaces/policy.ts @@ -40,6 +40,7 @@ export interface IPolicy { created_at: string; updated_at: string; critical: boolean; + calendar_events_enabled: boolean; } // Used on the manage hosts page and other places where aggregate stats are displayed diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index b7edd18701d1..d7331569d5a2 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -892,11 +892,6 @@ const ManagePolicyPage = ({ // } url="https://google.com" policies={teamPolicies || []} - // TODO - may need to update per updated policy structure - // enabledPolicies={ - // teamConfig?.integrations.google_calendar?.policies || [] - // } - enabledPolicies={[]} /> )}
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index 0a6ffd9c1bb5..a3e310a2e49a 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -12,6 +12,7 @@ import Slider from "components/forms/fields/Slider"; import InputField from "components/forms/fields/InputField"; import Graphic from "components/Graphic"; import Modal from "components/Modal"; +import Checkbox from "components/forms/fields/Checkbox"; const baseClass = "calendar-events-modal"; @@ -22,13 +23,12 @@ interface ICalendarEventsModal { enabled: boolean; url: string; policies: IPolicy[]; - enabledPolicies: number[]; } interface IFormPolicy { name: string; id: number; - checked: boolean; + isChecked: boolean; } interface ICalendarEventsFormData { enabled: boolean; @@ -46,7 +46,6 @@ const CalendarEventsModal = ({ enabled, url, policies, - enabledPolicies, }: ICalendarEventsModal) => { const [formData, setFormData] = useState({ enabled, @@ -58,7 +57,7 @@ const CalendarEventsModal = ({ policies: policies.map((policy) => ({ name: policy.name, id: policy.id, - checked: enabledPolicies.includes(policy.id), + isChecked: policy.calendar_events_enabled || false, })), }); const [formErrors, setFormErrors] = useState>( @@ -89,7 +88,7 @@ const CalendarEventsModal = ({ } else if (typeof value === "boolean") { const newFormPolicies = formData.policies.map((formPolicy) => { if (formPolicy.name === name) { - return { ...formPolicy, checked: value }; + return { ...formPolicy, isChecked: value }; } return formPolicy; }); @@ -107,8 +106,38 @@ const CalendarEventsModal = ({ // TODO }; - const renderPoliciesList = () => { - // TODO + const renderPolicies = () => { + return ( +
+
Policies:
+ {formData.policies.map((policy) => { + const { isChecked, name, id } = policy; + return ( +
+ { + onInputChange({ name, value: !isChecked }); + }} + > + {name} + +
+ ); + })} + + A calendar event will be created for end users if one of their hosts + fail any of these policies.{" "} + + +
+ ); }; const renderPreviewCalendarEventModal = () => { // TODO @@ -180,7 +209,7 @@ const CalendarEventsModal = ({ }} /> {showExamplePayload && renderExamplePayload()} */} - {renderPoliciesList()} + {renderPolicies()}
); @@ -194,6 +223,7 @@ const CalendarEventsModal = ({ onExit={onExit} onEnter={configured ? onSubmit : onExit} className={baseClass} + width="large" > {configured ? renderConfiguredModal() : renderPlaceholderModal()}
From 9a9f29dbef3eee530fc114bb3dd1a73a3cd017a8 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Mon, 18 Mar 2024 19:19:17 -0700 Subject: [PATCH 10/22] wip - define update function. types need improvement --- frontend/interfaces/policy.ts | 1 + .../ManagePoliciesPage/ManagePoliciesPage.tsx | 65 ++++++++++- .../CalendarEventsModal.tsx | 109 +++++++++++------- frontend/services/entities/team_policies.ts | 2 + frontend/services/entities/teams.ts | 4 +- 5 files changed, 133 insertions(+), 48 deletions(-) diff --git a/frontend/interfaces/policy.ts b/frontend/interfaces/policy.ts index 4a59359fb217..056ab704066c 100644 --- a/frontend/interfaces/policy.ts +++ b/frontend/interfaces/policy.ts @@ -91,6 +91,7 @@ export interface IPolicyFormData { query?: string | number | boolean | undefined; team_id?: number; id?: number; + calendar_events_enabled?: boolean; } export interface IPolicyNew { diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index d7331569d5a2..f572195d1935 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useContext, useEffect, useState } from "react"; import { useQuery } from "react-query"; import { InjectedRouter } from "react-router/lib/Router"; import PATHS from "router/paths"; -import { noop, isEqual } from "lodash"; +import { noop, isEqual, set } from "lodash"; import { getNextLocationPath } from "utilities/helpers"; @@ -12,7 +12,11 @@ import { TableContext } from "context/table"; import { NotificationContext } from "context/notification"; import useTeamIdParam from "hooks/useTeamIdParam"; import { IConfig, IWebhookSettings } from "interfaces/config"; -import { IIntegrations } from "interfaces/integration"; +import { + IGlobalIntegrations, + IIntegrations, + ITeamIntegrations, +} from "interfaces/integration"; import { IPolicyStats, ILoadAllPoliciesResponse, @@ -48,6 +52,8 @@ import ManagePolicyAutomationsModal from "./components/ManagePolicyAutomationsMo import AddPolicyModal from "./components/AddPolicyModal"; import DeletePolicyModal from "./components/DeletePolicyModal"; import CalendarEventsModal from "./components/CalendarEventsModal"; +import { ICalendarEventsFormData } from "./components/CalendarEventsModal/CalendarEventsModal"; +import { IApiError } from "interfaces/errors"; interface IManagePoliciesPageProps { router: InjectedRouter; @@ -129,6 +135,10 @@ const ManagePolicyPage = ({ const [isUpdatingAutomations, setIsUpdatingAutomations] = useState(false); const [isUpdatingPolicies, setIsUpdatingPolicies] = useState(false); + const [ + updatingPolicyEnabledCalendarEvents, + setUpdatingPolicyEnabledCalendarEvents, + ] = useState(false); const [selectedPolicyIds, setSelectedPolicyIds] = useState([]); const [showManageAutomationsModal, setShowManageAutomationsModal] = useState( false @@ -529,7 +539,7 @@ const ManagePolicyPage = ({ const handleUpdateAutomations = async (requestBody: { webhook_settings: Pick; - integrations: IIntegrations; + integrations: IGlobalIntegrations | ITeamIntegrations; }) => { setIsUpdatingAutomations(true); try { @@ -550,6 +560,49 @@ const ManagePolicyPage = ({ } }; + const updatePolicyEnabledCalendarEvents = async ( + formData: ICalendarEventsFormData + ) => { + setUpdatingPolicyEnabledCalendarEvents(true); + + try { + // update enabled and URL in config + const configResponse = teamsAPI.update( + { + integrations: { + google_calendar: { + enable_calendar_events: formData.enabled, + webhook_url: formData.url, + }, + // TODO - can omit these? + zendesk: teamConfig?.integrations.zendesk || [], + jira: teamConfig?.integrations.jira || [], + }, + }, + teamIdForApi + ); + + // update policies calendar events enabled + const policyResponses = formData.policies.map((formPolicy) => + teamPoliciesAPI.update(formPolicy.id, { + calendar_events_enabled: formPolicy.isChecked, + team_id: teamIdForApi, + }) + ); + + await Promise.all([configResponse, ...policyResponses]); + } catch { + (errorResponse: { data: IApiError }) => { + renderFlash( + "error", + `Could not update team settings. ${errorResponse.data.errors[0].reason}` + ); + }; + } finally { + setUpdatingPolicyEnabledCalendarEvents(false); + } + }; + const onAddPolicyClick = () => { setLastEditedQueryName(""); setLastEditedQueryDescription(""); @@ -758,6 +811,7 @@ const ManagePolicyPage = ({
{canManageAutomations && automationsConfig && (
+ {/* TODO - add subtext to dropdown options. See Hosts dropdown filters on manage hosts page */} <>{/* TODO */}} + updatePolicyEnabledCalendarEvents={ + updatePolicyEnabledCalendarEvents + } // TODO - remove dummy prop values // configured={config?.integrations.google_calendar} configured @@ -892,6 +948,7 @@ const ManagePolicyPage = ({ // } url="https://google.com" policies={teamPolicies || []} + isUpdating={updatingPolicyEnabledCalendarEvents} /> )}
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index a3e310a2e49a..bfa153d214fe 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -16,32 +16,36 @@ import Checkbox from "components/forms/fields/Checkbox"; const baseClass = "calendar-events-modal"; -interface ICalendarEventsModal { - onExit: () => void; - onSubmit: () => void; - configured: boolean; - enabled: boolean; - url: string; - policies: IPolicy[]; -} - interface IFormPolicy { name: string; id: number; isChecked: boolean; } -interface ICalendarEventsFormData { +export interface ICalendarEventsFormData { enabled: boolean; url: string; policies: IFormPolicy[]; } +interface ICalendarEventsModal { + onExit: () => void; + updatePolicyEnabledCalendarEvents: ( + formData: ICalendarEventsFormData + ) => void; + isUpdating: boolean; + configured: boolean; + enabled: boolean; + url: string; + policies: IPolicy[]; +} + // allows any policy name to be the name of a form field, one of the checkboxes type FormNames = string; const CalendarEventsModal = ({ onExit, - onSubmit, + updatePolicyEnabledCalendarEvents, + isUpdating, configured, enabled, url, @@ -171,34 +175,33 @@ const CalendarEventsModal = ({ const renderConfiguredModal = () => (
- <> - { - onInputChange({ name: "enabled", value: !formData.enabled }); - }} - inactiveText="Disabled" - activeText="Enabled" - /> - - - {/* { + onInputChange({ name: "enabled", value: !formData.enabled }); + }} + inactiveText="Disabled" + activeText="Enabled" + /> + + + {/* {showExamplePayload && renderExamplePayload()} */} - {renderPolicies()} - + {renderPolicies()} + +
+ + +
); @@ -221,7 +240,13 @@ const CalendarEventsModal = ({ { + updatePolicyEnabledCalendarEvents(formData); + } + : onExit + } className={baseClass} width="large" > diff --git a/frontend/services/entities/team_policies.ts b/frontend/services/entities/team_policies.ts index d7a03f1f1b98..2858938dc737 100644 --- a/frontend/services/entities/team_policies.ts +++ b/frontend/services/entities/team_policies.ts @@ -87,6 +87,7 @@ export default { resolution, platform, critical, + calendar_events_enabled, } = data; const { TEAMS } = endpoints; const path = `${TEAMS}/${team_id}/policies/${id}`; @@ -98,6 +99,7 @@ export default { resolution, platform, critical, + calendar_events_enabled, }); }, destroy: (teamId: number | undefined, ids: number[]) => { diff --git a/frontend/services/entities/teams.ts b/frontend/services/entities/teams.ts index 3c7e1a26193b..f0d647478d65 100644 --- a/frontend/services/entities/teams.ts +++ b/frontend/services/entities/teams.ts @@ -5,7 +5,7 @@ import { pick } from "lodash"; import { buildQueryStringFromParams } from "utilities/url"; import { IEnrollSecret } from "interfaces/enroll_secret"; -import { IIntegrations } from "interfaces/integration"; +import { ITeamIntegrations } from "interfaces/integration"; import { API_NO_TEAM_ID, INewTeamUsersBody, @@ -39,7 +39,7 @@ export interface ITeamFormData { export interface IUpdateTeamFormData { name: string; webhook_settings: Partial; - integrations: IIntegrations; + integrations: ITeamIntegrations; mdm: { macos_updates?: { minimum_version: string; From a489c544855cf75dbb060068a5d2ac3db998f745 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Tue, 19 Mar 2024 09:51:47 -0700 Subject: [PATCH 11/22] working for demo --- frontend/__mocks__/configMock.ts | 1 + frontend/interfaces/config.ts | 4 +-- frontend/interfaces/integration.ts | 9 +++++-- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 25 +++++++++++-------- .../CalendarEventsModal.tsx | 13 +++++++--- frontend/services/entities/teams.ts | 5 ++-- 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts index 26b996662a59..03e555485166 100644 --- a/frontend/__mocks__/configMock.ts +++ b/frontend/__mocks__/configMock.ts @@ -76,6 +76,7 @@ const DEFAULT_CONFIG_MOCK: IConfig = { integrations: { jira: [], zendesk: [], + google_calendar: [], }, logging: { debug: false, diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts index 1df44de33d8c..9fb770223247 100644 --- a/frontend/interfaces/config.ts +++ b/frontend/interfaces/config.ts @@ -4,7 +4,7 @@ import { IWebhookFailingPolicies, IWebhookSoftwareVulnerabilities, } from "interfaces/webhook"; -import { IIntegrations } from "./integration"; +import { IGlobalIntegrations, IIntegrations } from "./integration"; export interface ILicense { tier: string; @@ -175,7 +175,7 @@ export interface IConfig { // databases_path: string; // }; webhook_settings: IWebhookSettings; - integrations: IIntegrations; + integrations: IGlobalIntegrations; logging: { debug: boolean; json: boolean; diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index 2489d4dea43a..49156d6277f0 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -71,15 +71,20 @@ interface ITeamCalendarSettings { webhook_url: string; } +// zendesk and jira fields are coupled – if one is present, the other needs to be present. If +// one is present and the other is null/missing, the other will be nullified. google_calendar is +// separated – it can be present without the other 2 without nullifying them. +// TODO: Update these types to reflect this. + export interface IIntegrations { zendesk: IZendeskIntegration[]; jira: IJiraIntegration[]; } export interface IGlobalIntegrations extends IIntegrations { - google_calendar: IGlobalCalendarIntegration[] | null; + google_calendar?: IGlobalCalendarIntegration[] | null; } export interface ITeamIntegrations extends IIntegrations { - google_calendar: ITeamCalendarSettings | null; + google_calendar?: ITeamCalendarSettings | null; } diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index f572195d1935..e95eed64ccd1 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -539,7 +539,8 @@ const ManagePolicyPage = ({ const handleUpdateAutomations = async (requestBody: { webhook_settings: Pick; - integrations: IGlobalIntegrations | ITeamIntegrations; + // TODO - update below type to specify team integration + integrations: IIntegrations; }) => { setIsUpdatingAutomations(true); try { @@ -783,6 +784,12 @@ const ManagePolicyPage = ({ premiumOnly: false, }, ]; + + const isCalEventsConfigured = + (config?.integrations.google_calendar && + config?.integrations.google_calendar.length > 0) ?? + false; + return (
@@ -936,17 +943,13 @@ const ManagePolicyPage = ({ updatePolicyEnabledCalendarEvents } // TODO - remove dummy prop values - // configured={config?.integrations.google_calendar} + // configured={isCalEventsConfigured} configured - // enabled={ - // teamConfig?.integrations.google_calendar?.enable_calendar_events - // } - enabled - // url={ - // teamConfig?.integrations.google_calendar?.resolution_webhook_url || - // "" - // } - url="https://google.com" + enabled={ + teamConfig?.integrations.google_calendar + ?.enable_calendar_events ?? false + } + url={teamConfig?.integrations.google_calendar?.webhook_url || ""} policies={teamPolicies || []} isUpdating={updatingPolicyEnabledCalendarEvents} /> diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index bfa153d214fe..57f8f0b9d823 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -78,18 +78,24 @@ const CalendarEventsModal = ({ const { url: curUrl } = curFormData; if (!validURL({ url: curUrl })) { const errorPrefix = curUrl ? `${curUrl} is not` : "Please enter"; - errors.resolutionWebhookUrl = `${errorPrefix} a valid resolution webhook URL`; + errors.url = `${errorPrefix} a valid resolution webhook URL`; } - return {}; + return errors; }; + // TODO - separate change handlers for checkboxes: + // const onPolicyUpdate = ... + // const onTextFieldUpdate = ... + const onInputChange = useCallback( (newVal: { name: FormNames; value: string | number | boolean }) => { const { name, value } = newVal; let newFormData: ICalendarEventsFormData; + // for the first two fields, set the new value directly if (["enabled", "url"].includes(name)) { newFormData = { ...formData, [name]: value }; } else if (typeof value === "boolean") { + // otherwise, set the value for a nested policy const newFormPolicies = formData.policies.map((formPolicy) => { if (formPolicy.name === name) { return { ...formPolicy, isChecked: value }; @@ -194,7 +200,7 @@ const CalendarEventsModal = ({ placeholder="https://server.com/example" label="Resolution webhook URL" onChange={onInputChange} - name="resolutionWebhookUrl" + name="url" value={formData.url} parseTarget error={formErrors.url} @@ -223,6 +229,7 @@ const CalendarEventsModal = ({ }} className="save-loading" isLoading={isUpdating} + disabled={Object.keys(formErrors).length > 0} > Save diff --git a/frontend/services/entities/teams.ts b/frontend/services/entities/teams.ts index f0d647478d65..43459d212d5f 100644 --- a/frontend/services/entities/teams.ts +++ b/frontend/services/entities/teams.ts @@ -5,7 +5,7 @@ import { pick } from "lodash"; import { buildQueryStringFromParams } from "utilities/url"; import { IEnrollSecret } from "interfaces/enroll_secret"; -import { ITeamIntegrations } from "interfaces/integration"; +import { IIntegrations, ITeamIntegrations } from "interfaces/integration"; import { API_NO_TEAM_ID, INewTeamUsersBody, @@ -118,7 +118,7 @@ export default { requestBody.webhook_settings = webhook_settings; } if (integrations) { - const { jira, zendesk } = integrations; + const { jira, zendesk, google_calendar } = integrations; const teamIntegrationProps = [ "enable_failing_policies", "group_id", @@ -128,6 +128,7 @@ export default { requestBody.integrations = { jira: jira?.map((j) => pick(j, teamIntegrationProps)), zendesk: zendesk?.map((z) => pick(z, teamIntegrationProps)), + google_calendar, }; } if (mdm) { From fac0f28abb44adce4ed243b9e4be856620e03c79 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Tue, 19 Mar 2024 10:09:29 -0700 Subject: [PATCH 12/22] reorder import --- .../pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index e95eed64ccd1..d8cb81db1a5d 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -25,6 +25,7 @@ import { } from "interfaces/policy"; import { ITeamConfig } from "interfaces/team"; import { IDropdownOption } from "interfaces/dropdownOption"; +import { IApiError } from "interfaces/errors"; import configAPI from "services/entities/config"; import globalPoliciesAPI, { @@ -53,7 +54,6 @@ import AddPolicyModal from "./components/AddPolicyModal"; import DeletePolicyModal from "./components/DeletePolicyModal"; import CalendarEventsModal from "./components/CalendarEventsModal"; import { ICalendarEventsFormData } from "./components/CalendarEventsModal/CalendarEventsModal"; -import { IApiError } from "interfaces/errors"; interface IManagePoliciesPageProps { router: InjectedRouter; From 3c999bec0737b918e38152dff27852496e28e3c7 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Tue, 19 Mar 2024 12:19:50 -0700 Subject: [PATCH 13/22] on submit, close and refetch --- .../pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index d8cb81db1a5d..d3f9289367de 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -584,6 +584,7 @@ const ManagePolicyPage = ({ ); // update policies calendar events enabled + // TODO - only update changed policies const policyResponses = formData.policies.map((formPolicy) => teamPoliciesAPI.update(formPolicy.id, { calendar_events_enabled: formPolicy.isChecked, @@ -600,7 +601,10 @@ const ManagePolicyPage = ({ ); }; } finally { + toggleCalendarEventsModal(); setUpdatingPolicyEnabledCalendarEvents(false); + refetchTeamPolicies(); + refetchTeamConfig(); } }; From 70134ab6fb99ff3f49f250f4afe47d1eaec44be2 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Tue, 19 Mar 2024 13:39:41 -0700 Subject: [PATCH 14/22] update tooltip position, flash messages --- frontend/components/forms/FormField/FormField.tsx | 8 +++++++- .../components/forms/fields/InputField/InputField.jsx | 3 +++ .../ManagePoliciesPage/ManagePoliciesPage.tsx | 11 +++++------ .../CalendarEventsModal/CalendarEventsModal.tsx | 1 + 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/frontend/components/forms/FormField/FormField.tsx b/frontend/components/forms/FormField/FormField.tsx index 68d07507a719..80bf5833a291 100644 --- a/frontend/components/forms/FormField/FormField.tsx +++ b/frontend/components/forms/FormField/FormField.tsx @@ -3,6 +3,7 @@ import classnames from "classnames"; import { isEmpty } from "lodash"; import TooltipWrapper from "components/TooltipWrapper"; +import { PlacesType } from "react-tooltip-5"; // all form-field styles are defined in _global.scss, which apply here and elsewhere const baseClass = "form-field"; @@ -16,6 +17,7 @@ export interface IFormFieldProps { name: string; type: string; tooltip?: React.ReactNode; + labelTooltipPosition?: PlacesType; } const FormField = ({ @@ -27,6 +29,7 @@ const FormField = ({ name, type, tooltip, + labelTooltipPosition, }: IFormFieldProps): JSX.Element => { const renderLabel = () => { const labelWrapperClasses = classnames(`${baseClass}__label`, { @@ -45,7 +48,10 @@ const FormField = ({ > {error || (tooltip ? ( - + {label as string} ) : ( diff --git a/frontend/components/forms/fields/InputField/InputField.jsx b/frontend/components/forms/fields/InputField/InputField.jsx index 83c892eb162f..72969c256bf0 100644 --- a/frontend/components/forms/fields/InputField/InputField.jsx +++ b/frontend/components/forms/fields/InputField/InputField.jsx @@ -33,6 +33,7 @@ class InputField extends Component { ]).isRequired, parseTarget: PropTypes.bool, tooltip: PropTypes.string, + labelTooltipPosition: PropTypes.string, helpText: PropTypes.oneOfType([ PropTypes.string, PropTypes.arrayOf(PropTypes.string), @@ -55,6 +56,7 @@ class InputField extends Component { value: "", parseTarget: false, tooltip: "", + labelTooltipPosition: "", helpText: "", enableCopy: false, ignore1password: false, @@ -124,6 +126,7 @@ class InputField extends Component { "error", "name", "tooltip", + "labelTooltipPosition", ]); const copyValue = (e) => { diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index d3f9289367de..102e1f610a16 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -593,13 +593,12 @@ const ManagePolicyPage = ({ ); await Promise.all([configResponse, ...policyResponses]); + renderFlash("success", "Successfully updated policy automations."); } catch { - (errorResponse: { data: IApiError }) => { - renderFlash( - "error", - `Could not update team settings. ${errorResponse.data.errors[0].reason}` - ); - }; + renderFlash( + "error", + "Could not update policy automations. Please try again." + ); } finally { toggleCalendarEventsModal(); setUpdatingPolicyEnabledCalendarEvents(false); diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index 57f8f0b9d823..1f75ecb4cf07 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -205,6 +205,7 @@ const CalendarEventsModal = ({ parseTarget error={formErrors.url} tooltip="Provide a URL to deliver a webhook request to." + labelTooltipPosition="top-start" helpText="A request will be sent to this URL during the calendar event. Use it to trigger auto-remidiation." /> {/* Date: Tue, 19 Mar 2024 13:54:27 -0700 Subject: [PATCH 15/22] example payload --- .../CalendarEventsModal.tsx | 30 +++++++++++++++++-- .../CalendarEventsModal/_styles.scss | 5 +++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index 1f75ecb4cf07..9a7dbc3689d8 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -13,6 +13,7 @@ import InputField from "components/forms/fields/InputField"; import Graphic from "components/Graphic"; import Modal from "components/Modal"; import Checkbox from "components/forms/fields/Checkbox"; +import { syntaxHighlight } from "utilities/helpers"; const baseClass = "calendar-events-modal"; @@ -70,6 +71,7 @@ const CalendarEventsModal = ({ const [showPreviewCalendarEvent, setShowPreviewCalendarEvent] = useState( false ); + const [showExamplePayload, setShowExamplePayload] = useState(false); const validateCalendarEventsFormData = ( curFormData: ICalendarEventsFormData @@ -116,6 +118,30 @@ const CalendarEventsModal = ({ // TODO }; + const renderExamplePayload = () => { + return ( + <> +
POST https://server.com/example
+
+      
+    );
+  };
+
   const renderPolicies = () => {
     return (
       
@@ -208,7 +234,7 @@ const CalendarEventsModal = ({ labelTooltipPosition="top-start" helpText="A request will be sent to this URL during the calendar event. Use it to trigger auto-remidiation." /> - {/* - {showExamplePayload && renderExamplePayload()} */} + {showExamplePayload && renderExamplePayload()} {renderPolicies()}
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss index 165ff47dae24..76eb657beeb8 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss @@ -1,3 +1,6 @@ .calendar-events-modal { - + pre { + box-sizing: border-box; + margin: 0; + } } From 026ddb5a39b86035edf87715d7be99e85bae0a95 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Tue, 19 Mar 2024 21:16:24 -0700 Subject: [PATCH 16/22] finalize placeholder, preview, and example --- .../graphics/CalendarEventPreview.tsx | 1184 +++++++++++++++++ .../CalendarIntegrationNotConfigured.tsx | 529 -------- frontend/components/graphics/index.ts | 4 +- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 5 +- .../CalendarEventsModal.tsx | 41 +- .../CalendarEventsModal/_styles.scss | 16 + 6 files changed, 1234 insertions(+), 545 deletions(-) create mode 100644 frontend/components/graphics/CalendarEventPreview.tsx delete mode 100644 frontend/components/graphics/CalendarIntegrationNotConfigured.tsx diff --git a/frontend/components/graphics/CalendarEventPreview.tsx b/frontend/components/graphics/CalendarEventPreview.tsx new file mode 100644 index 000000000000..4c29770a6861 --- /dev/null +++ b/frontend/components/graphics/CalendarEventPreview.tsx @@ -0,0 +1,1184 @@ +import React from "react"; + +const CalendarEventPreview = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 💻 🚫  + + + + + + + + + + + + + + + + + 💻 🚫  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default CalendarEventPreview; diff --git a/frontend/components/graphics/CalendarIntegrationNotConfigured.tsx b/frontend/components/graphics/CalendarIntegrationNotConfigured.tsx deleted file mode 100644 index 60d42ae8d167..000000000000 --- a/frontend/components/graphics/CalendarIntegrationNotConfigured.tsx +++ /dev/null @@ -1,529 +0,0 @@ -import React from "react"; - -const CalIntegrationNotConfigured = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -export default CalIntegrationNotConfigured; diff --git a/frontend/components/graphics/index.ts b/frontend/components/graphics/index.ts index 876cef9db2e9..fb3b0c5fd4b1 100644 --- a/frontend/components/graphics/index.ts +++ b/frontend/components/graphics/index.ts @@ -17,7 +17,7 @@ import EmptyTeams from "./EmptyTeams"; import EmptyPacks from "./EmptyPacks"; import EmptySchedule from "./EmptySchedule"; import CollectingResults from "./CollectingResults"; -import CalIntegrationNotConfigured from "./CalendarIntegrationNotConfigured"; +import CalendarEventPreview from "./CalendarEventPreview"; export const GRAPHIC_MAP = { // Empty state graphics @@ -42,7 +42,7 @@ export const GRAPHIC_MAP = { "file-pem": FilePem, // Other graphics "collecting-results": CollectingResults, - "calendar-integration-not-configured": CalIntegrationNotConfigured, + "calendar-event-preview": CalendarEventPreview, }; export type GraphicNames = keyof typeof GRAPHIC_MAP; diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 102e1f610a16..b4b1cb04b84c 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -945,9 +945,8 @@ const ManagePolicyPage = ({ updatePolicyEnabledCalendarEvents={ updatePolicyEnabledCalendarEvents } - // TODO - remove dummy prop values - // configured={isCalEventsConfigured} - configured + configured={isCalEventsConfigured} + // configured={false} enabled={ teamConfig?.integrations.google_calendar ?.enable_calendar_events ?? false diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index 9a7dbc3689d8..c4e4b5772388 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -115,7 +115,7 @@ const CalendarEventsModal = ({ ); const togglePreviewCalendarEvent = () => { - // TODO + setShowPreviewCalendarEvent(!showPreviewCalendarEvent); }; const renderExamplePayload = () => { @@ -176,21 +176,40 @@ const CalendarEventsModal = ({ ); }; const renderPreviewCalendarEventModal = () => { - // TODO - return <>; + return ( + + <> +

A similar event will appear in the end user's calendar:

+ +
+ +
+ +
+ ); }; const renderPlaceholderModal = () => { return ( - <> +
- + - To create calendar events for end users if their hosts fail policies, - you must first connect Fleet to your Google Workspace service account. -
- This can be configured in{" "} - Settings > Integrations > Calendars. +
+ To create calendar events for end users if their hosts fail policies, + you must first connect Fleet to your Google Workspace service account. +
+
+ This can be configured in{" "} + Settings > Integrations > Calendars. +
- +
); }; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss index 76eb657beeb8..dbec21af508a 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/_styles.scss @@ -1,6 +1,22 @@ .calendar-events-modal { + .placeholder { + display: flex; + flex-direction: column; + gap: 24px; + line-height: 150%; + .modal-cta-wrap { + margin-top: 0; + } + } + pre { box-sizing: border-box; margin: 0; } } + +.calendar-event-preview { + p { + margin: 24px 0; + } +} From 69f8c45514f4532618ef82b46f187ed4ce9a49e7 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Wed, 20 Mar 2024 11:00:40 -0700 Subject: [PATCH 17/22] disable form fields when feature disabled; update error handling --- .../CalendarEventsModal.tsx | 93 ++++++++++--------- .../CalendarEventsModal/_styles.scss | 13 +++ 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx index c4e4b5772388..eba5abb4e0d1 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tsx @@ -77,10 +77,12 @@ const CalendarEventsModal = ({ curFormData: ICalendarEventsFormData ) => { const errors: Record = {}; - const { url: curUrl } = curFormData; - if (!validURL({ url: curUrl })) { - const errorPrefix = curUrl ? `${curUrl} is not` : "Please enter"; - errors.url = `${errorPrefix} a valid resolution webhook URL`; + if (curFormData.enabled) { + const { url: curUrl } = curFormData; + if (!validURL({ url: curUrl })) { + const errorPrefix = curUrl ? `${curUrl} is not` : "Please enter"; + errors.url = `${errorPrefix} a valid resolution webhook URL`; + } } return errors; }; @@ -226,46 +228,51 @@ const CalendarEventsModal = ({ const renderConfiguredModal = () => (
- { - onInputChange({ name: "enabled", value: !formData.enabled }); - }} - inactiveText="Disabled" - activeText="Enabled" - /> - +
+
- Preview calendar event - - - { - setShowExamplePayload(!showExamplePayload); - }} - /> - {showExamplePayload && renderExamplePayload()} - {renderPolicies()} - + + { + setShowExamplePayload(!showExamplePayload); + }} + /> + {showExamplePayload && renderExamplePayload()} + {renderPolicies()} +
diff --git a/frontend/pages/policies/ManagePoliciesPage/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/_styles.scss index f094e245ab19..ed99ad013734 100644 --- a/frontend/pages/policies/ManagePoliciesPage/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/_styles.scss @@ -21,6 +21,12 @@ .Select > .Select-menu-outer { left: -186px; width: 360px; + .is-disabled * { + color: $ui-fleet-black-25; + .react-tooltip { + @include tooltip-text; + } + } } .Select-control { margin-top: 0; From 3f9faebe066ed99b0167460bdb7fbd7f4fda6bc6 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Wed, 20 Mar 2024 13:12:24 -0700 Subject: [PATCH 20/22] update names to "OtherWorkflowsModal" --- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 9bf1def35751..c90dc398b3d3 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -138,9 +138,7 @@ const ManagePolicyPage = ({ setUpdatingPolicyEnabledCalendarEvents, ] = useState(false); const [selectedPolicyIds, setSelectedPolicyIds] = useState([]); - const [showManageAutomationsModal, setShowManageAutomationsModal] = useState( - false - ); + const [showOtherWorkflowsModal, setShowOtherWorkflowsModal] = useState(false); const [showAddPolicyModal, setShowAddPolicyModal] = useState(false); const [showDeletePolicyModal, setShowDeletePolicyModal] = useState(false); const [showCalendarEventsModal, setShowCalendarEventsModal] = useState(false); @@ -493,8 +491,8 @@ const ManagePolicyPage = ({ ] // Other dependencies can cause infinite re-renders as URL is source of truth ); - const toggleManageAutomationsModal = () => - setShowManageAutomationsModal(!showManageAutomationsModal); + const toggleOtherWorkflowsModal = () => + setShowOtherWorkflowsModal(!showOtherWorkflowsModal); const toggleAddPolicyModal = () => setShowAddPolicyModal(!showAddPolicyModal); @@ -511,7 +509,7 @@ const ManagePolicyPage = ({ toggleCalendarEventsModal(); break; case "other_workflows": - toggleManageAutomationsModal(); + toggleOtherWorkflowsModal(); break; default: } @@ -547,7 +545,7 @@ const ManagePolicyPage = ({ "Could not update policy automations. Please try again." ); } finally { - toggleManageAutomationsModal(); + toggleOtherWorkflowsModal(); setIsUpdatingAutomations(false); refetchConfig(); isAnyTeamSelected && refetchTeamConfig(); @@ -939,13 +937,13 @@ const ManagePolicyPage = ({ )}
)} - {config && automationsConfig && showManageAutomationsModal && ( + {config && automationsConfig && showOtherWorkflowsModal && ( )} From 75c175819595c68102619bd3966d687dd55f5ed3 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Wed, 20 Mar 2024 13:26:34 -0700 Subject: [PATCH 21/22] cleanup --- frontend/interfaces/config.ts | 2 +- .../CalendarEventsModal/CalendarEventsModal.tests.tsx | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tests.tsx diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts index 9fb770223247..8eee167f1224 100644 --- a/frontend/interfaces/config.ts +++ b/frontend/interfaces/config.ts @@ -4,7 +4,7 @@ import { IWebhookFailingPolicies, IWebhookSoftwareVulnerabilities, } from "interfaces/webhook"; -import { IGlobalIntegrations, IIntegrations } from "./integration"; +import { IGlobalIntegrations } from "./integration"; export interface ILicense { tier: string; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tests.tsx b/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tests.tsx deleted file mode 100644 index b7622a67ad03..000000000000 --- a/frontend/pages/policies/ManagePoliciesPage/components/CalendarEventsModal/CalendarEventsModal.tests.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -import { render, screen } from "@testing-library/react"; - -import CalendarEventsModal from "./CalendarEventsModal"; - -describe("CalendarEventsModal component", () => { - it("", () => { - - }); -}); From c8781cc9303ed2bee807b38f5b740cf247793291 Mon Sep 17 00:00:00 2001 From: Jacob Shandling Date: Wed, 20 Mar 2024 13:36:37 -0700 Subject: [PATCH 22/22] more cleanup --- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 29 ++----------------- frontend/services/entities/teams.ts | 2 +- frontend/utilities/typeguards.ts | 12 -------- 3 files changed, 3 insertions(+), 40 deletions(-) delete mode 100644 frontend/utilities/typeguards.ts diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index c90dc398b3d3..80a832026b41 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -4,7 +4,7 @@ import { InjectedRouter } from "react-router/lib/Router"; import PATHS from "router/paths"; import { noop, isEqual, uniqueId } from "lodash"; -import { Tooltip as ReactTooltip5, PlacesType } from "react-tooltip-5"; +import { Tooltip as ReactTooltip5 } from "react-tooltip-5"; import { getNextLocationPath } from "utilities/helpers"; @@ -14,7 +14,7 @@ import { TableContext } from "context/table"; import { NotificationContext } from "context/notification"; import useTeamIdParam from "hooks/useTeamIdParam"; import { IConfig, IWebhookSettings } from "interfaces/config"; -import { IIntegrations, ITeamIntegrations } from "interfaces/integration"; +import { IIntegrations } from "interfaces/integration"; import { IPolicyStats, ILoadAllPoliciesResponse, @@ -22,8 +22,6 @@ import { IPoliciesCountResponse, } from "interfaces/policy"; import { ITeamConfig } from "interfaces/team"; -import { IDropdownOption } from "interfaces/dropdownOption"; -import { IApiError } from "interfaces/errors"; import configAPI from "services/entities/config"; import globalPoliciesAPI, { @@ -363,13 +361,6 @@ const ManagePolicyPage = ({ } ); - // wip.../TODO - // let calendarConfigured = false; - // const googleCalendarGlobalConfig = config?.integrations.google_calendar; - // if (typeof googleCalendarGlobalConfig - // isGlobalCalendarConfig(googleCalendarGlobalConfig) && - // googleCalendarGlobalConfig.length > 0; - const { data: teamConfig, isFetching: isFetchingTeamConfig, @@ -800,7 +791,6 @@ const ManagePolicyPage = ({ { label: calEventsLabel, value: "calendar_events", - // TODO - disable and different tooltips for each of below scenarios disabled: !isPremiumTier || isAllTeams, helpText: "Automatically reserve time to resolve failing policies.", }, @@ -846,7 +836,6 @@ const ManagePolicyPage = ({
{canManageAutomations && automationsConfig && (
- {/* TODO - add subtext to dropdown options. See Hosts dropdown filters on manage hosts page */}
- - // )} {canAddOrDeletePolicy && (
@@ -969,7 +945,6 @@ const ManagePolicyPage = ({ updatePolicyEnabledCalendarEvents } configured={isCalEventsConfigured} - // configured={false} enabled={ teamConfig?.integrations.google_calendar ?.enable_calendar_events ?? false diff --git a/frontend/services/entities/teams.ts b/frontend/services/entities/teams.ts index 43459d212d5f..149544582765 100644 --- a/frontend/services/entities/teams.ts +++ b/frontend/services/entities/teams.ts @@ -5,7 +5,7 @@ import { pick } from "lodash"; import { buildQueryStringFromParams } from "utilities/url"; import { IEnrollSecret } from "interfaces/enroll_secret"; -import { IIntegrations, ITeamIntegrations } from "interfaces/integration"; +import { ITeamIntegrations } from "interfaces/integration"; import { API_NO_TEAM_ID, INewTeamUsersBody, diff --git a/frontend/utilities/typeguards.ts b/frontend/utilities/typeguards.ts deleted file mode 100644 index 24aa3b3dfa46..000000000000 --- a/frontend/utilities/typeguards.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IGlobalCalendarIntegration } from "interfaces/integration"; - -const isBoolean = (value: any): value is boolean => typeof value === "boolean"; - -const isGlobalCalendarConfig = ( - value: any -): value is IGlobalCalendarIntegration[] => { - // if it's an array, it's the global config - return value.length !== undefined; -}; - -export default { isBoolean, isGlobalCalendarConfig };