From 1bc2f231d931f869507b5070db98fa51470b3b28 Mon Sep 17 00:00:00 2001 From: nulmete Date: Wed, 20 May 2026 16:00:53 -0300 Subject: [PATCH 1/3] Manage automations changes for Policies page --- .../ManagePoliciesPage/ManagePoliciesPage.tsx | 656 ++---------------- .../policies/ManagePoliciesPage/_styles.scss | 40 +- .../AutomationsModal/AutomationsModal.tsx | 24 + .../components/AutomationsModal/index.ts | 1 + .../ManageAutomationsModal.tsx | 29 + .../ManageAutomationsModal/index.ts | 1 + .../PoliciesTable/PoliciesTable.tsx | 7 + .../PoliciesTable/PoliciesTableConfig.tsx | 205 ++++-- .../components/PoliciesTable/_styles.scss | 58 +- .../ManagePoliciesPage/helpers.tests.tsx | 140 ++-- .../policies/ManagePoliciesPage/helpers.tsx | 109 ++- 11 files changed, 509 insertions(+), 761 deletions(-) create mode 100644 frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx create mode 100644 frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/index.ts create mode 100644 frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx create mode 100644 frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/index.ts diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index ed8cb720595..4b12a485818 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -6,37 +6,26 @@ import React, { useRef, useState, } from "react"; -import { useQuery, useQueryClient } from "react-query"; +import { useQuery } from "react-query"; import { InjectedRouter } from "react-router/lib/Router"; import PATHS from "router/paths"; import { isEqual } from "lodash"; -import { getNextLocationPath, wait } from "utilities/helpers"; +import { getNextLocationPath } from "utilities/helpers"; import { AppContext } from "context/app"; import { PolicyContext } from "context/policy"; import { TableContext } from "context/table"; import { NotificationContext } from "context/notification"; import useTeamIdParam from "hooks/useTeamIdParam"; -import { - IConfig, - IWebhookSettings, - isConditionalAccessConfigured, -} from "interfaces/config"; -import { IZendeskJiraIntegrations } from "interfaces/integration"; -import { INotification } from "interfaces/notification"; +import { IConfig } from "interfaces/config"; import { IPolicyStats, ILoadAllPoliciesResponse, ILoadTeamPoliciesResponse, IPoliciesCountResponse, } from "interfaces/policy"; -import { - API_ALL_TEAMS_ID, - API_NO_TEAM_ID, - APP_CONTEXT_ALL_TEAMS_ID, -} from "interfaces/team"; -import { TooltipContent } from "interfaces/dropdownOption"; +import { API_ALL_TEAMS_ID, APP_CONTEXT_ALL_TEAMS_ID } from "interfaces/team"; import configAPI from "services/entities/config"; import globalPoliciesAPI, { @@ -54,6 +43,7 @@ import teamsAPI, { ILoadTeamResponse } from "services/entities/teams"; import { ITableQueryData } from "components/TableContainer/TableContainer"; import TableCount from "components/TableContainer/TableCount"; import Button from "components/buttons/Button"; +import Icon from "components/Icon"; import { SingleValue } from "react-select-5"; import DropdownWrapper from "components/forms/fields/DropdownWrapper"; @@ -67,21 +57,11 @@ import LastUpdatedText from "components/LastUpdatedText"; import TooltipWrapper from "components/TooltipWrapper"; import PoliciesTable from "./components/PoliciesTable"; -import OtherWorkflowsModal from "./components/OtherWorkflowsModal"; import DeletePoliciesModal from "./components/DeletePoliciesModal"; -import CalendarEventsModal from "./components/CalendarEventsModal"; -import { ICalendarEventsFormData } from "./components/CalendarEventsModal/CalendarEventsModal"; -import InstallSoftwareModal from "./components/InstallSoftwareModal"; -import { IInstallSoftwareFormData } from "./components/InstallSoftwareModal/InstallSoftwareModal"; -import PolicyRunScriptModal from "./components/PolicyRunScriptModal"; -import { IPolicyRunScriptFormData } from "./components/PolicyRunScriptModal/PolicyRunScriptModal"; -import { - getInstallSoftwareErrorMessage, - getRunScriptErrorMessage, -} from "./helpers"; +import { OtherAutomationType } from "./helpers"; import { DEFAULT_POLICY } from "../constants"; -import ConditionalAccessModal from "./components/ConditionalAccessModal"; -import { IConditionalAccessFormData } from "./components/ConditionalAccessModal/ConditionalAccessModal"; +import AutomationsModal from "./components/AutomationsModal"; +import ManageAutomationsModal from "./components/ManageAutomationsModal"; interface IManagePoliciesPageProps { router: InjectedRouter; @@ -105,13 +85,6 @@ interface IManagePoliciesPageProps { export const DEFAULT_SORT_DIRECTION = "asc"; export const DEFAULT_PAGE_SIZE = 20; export const DEFAULT_SORT_COLUMN = "name"; -const [ - DEFAULT_AUTOMATION_UPDATE_SUCCESS_MSG, - DEFAULT_AUTOMATION_UPDATE_ERR_MSG, -] = [ - "Successfully updated policy automations.", - "Could not update policy automations.", -]; const AUTOMATION_TYPES: AutomationType[] = [ "software", @@ -142,14 +115,8 @@ const ManagePolicyPage = ({ } = useContext(AppContext); const isPrimoMode = globalConfigFromContext?.partnerships?.enable_primo || false; - const isManagedCloud = - globalConfigFromContext?.license?.managed_cloud || false; - const conditionalAccessProviderText = isManagedCloud - ? "Okta or Microsoft Entra" - : "Okta"; - - const queryClient = useQueryClient(); - const { renderFlash, renderMultiFlash } = useContext(NotificationContext); + + const { renderFlash } = useContext(NotificationContext); const { setResetSelectedRows } = useContext(TableContext); const { setLastEditedQueryName, @@ -164,7 +131,6 @@ const ManagePolicyPage = ({ const { currentTeamId, - currentTeamName, currentTeamSummary, isAllTeamsSelected, isTeamAdmin, @@ -192,17 +158,11 @@ const ManagePolicyPage = ({ const [selectedPolicyIds, setSelectedPolicyIds] = useState([]); const [showDeletePoliciesModal, setShowDeletePoliciesModal] = useState(false); - const [showInstallSoftwareModal, setShowInstallSoftwareModal] = useState( - false - ); - const [showPolicyRunScriptModal, setShowPolicyRunScriptModal] = useState( - false - ); - const [showCalendarEventsModal, setShowCalendarEventsModal] = useState(false); - const [showOtherWorkflowsModal, setShowOtherWorkflowsModal] = useState(false); - const [showConditionalAccessModal, setShowConditionalAccessModal] = useState( - false - ); + const [showAutomationsModal, setShowAutomationsModal] = useState(false); + const [ + selectedPolicyForAutomations, + setSelectedPolicyForAutomations, + ] = useState(null); // Functions to avoid race conditions const initialSearchQuery = (() => queryParams.query ?? "")(); const initialSortHeader = (() => @@ -435,15 +395,6 @@ const ManagePolicyPage = ({ const automationsConfig = isAllTeamsSelected ? globalConfig : teamConfig; - const updateGlobalConfig = (updatedConfig: IConfig) => { - queryClient.setQueryData(["config"], updatedConfig); - setConfig(updatedConfig); - }; - - const updateTeamConfig = (updatedTeamResponse: ILoadTeamResponse) => { - queryClient.setQueryData(["teams", teamIdForApi], updatedTeamResponse); - }; - const refetchPolicies = (teamId?: number) => { if (teamId !== undefined) { refetchTeamPolicies(); @@ -523,349 +474,17 @@ const ManagePolicyPage = ({ ] // Other dependencies can cause infinite re-renders as URL is source of truth ); - const toggleOtherWorkflowsModal = () => - setShowOtherWorkflowsModal(!showOtherWorkflowsModal); - const toggleDeletePoliciesModal = () => setShowDeletePoliciesModal(!showDeletePoliciesModal); - const toggleInstallSoftwareModal = () => { - setShowInstallSoftwareModal(!showInstallSoftwareModal); - }; - - const togglePolicyRunScriptModal = () => { - setShowPolicyRunScriptModal(!showPolicyRunScriptModal); - }; - - const toggleCalendarEventsModal = () => { - setShowCalendarEventsModal(!showCalendarEventsModal); - }; - - const toggleConditionalAccessModal = () => { - setShowConditionalAccessModal(!showConditionalAccessModal); - }; - - const onSelectAutomationOption = (option: SingleValue) => { - switch (option?.value) { - case "calendar_events": - toggleCalendarEventsModal(); - break; - case "install_software": - toggleInstallSoftwareModal(); - break; - case "run_script": - togglePolicyRunScriptModal(); - break; - case "conditional_access": - toggleConditionalAccessModal(); - break; - case "other_workflows": - toggleOtherWorkflowsModal(); - break; - default: - } - }; - - const onUpdateOtherWorkflows = async (requestBody: { - webhook_settings: Pick; - integrations: IZendeskJiraIntegrations; - }) => { - setIsUpdatingPolicies(true); - try { - if (isAllTeamsSelected) { - const updatedConfig = await configAPI.update(requestBody); - updateGlobalConfig(updatedConfig); - } else { - // For any team including "No team" (team ID 0), use the teams API - const updatedTeamResponse = await teamsAPI.update( - requestBody, - teamIdForApi - ); - updateTeamConfig(updatedTeamResponse); - } - renderFlash("success", DEFAULT_AUTOMATION_UPDATE_SUCCESS_MSG); - } catch { - renderFlash("error", DEFAULT_AUTOMATION_UPDATE_ERR_MSG); - } finally { - toggleOtherWorkflowsModal(); - setIsUpdatingPolicies(false); - } - }; - - const onUpdatePolicySoftwareInstall = async ( - formData: IInstallSoftwareFormData - ) => { - try { - setIsUpdatingPolicies(true); - - // Truly dirty items previously detected in InstallSoftwareModal - if (!formData.length) { - renderFlash("success", "No changes detected."); - return; - } - - // Execute policy updates sequentially to reduce DB load - const results: PromiseSettledResult[] = []; - - // Use reduce to execute promises sequentially - await formData.reduce(async (previousPromise, changedPolicy) => { - await previousPromise; - try { - const result = await teamPoliciesAPI.update(changedPolicy.id, { - software_title_id: changedPolicy.swIdToInstall || null, - team_id: teamIdForApi, - }); - results.push({ status: "fulfilled", value: result }); - } catch (error) { - results.push({ status: "rejected", reason: error }); - } - }, Promise.resolve()); - - const successfulUpdates = results.filter( - (result) => result.status === "fulfilled" - ); - const failedUpdates = results.filter( - (result) => result.status === "rejected" - ); - - // Renders API error reason for each error in a single message - if (failedUpdates.length > 0) { - const errorNotifications: INotification[] = failedUpdates.map( - (result, index) => { - const message = getInstallSoftwareErrorMessage( - result as PromiseRejectedResult, - formData, - currentTeamName - ); - - return { - id: `error-${index}`, - alertType: "error", - isVisible: true, - message, - persistOnPageChange: false, - }; - } - ); - - renderMultiFlash({ - notifications: errorNotifications, - }); - } else if (successfulUpdates.length > 0) { - // Only render success message if there are no failures - renderFlash("success", DEFAULT_AUTOMATION_UPDATE_SUCCESS_MSG); - } - - await wait(100); // Wait 100ms to avoid race conditions with refetch - refetchTeamPolicies(); - } catch { - renderFlash("error", DEFAULT_AUTOMATION_UPDATE_ERR_MSG); - } finally { - toggleInstallSoftwareModal(); - setIsUpdatingPolicies(false); - } - }; + const toggleAutomationsModal = () => + setShowAutomationsModal(!showAutomationsModal); - const onUpdatePolicyRunScript = async ( - formData: IPolicyRunScriptFormData - ) => { - try { - setIsUpdatingPolicies(true); - - // Truly dirty items previously detected in PolicyRunScriptModal - if (!formData.length) { - renderFlash("success", "No changes detected."); - return; - } + const onOpenManageAutomationsModal = (policy: IPolicyStats) => + setSelectedPolicyForAutomations(policy); - const promises = formData.map((changedPolicy) => - teamPoliciesAPI.update(changedPolicy.id, { - // "script_id": null will unset running a script for the policy - // "script_id": X will sets script X to run when the policy fails - script_id: changedPolicy.scriptIdToRun || null, - team_id: teamIdForApi, - }) - ); - - // Allows for all API calls to settle even if there is an error on one - const results = await Promise.allSettled(promises); - - const successfulUpdates = results.filter( - (result) => result.status === "fulfilled" - ); - const failedUpdates = results.filter( - (result) => result.status === "rejected" - ); - - // Renders API error reason for each error in a single message - if (failedUpdates.length > 0) { - const errorNotifications: INotification[] = failedUpdates.map( - (result, index) => { - const message = getRunScriptErrorMessage( - result as PromiseRejectedResult, - formData, - currentTeamName - ); - - return { - id: `error-${index}`, - alertType: "error", - isVisible: true, - message, - persistOnPageChange: false, - }; - } - ); - - renderMultiFlash({ - notifications: errorNotifications, - }); - } else if (successfulUpdates.length > 0) { - // Only render success message if there are no failures - renderFlash("success", DEFAULT_AUTOMATION_UPDATE_SUCCESS_MSG); - } - - await wait(100); // Wait 100ms to avoid race conditions with refetch - refetchTeamPolicies(); - } catch { - renderFlash("error", DEFAULT_AUTOMATION_UPDATE_ERR_MSG); - } finally { - togglePolicyRunScriptModal(); - setIsUpdatingPolicies(false); - } - }; - - const onUpdateCalendarEvents = async (formData: ICalendarEventsFormData) => { - setIsUpdatingPolicies(true); - - try { - // update team config if either field has been changed - let teamConfigPromise: Promise | undefined; - const policyPromises = []; - if ( - formData.enabled !== - teamConfig?.integrations.google_calendar?.enable_calendar_events || - formData.url !== teamConfig?.integrations.google_calendar?.webhook_url - ) { - teamConfigPromise = teamsAPI.update( - { - integrations: { - google_calendar: { - enable_calendar_events: formData.enabled, - webhook_url: formData.url, - }, - // These fields will never actually be changed here. See comment above - // IGlobalIntegrations definition. - zendesk: teamConfig?.integrations.zendesk || [], - jira: teamConfig?.integrations.jira || [], - }, - }, - teamIdForApi - ); - } - - // update changed policies calendar events enabled - policyPromises.push( - ...formData.changedPolicies.map((changedPolicy) => { - return teamPoliciesAPI.update(changedPolicy.id, { - calendar_events_enabled: changedPolicy.calendar_events_enabled, - team_id: teamIdForApi, - }); - }) - ); - - const [teamConfigResult] = await Promise.all([ - teamConfigPromise, - ...policyPromises, - ]); - - if (teamConfigResult) { - updateTeamConfig(teamConfigResult); - } - - await wait(100); // Wait 100ms to avoid race conditions with refetch - await refetchTeamPolicies(); - - renderFlash("success", DEFAULT_AUTOMATION_UPDATE_SUCCESS_MSG); - } catch { - renderFlash("error", DEFAULT_AUTOMATION_UPDATE_ERR_MSG); - } finally { - toggleCalendarEventsModal(); - setIsUpdatingPolicies(false); - } - }; - - const onUpdateConditionalAccess = async ({ - enabled: enableConditionalAccess, - changedPolicies, - }: IConditionalAccessFormData) => { - setIsUpdatingPolicies(true); - - try { - let globalConfigPromise: Promise | undefined; - let teamConfigPromise: Promise | undefined; - - // If enabling/disabling the feature, update appropriate config - if (teamIdForApi === API_NO_TEAM_ID) { - if ( - enableConditionalAccess !== - globalConfig?.integrations.conditional_access_enabled - ) { - const payload = { - integrations: { - conditional_access_enabled: enableConditionalAccess, - }, - }; - globalConfigPromise = configAPI.update(payload); - } - } else if ( - enableConditionalAccess !== - teamConfig?.integrations.conditional_access_enabled - ) { - // patch team config (all teams but No team) - const payload = { - integrations: { - // These fields will never actually be changed here. See comment above - // IGlobalIntegrations definition. - zendesk: teamConfig?.integrations.zendesk || [], - jira: teamConfig?.integrations.jira || [], - conditional_access_enabled: enableConditionalAccess, - }, - }; - teamConfigPromise = teamsAPI.update(payload, teamIdForApi); - } - - // handle any changed policies for no team or a team - const policyPromises = changedPolicies.map((changedPolicy) => { - return teamPoliciesAPI.update(changedPolicy.id, { - conditional_access_enabled: changedPolicy.conditional_access_enabled, - team_id: teamIdForApi, - }); - }); - - const [globalConfigResult, teamConfigResult] = await Promise.all([ - globalConfigPromise, - teamConfigPromise, - ...policyPromises, - ]); - - if (globalConfigResult) { - updateGlobalConfig(globalConfigResult); - } - if (teamConfigResult) { - updateTeamConfig(teamConfigResult); - } - renderFlash( - "success", - "Successfully updated conditional access automations." - ); - } catch { - renderFlash("error", "Could not update conditional access automations."); - } finally { - toggleConditionalAccessModal(); - setIsUpdatingPolicies(false); - } - }; + const onCloseManageAutomationsModal = () => + setSelectedPolicyForAutomations(null); const onAddPolicyClick = () => { setLastEditedQueryName(""); @@ -989,10 +608,9 @@ const ManagePolicyPage = ({ const hasPoliciesToDelete = hasPoliciesToAutomate || (isPrimoMode && (teamPolicies?.length ?? 0) > 0); // in Primo mode, allow deleting inherited policies, which will be included in teamPolicies, from this view - const showAutomationsDropdown = canManageAutomations; - // NOTE: backend uses webhook_settings to store automated policy ids for both webhooks and integrations let currentAutomatedPolicies: number[] = []; + let otherAutomationType: OtherAutomationType | undefined; if (automationsConfig) { const { webhook_settings: { failing_policies_webhook: webhook }, @@ -1010,6 +628,12 @@ const ManagePolicyPage = ({ if (isIntegrationEnabled || webhook?.enable_failing_policies_webhook) { currentAutomatedPolicies = webhook?.policy_ids || []; } + + if (isIntegrationEnabled) { + otherAutomationType = "ticket"; + } else if (webhook?.enable_failing_policies_webhook) { + otherAutomationType = "webhook"; + } } const renderPoliciesCountAndLastUpdated = ( @@ -1053,7 +677,7 @@ const ManagePolicyPage = ({ const automationFilterOptions: CustomOptionType[] = [ { - label: "All policies", + label: "All automations", value: "all", helpText: "All policies added to Fleet.", }, @@ -1078,9 +702,9 @@ const ManagePolicyPage = ({ helpText: "Policies with conditional access automation enabled.", }, { - label: "Other", + label: "Webhooks or tickets", value: "other", - helpText: "Policies with other automation enabled.", + helpText: "Policies with webhook or ticket automation enabled.", }, ]; @@ -1165,6 +789,10 @@ const ManagePolicyPage = ({ onQueryChange={onQueryChange} customControl={renderAutomationFilter} isFiltered={!!automationFilter} + otherAutomationType={otherAutomationType} + onOpenManageAutomationsModal={ + canManageAutomations ? onOpenManageAutomationsModal : undefined + } /> ); } @@ -1205,146 +833,28 @@ const ManagePolicyPage = ({ onQueryChange={onQueryChange} customControl={renderAutomationFilter} isFiltered={!!automationFilter} + otherAutomationType={otherAutomationType} + onOpenManageAutomationsModal={ + canManageAutomations ? onOpenManageAutomationsModal : undefined + } /> ); }; - const gitOpsModeEnabled = globalConfig?.gitops.gitops_mode_enabled; - - const isCalEventsConfigured = - (globalConfig?.integrations.google_calendar && - globalConfig?.integrations.google_calendar.length > 0) ?? - false; - - const isCalEventsEnabled = - teamConfig?.integrations.google_calendar?.enable_calendar_events ?? false; - - const isConditionalAccessEnabled = - (teamIdForApi === API_NO_TEAM_ID - ? globalConfig?.integrations.conditional_access_enabled - : teamConfig?.integrations.conditional_access_enabled) ?? false; - - const getAutomationsDropdownOptions = () => { - let disabledInstallTooltipContent: TooltipContent; - let disabledCalendarTooltipContent: TooltipContent; - let disabledRunScriptTooltipContent: TooltipContent; - let disabledConditionalAccessTooltipContent: TooltipContent; - if (!isPremiumTier) { - disabledInstallTooltipContent = "Available in Fleet Premium"; - disabledCalendarTooltipContent = "Available in Fleet Premium"; - disabledRunScriptTooltipContent = "Available in Fleet Premium"; - disabledConditionalAccessTooltipContent = "Available in Fleet Premium"; - } else if (isAllTeamsSelected) { - disabledInstallTooltipContent = ( - <> - Select a fleet to manage -
- install software automation. - - ); - disabledCalendarTooltipContent = ( + let automationsButton = null; + if (canManageAutomations) { + automationsButton = ( + ); if (!hasPoliciesToAutomate) { const tipContent = @@ -1362,14 +872,14 @@ const ManagePolicyPage = ({ ); - automationsDropdown = ( + automationsButton = ( - {automationsDropdown} + {automationsButton} ); } @@ -1410,7 +920,7 @@ const ManagePolicyPage = ({ {showCtaButtons && (
- {automationsDropdown} + {automationsButton} {canAddOrDeletePolicies && (
{renderMainTable()} - {automationsConfig && showOtherWorkflowsModal && ( - - )} {showDeletePoliciesModal && ( )} - {showInstallSoftwareModal && ( - - )} - {showPolicyRunScriptModal && ( - - )} - {showCalendarEventsModal && ( - + {showAutomationsModal && ( + )} - {showConditionalAccessModal && ( - )} diff --git a/frontend/pages/policies/ManagePoliciesPage/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/_styles.scss index 3c34b40e154..652dbd9936e 100644 --- a/frontend/pages/policies/ManagePoliciesPage/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/_styles.scss @@ -23,42 +23,10 @@ min-width: 277px; } - &__manage-automations-wrapper { - @include button-dropdown; - .Select-multi-value-wrapper { - width: 146px; - } - - // When tabbing through the app - .manage-policies-page__manage-automations-dropdown { - &.is-focused { - outline: 2px solid $ui-vibrant-blue-25; - } - } - - .Select > .Select-menu-outer { - left: -186px; - width: 360px; - } - .Select-input { - padding: 8px; - } - - .Select-control { - margin-top: 0; - gap: 6px; - .Select-placeholder { - color: $core-vibrant-blue; - font-weight: $bold; - } - .dropdown__custom-arrow .dropdown__icon { - svg { - path { - stroke: $core-vibrant-blue-over; - } - } - } - } + &__automations-button { + display: flex; + align-items: center; + gap: $pad-small; } &__header { diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx new file mode 100644 index 00000000000..c8e30c13c51 --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import Modal from "components/Modal"; +import Button from "components/buttons/Button"; + +const baseClass = "automations-modal"; + +interface IAutomationsModalProps { + onExit: () => void; +} + +// Placeholder – full implementation in follow-up PR +const AutomationsModal = ({ onExit }: IAutomationsModalProps): JSX.Element => { + return ( + +
+ +
+
+ ); +}; + +export default AutomationsModal; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/index.ts b/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/index.ts new file mode 100644 index 00000000000..76ba7160304 --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/index.ts @@ -0,0 +1 @@ +export { default } from "./AutomationsModal"; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx new file mode 100644 index 00000000000..ad8359b9f9a --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import Modal from "components/Modal"; +import Button from "components/buttons/Button"; +import { IPolicyStats } from "interfaces/policy"; + +const baseClass = "manage-automations-modal"; + +interface IManageAutomationsModalProps { + policy: IPolicyStats; + onExit: () => void; +} + +// Placeholder – full implementation in follow-up PR +const ManageAutomationsModal = ({ + policy, + onExit, +}: IManageAutomationsModalProps): JSX.Element => { + return ( + +
+ +
+
+ ); +}; + +export default ManageAutomationsModal; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/index.ts b/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/index.ts new file mode 100644 index 00000000000..c9128e3c2d5 --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/index.ts @@ -0,0 +1 @@ +export { default } from "./ManageAutomationsModal"; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx index a9bb969f363..066f8148d53 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx @@ -14,6 +14,7 @@ import { DEFAULT_SORT_DIRECTION, DEFAULT_PAGE_SIZE, } from "../../ManagePoliciesPage"; +import { OtherAutomationType } from "../../helpers"; // isLastPage is removable if/when API is updated to include meta.has_next_results const isLastPage = (count: number, pageSize: number, page: number) => { @@ -41,6 +42,8 @@ interface IPoliciesTableProps { count: number; customControl?: () => JSX.Element | null; isFiltered?: boolean; + otherAutomationType?: OtherAutomationType; + onOpenManageAutomationsModal?: (policy: IPolicyStats) => void; } const PoliciesTable = ({ @@ -62,6 +65,8 @@ const PoliciesTable = ({ count, customControl, isFiltered, + otherAutomationType, + onOpenManageAutomationsModal, }: IPoliciesTableProps): JSX.Element => { const { config } = useContext(AppContext); @@ -121,6 +126,8 @@ const PoliciesTable = ({ { selectedTeamId: currentTeam?.id, hasPermissionAndPoliciesToDelete, + otherAutomationType, + onOpenManageAutomationsModal, }, isPremiumTier, config?.partnerships?.enable_primo diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx index 031c8d928c0..dbe73968dfc 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx @@ -3,13 +3,18 @@ // definitions for the selection row for some reason when we dont really need it. import React from "react"; import { millisecondsToHours, millisecondsToMinutes } from "date-fns"; -import { Tooltip as ReactTooltip5 } from "react-tooltip-5"; // @ts-ignore import Checkbox from "components/forms/fields/Checkbox"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell"; import LinkCell from "components/TableContainer/DataTable/LinkCell/LinkCell"; +import TooltipTruncatedTextCell from "components/TableContainer/DataTable/TooltipTruncatedTextCell"; +import TooltipWrapper from "components/TooltipWrapper"; +import Icon from "components/Icon"; +import Graphic from "components/Graphic"; +import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon"; import { IPolicyStats } from "interfaces/policy"; import PATHS from "router/paths"; +import ENDPOINTS from "utilities/endpoints"; import { getPathWithQueryParams } from "utilities/url"; import sortUtils from "utilities/sort"; @@ -21,7 +26,11 @@ import { PATCH_TOOLTIP_CONTENT } from "components/SoftwareInstallPolicyBadges/So import { getConditionalSelectHeaderCheckboxProps } from "components/TableContainer/utilities/config_utils"; import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; -import { getAutomationTypesString } from "../../helpers"; +import { + getAutomationsForPolicy, + IAutomationData, + OtherAutomationType, +} from "../../helpers"; import PassingColumnHeader from "../PassingColumnHeader"; interface IGetToggleAllRowsSelectedProps { @@ -62,6 +71,126 @@ interface IDataColumn { sortType?: string; } +const AUTOMATION_ICON_RENDERERS: Record< + IAutomationData["type"], + (args: { name: string; iconUrl?: string }) => JSX.Element +> = { + software: ({ name, iconUrl }) => ( + + + + ), + script: ({ name }) => ( + + ), + calendar: () => , + conditional_access: () => , + other: () => , +}; + +interface IAutomationsCellProps { + policy: IPolicyStats; + selectedTeamId?: number | null; + otherAutomationType?: OtherAutomationType; + onOpenManageAutomationsModal?: (policy: IPolicyStats) => void; +} + +const AutomationsCell = ({ + policy, + selectedTeamId, + otherAutomationType, + onOpenManageAutomationsModal, +}: IAutomationsCellProps): JSX.Element => { + const automations = getAutomationsForPolicy(policy, otherAutomationType); + + if (automations.length === 0) { + return ( + + {DEFAULT_EMPTY_CELL_VALUE} + + ); + } + + const handleClick = () => onOpenManageAutomationsModal?.(policy); + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleClick(); + } + }; + + const renderAutomationIcon = ({ + type, + name, + softwareTitleId, + }: IAutomationData) => { + const iconUrl = + type === "software" && softwareTitleId != null + ? `/api${getPathWithQueryParams( + ENDPOINTS.SOFTWARE_ICON(softwareTitleId), + { + fleet_id: + selectedTeamId != null && selectedTeamId !== -1 + ? selectedTeamId + : undefined, + } + )}` + : undefined; + return AUTOMATION_ICON_RENDERERS[type]({ name, iconUrl }); + }; + + if (automations.length === 1) { + const automation = automations[0]; + return ( +
+ + +
+ ); + } + + return ( +
+ name).join(", ")} + > + {automations.length} automations + + +
+ ); +}; + const getPolicyRefreshTime = (ms: number): string => { const seconds = ms / 1000; if (seconds < 60) { @@ -92,11 +221,18 @@ const generateTableHeaders = ( selectedTeamId?: number | null; hasPermissionAndPoliciesToDelete?: boolean; tableType?: string; + otherAutomationType?: OtherAutomationType; + onOpenManageAutomationsModal?: (policy: IPolicyStats) => void; }, isPremiumTier?: boolean, isPrimoMode?: boolean ): IDataColumn[] => { - const { selectedTeamId, hasPermissionAndPoliciesToDelete } = options; + const { + selectedTeamId, + hasPermissionAndPoliciesToDelete, + otherAutomationType, + onOpenManageAutomationsModal, + } = options; const viewingTeamPolicies = selectedTeamId !== -1; const tableHeaders: IDataColumn[] = [ @@ -148,21 +284,14 @@ const generateTableHeaders = ( Header: "Automations", accessor: "automations", disableSortBy: true, - Cell: (cellProps: ICellProps): JSX.Element => { - const policy = cellProps.row.original; - const automationsText = getAutomationTypesString(policy); - const isNone = automationsText === DEFAULT_EMPTY_CELL_VALUE; - return ( - - {automationsText} - - ); - }, + Cell: (cellProps: ICellProps): JSX.Element => ( + + ), }, { title: "Pass", @@ -192,18 +321,16 @@ const generateTableHeaders = ( } return (
- --- - - {getTooltip(next_update_ms)} - + --- +
); }, @@ -236,18 +363,16 @@ const generateTableHeaders = ( } return (
- --- - - {getTooltip(next_update_ms)} - + --- +
); }, diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss index 613faf18e7e..c878363f350 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss @@ -1,6 +1,10 @@ .policies-table { border-collapse: collapse; + .data-table__wrapper { + overflow-x: auto; + } + thead { th { &.passing_host_count__header, @@ -20,26 +24,60 @@ } } + .data-table tbody td.automations__cell { + max-width: 300px; + } + &__action-button-container { display: flex; justify-content: center; } } -.automations-cell { - display: block; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +.automations__cell-content { + display: flex; + align-items: center; + cursor: pointer; &--none { color: $ui-fleet-black-50; + cursor: default; } } -.no-team-policy { - border: 1px solid #e2e4ea; - box-sizing: border-box; - border-radius: 8px; +.automations__name { + flex: 1; + min-width: 0; +} + +// Fixed-size wrapper so layout doesn't shift while a software icon loads +.automations__software-icon { + display: flex; + align-items: center; + width: 24px; + height: 24px; + background-color: $ui-fleet-black-33; + border-radius: 4px; + flex-shrink: 0; + overflow: hidden; +} + +.automations__count { + flex: 1; + min-width: 0; + font-style: italic; + color: $ui-fleet-black-75; +} + +.automations__edit-button { + visibility: hidden; + flex-shrink: 0; + margin-left: $pad-small; + display: flex; + align-items: center; + color: $core-vibrant-blue; +} + +.automations__cell-content:hover .automations__edit-button { + visibility: visible; } diff --git a/frontend/pages/policies/ManagePoliciesPage/helpers.tests.tsx b/frontend/pages/policies/ManagePoliciesPage/helpers.tests.tsx index 35318022bcc..0ac63a64bb1 100644 --- a/frontend/pages/policies/ManagePoliciesPage/helpers.tests.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/helpers.tests.tsx @@ -1,9 +1,8 @@ import React from "react"; -import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; import { getInstallSoftwareErrorMessage, getRunScriptErrorMessage, - getAutomationTypesString, + getAutomationsForPolicy, } from "./helpers"; import { IInstallSoftwareFormData } from "./components/InstallSoftwareModal/InstallSoftwareModal"; import { IPolicyRunScriptFormData } from "./components/PolicyRunScriptModal/PolicyRunScriptModal"; @@ -275,63 +274,114 @@ describe("getRunScriptErrorMessage", () => { }); }); -describe("getAutomationTypesString", () => { +describe("getAutomationsForPolicy", () => { const basePolicy = { calendar_events_enabled: false, conditional_access_enabled: false, + webhook: "Off", }; - it("returns DEFAULT_EMPTY_CELL_VALUE when no automations are enabled", () => { - expect(getAutomationTypesString(basePolicy)).toBe(DEFAULT_EMPTY_CELL_VALUE); + it("returns empty array when no automations are enabled", () => { + expect(getAutomationsForPolicy(basePolicy)).toEqual([]); }); - it("returns 'Software' when only install_software is present", () => { - expect( - getAutomationTypesString({ - ...basePolicy, - install_software: { software_title_id: 1 }, - }) - ).toBe("Software"); + it("returns software automation with display_name preferred over name", () => { + const result = getAutomationsForPolicy({ + ...basePolicy, + install_software: { + name: "Chrome.app", + display_name: "Google Chrome", + software_title_id: 42, + }, + }); + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + type: "software", + name: "Google Chrome", + softwareTitleId: 42, + }); }); - it("returns 'Script' when only run_script is present", () => { - expect( - getAutomationTypesString({ - ...basePolicy, - run_script: { id: 1 }, - }) - ).toBe("Script"); + it("falls back to name when display_name is absent", () => { + const result = getAutomationsForPolicy({ + ...basePolicy, + install_software: { name: "Chrome.app", software_title_id: 42 }, + }); + expect(result[0].name).toBe("Chrome.app"); }); - it("returns types in correct order with sentence case: Software, script, calendar, conditional access, other", () => { - expect( - getAutomationTypesString({ - install_software: { software_title_id: 1 }, - run_script: { id: 1 }, - calendar_events_enabled: true, - conditional_access_enabled: true, - webhook: "On", - }) - ).toBe("Software, script, calendar, conditional access, other"); + it("returns script automation with file name", () => { + const result = getAutomationsForPolicy({ + ...basePolicy, + run_script: { id: 7, name: "fix-disk.sh" }, + }); + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + type: "script", + name: "fix-disk.sh", + }); }); - it("returns 'Software, calendar' for software + calendar", () => { - expect( - getAutomationTypesString({ - ...basePolicy, - install_software: { software_title_id: 1 }, - calendar_events_enabled: true, - }) - ).toBe("Software, calendar"); + it("returns calendar automation", () => { + const result = getAutomationsForPolicy({ + ...basePolicy, + calendar_events_enabled: true, + }); + expect(result[0]).toMatchObject({ + type: "calendar", + name: "Maintenance window", + }); }); - it("does not include Other when webhook is Off", () => { - expect( - getAutomationTypesString({ - ...basePolicy, - install_software: { software_title_id: 1 }, - webhook: "Off", - }) - ).toBe("Software"); + it("returns conditional access automation", () => { + const result = getAutomationsForPolicy({ + ...basePolicy, + conditional_access_enabled: true, + }); + expect(result[0]).toMatchObject({ + type: "conditional_access", + name: "Conditional access", + }); + }); + + it("labels other automation as Webhook by default", () => { + const result = getAutomationsForPolicy({ ...basePolicy, webhook: "On" }); + expect(result[0]).toMatchObject({ type: "other", name: "Webhook" }); + }); + + it("labels other automation as Ticket when otherAutomationType is ticket", () => { + const result = getAutomationsForPolicy( + { ...basePolicy, webhook: "On" }, + "ticket" + ); + expect(result[0]).toMatchObject({ type: "other", name: "Ticket" }); + }); + + it("does not include other automation when webhook is Off", () => { + const result = getAutomationsForPolicy({ + ...basePolicy, + webhook: "Off", + }); + expect(result).toHaveLength(0); + }); + + it("returns all automations in order", () => { + const result = getAutomationsForPolicy( + { + install_software: { name: "Chrome.app", software_title_id: 1 }, + run_script: { id: 2, name: "fix.sh" }, + calendar_events_enabled: true, + conditional_access_enabled: true, + webhook: "On", + }, + "webhook" + ); + expect(result.map((a) => a.type)).toEqual([ + "software", + "script", + "calendar", + "conditional_access", + "other", + ]); }); }); diff --git a/frontend/pages/policies/ManagePoliciesPage/helpers.tsx b/frontend/pages/policies/ManagePoliciesPage/helpers.tsx index cae20862178..477a2b3b24b 100644 --- a/frontend/pages/policies/ManagePoliciesPage/helpers.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/helpers.tsx @@ -1,10 +1,85 @@ import React from "react"; -import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; +import { IPolicyStats } from "interfaces/policy"; import { IInstallSoftwareFormData } from "./components/InstallSoftwareModal/InstallSoftwareModal"; import { IPolicyRunScriptFormData } from "./components/PolicyRunScriptModal/PolicyRunScriptModal"; +export type OtherAutomationType = "webhook" | "ticket"; + +export type AutomationDisplayType = + | "software" + | "script" + | "calendar" + | "conditional_access" + | "other"; + +interface ISoftwareAutomationData { + type: "software"; + name: string; + softwareTitleId: number; +} + +interface INonSoftwareAutomationData { + type: Exclude; + name: string; + softwareTitleId?: never; +} + +export type IAutomationData = + | ISoftwareAutomationData + | INonSoftwareAutomationData; + +/** Returns an ordered list of automations configured for a policy. */ +export const getAutomationsForPolicy = ( + policy: Pick< + IPolicyStats, + | "install_software" + | "run_script" + | "calendar_events_enabled" + | "conditional_access_enabled" + | "webhook" + >, + otherAutomationType?: OtherAutomationType +): IAutomationData[] => { + const automations: IAutomationData[] = []; + + if (policy.install_software) { + automations.push({ + type: "software", + name: + policy.install_software.display_name || policy.install_software.name, + softwareTitleId: policy.install_software.software_title_id, + }); + } + if (policy.run_script) { + automations.push({ + type: "script", + name: policy.run_script.name, + }); + } + if (policy.calendar_events_enabled) { + automations.push({ + type: "calendar", + name: "Maintenance window", + }); + } + if (policy.conditional_access_enabled) { + automations.push({ + type: "conditional_access", + name: "Conditional access", + }); + } + if (policy.webhook === "On") { + automations.push({ + type: "other", + name: otherAutomationType === "ticket" ? "Ticket" : "Webhook", + }); + } + + return automations; +}; + /** Creates a readable JSX element from the error message */ export const getInstallSoftwareErrorMessage = ( result: PromiseRejectedResult, @@ -68,35 +143,3 @@ export const getRunScriptErrorMessage = ( return <>Could not update policy. {jsxElement}; }; - -/** Derives a comma-separated string of automation types enabled for a policy. - * Returns "---" if no automations are enabled. */ -export const getAutomationTypesString = (policy: { - install_software?: { software_title_id: number }; - run_script?: { id: number }; - calendar_events_enabled: boolean; - conditional_access_enabled: boolean; - webhook?: string; -}): string => { - const types: string[] = []; - - if (policy.install_software) { - types.push("Software"); - } - if (policy.run_script) { - types.push("Script"); - } - if (policy.calendar_events_enabled) { - types.push("Calendar"); - } - if (policy.conditional_access_enabled) { - types.push("Conditional access"); - } - if (policy.webhook === "On") { - types.push("Other"); - } - - if (types.length === 0) return DEFAULT_EMPTY_CELL_VALUE; - // Lowercase all types after the first to match sentence-case display - return types.map((t, i) => (i === 0 ? t : t.toLowerCase())).join(", "); -}; From 9b7062e04d1a15b114fdd11ec88d7d0dfb38fd6f Mon Sep 17 00:00:00 2001 From: nulmete Date: Wed, 20 May 2026 16:11:13 -0300 Subject: [PATCH 2/3] fix test --- .../components/PoliciesTable/PoliciesTable.tests.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx index e74a2eb51db..08c7aa6db0f 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx @@ -470,7 +470,7 @@ describe("Policies table", () => { ); expect(screen.getByText("Automations")).toBeInTheDocument(); - expect(screen.getByText("Software, calendar")).toBeInTheDocument(); + expect(screen.getByText("2 automations")).toBeInTheDocument(); expect(screen.getByText("---")).toBeInTheDocument(); }); }); From 6aa418cf19d275c2321e6d91edfb1132fa3e2102 Mon Sep 17 00:00:00 2001 From: nulmete Date: Wed, 20 May 2026 16:28:47 -0300 Subject: [PATCH 3/3] move OtherAutomationType to interfaces/policy.ts --- frontend/interfaces/policy.ts | 2 ++ .../policies/ManagePoliciesPage/ManagePoliciesPage.tsx | 2 +- .../components/PoliciesTable/PoliciesTable.tsx | 3 +-- .../components/PoliciesTable/PoliciesTableConfig.tsx | 8 ++------ frontend/pages/policies/ManagePoliciesPage/helpers.tsx | 4 +--- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/frontend/interfaces/policy.ts b/frontend/interfaces/policy.ts index 9db6066bcec..8901a7586df 100644 --- a/frontend/interfaces/policy.ts +++ b/frontend/interfaces/policy.ts @@ -20,6 +20,8 @@ export default PropTypes.shape({ updated_at: PropTypes.string.isRequired, }); +export type OtherAutomationType = "webhook" | "ticket"; + export interface IStoredPolicyResponse { policy: IPolicy; } diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 4b12a485818..3b975fdb526 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -24,6 +24,7 @@ import { ILoadAllPoliciesResponse, ILoadTeamPoliciesResponse, IPoliciesCountResponse, + OtherAutomationType, } from "interfaces/policy"; import { API_ALL_TEAMS_ID, APP_CONTEXT_ALL_TEAMS_ID } from "interfaces/team"; @@ -58,7 +59,6 @@ import TooltipWrapper from "components/TooltipWrapper"; import PoliciesTable from "./components/PoliciesTable"; import DeletePoliciesModal from "./components/DeletePoliciesModal"; -import { OtherAutomationType } from "./helpers"; import { DEFAULT_POLICY } from "../constants"; import AutomationsModal from "./components/AutomationsModal"; import ManageAutomationsModal from "./components/ManageAutomationsModal"; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx index 066f8148d53..6f2d9eae2c9 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx @@ -1,7 +1,7 @@ import React, { useContext } from "react"; import { AppContext } from "context/app"; -import { IPolicyStats } from "interfaces/policy"; +import { IPolicyStats, OtherAutomationType } from "interfaces/policy"; import { ITeamSummary, APP_CONTEXT_ALL_TEAMS_ID } from "interfaces/team"; import { IEmptyStateProps } from "interfaces/empty_state"; import Button from "components/buttons/Button"; @@ -14,7 +14,6 @@ import { DEFAULT_SORT_DIRECTION, DEFAULT_PAGE_SIZE, } from "../../ManagePoliciesPage"; -import { OtherAutomationType } from "../../helpers"; // isLastPage is removable if/when API is updated to include meta.has_next_results const isLastPage = (count: number, pageSize: number, page: number) => { diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx index dbe73968dfc..b410c6a01a3 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx @@ -12,7 +12,7 @@ import TooltipWrapper from "components/TooltipWrapper"; import Icon from "components/Icon"; import Graphic from "components/Graphic"; import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon"; -import { IPolicyStats } from "interfaces/policy"; +import { IPolicyStats, OtherAutomationType } from "interfaces/policy"; import PATHS from "router/paths"; import ENDPOINTS from "utilities/endpoints"; @@ -26,11 +26,7 @@ import { PATCH_TOOLTIP_CONTENT } from "components/SoftwareInstallPolicyBadges/So import { getConditionalSelectHeaderCheckboxProps } from "components/TableContainer/utilities/config_utils"; import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; -import { - getAutomationsForPolicy, - IAutomationData, - OtherAutomationType, -} from "../../helpers"; +import { getAutomationsForPolicy, IAutomationData } from "../../helpers"; import PassingColumnHeader from "../PassingColumnHeader"; interface IGetToggleAllRowsSelectedProps { diff --git a/frontend/pages/policies/ManagePoliciesPage/helpers.tsx b/frontend/pages/policies/ManagePoliciesPage/helpers.tsx index 477a2b3b24b..1ec32925603 100644 --- a/frontend/pages/policies/ManagePoliciesPage/helpers.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/helpers.tsx @@ -1,12 +1,10 @@ import React from "react"; -import { IPolicyStats } from "interfaces/policy"; +import { IPolicyStats, OtherAutomationType } from "interfaces/policy"; import { IInstallSoftwareFormData } from "./components/InstallSoftwareModal/InstallSoftwareModal"; import { IPolicyRunScriptFormData } from "./components/PolicyRunScriptModal/PolicyRunScriptModal"; -export type OtherAutomationType = "webhook" | "ticket"; - export type AutomationDisplayType = | "software" | "script"