From 8d646cd1653084f577bc40b23e64c8cf5cc4c744 Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Wed, 18 Mar 2026 15:16:25 -0400 Subject: [PATCH] ui impl for labels include all (#41836) **Related issue:** Resolves #40724 # Checklist for submitter If some of the following don't apply, delete the relevant line. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually --- frontend/__mocks__/softwareMock.ts | 3 ++ frontend/interfaces/software.ts | 2 + .../EditAutoUpdateConfigModal.tests.tsx | 3 ++ .../SoftwareTitleDetailsPage/helpers.tests.ts | 2 + frontend/pages/SoftwarePage/helpers.tsx | 46 ++++++++++++++++--- frontend/services/entities/software.ts | 21 +++++++++ 6 files changed, 70 insertions(+), 7 deletions(-) diff --git a/frontend/__mocks__/softwareMock.ts b/frontend/__mocks__/softwareMock.ts index a33f37453c..e2a12f291e 100644 --- a/frontend/__mocks__/softwareMock.ts +++ b/frontend/__mocks__/softwareMock.ts @@ -159,6 +159,7 @@ const DEFAULT_APP_STORE_APP_MOCK: IAppStoreApp = { failed: 3, }, labels_include_any: null, + labels_include_all: null, labels_exclude_any: null, }; @@ -183,6 +184,7 @@ const DEFAULT_APP_STORE_APP_ANDROID_MOCK: IAppStoreApp = { categories: null, labels_include_any: null, labels_exclude_any: null, + labels_include_all: null, configuration: '{ workProfileWidgets: "WORK_PROFILE_WIDGETS_ALLOWED" }', }; @@ -256,6 +258,7 @@ const DEFAULT_SOFTWARE_PACKAGE_MOCK: ISoftwarePackage = { url: "https://fakeurl.testpackageurlforfalconapp.fake/test/package", hash_sha256: "abcd1234", labels_include_any: null, + labels_include_all: null, labels_exclude_any: null, install_during_setup: undefined, }; diff --git a/frontend/interfaces/software.ts b/frontend/interfaces/software.ts index 044e0ed082..00af37af59 100644 --- a/frontend/interfaces/software.ts +++ b/frontend/interfaces/software.ts @@ -142,6 +142,7 @@ export interface ISoftwarePackage { automatic_install_policies?: ISoftwareInstallPolicy[] | null; install_during_setup?: boolean; labels_include_any: ILabelSoftwareTitle[] | null; + labels_include_all: ILabelSoftwareTitle[] | null; labels_exclude_any: ILabelSoftwareTitle[] | null; categories?: SoftwareCategory[] | null; fleet_maintained_app_id?: number | null; @@ -172,6 +173,7 @@ export interface IAppStoreApp { } | null; version?: string; labels_include_any: ILabelSoftwareTitle[] | null; + labels_include_all: ILabelSoftwareTitle[] | null; labels_exclude_any: ILabelSoftwareTitle[] | null; categories?: SoftwareCategory[] | null; configuration?: string; diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditAutoUpdateConfigModal/EditAutoUpdateConfigModal.tests.tsx b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditAutoUpdateConfigModal/EditAutoUpdateConfigModal.tests.tsx index 7debb4466c..5a68e974a9 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditAutoUpdateConfigModal/EditAutoUpdateConfigModal.tests.tsx +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/EditAutoUpdateConfigModal/EditAutoUpdateConfigModal.tests.tsx @@ -473,6 +473,7 @@ describe("Edit Auto Update Config Modal", () => { auto_update_enabled: false, labels_include_any: [], labels_exclude_any: [], + labels_include_all: [], fleet_id: 1, }); }); @@ -514,6 +515,7 @@ describe("Edit Auto Update Config Modal", () => { auto_update_window_end: "04:00", labels_include_any: [], labels_exclude_any: [], + labels_include_all: [], fleet_id: 1, }); }); @@ -547,6 +549,7 @@ describe("Edit Auto Update Config Modal", () => { auto_update_enabled: false, labels_include_any: [], labels_exclude_any: [], + labels_include_all: [], fleet_id: 1, }); }); diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.tests.ts b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.tests.ts index b8a105f75c..58d54b34ff 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.tests.ts +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.tests.ts @@ -13,6 +13,7 @@ describe("SoftwareTitleDetailsPage helpers", () => { software_package: { labels_include_any: null, labels_exclude_any: null, + labels_include_all: null, name: "TestPackage.pkg", title_id: 2, version: "1.0.0", @@ -79,6 +80,7 @@ describe("SoftwareTitleDetailsPage helpers", () => { icon_url: "https://example.com/icon.png", labels_exclude_any: null, labels_include_any: null, + labels_include_all: null, }, source: "apps", hosts_count: 10, diff --git a/frontend/pages/SoftwarePage/helpers.tsx b/frontend/pages/SoftwarePage/helpers.tsx index 6d005e1148..a20c0c37da 100644 --- a/frontend/pages/SoftwarePage/helpers.tsx +++ b/frontend/pages/SoftwarePage/helpers.tsx @@ -83,6 +83,7 @@ export const getTargetType = ( if (!softwareInstaller) return "All hosts"; return !softwareInstaller.labels_include_any && + !softwareInstaller.labels_include_all && !softwareInstaller.labels_exclude_any ? "All hosts" : "Custom"; @@ -94,9 +95,9 @@ export const getCustomTarget = ( ) => { if (!softwareInstaller) return "labelsIncludeAny"; - return softwareInstaller.labels_include_any - ? "labelsIncludeAny" - : "labelsExcludeAny"; + if (softwareInstaller.labels_include_any) return "labelsIncludeAny"; + if (softwareInstaller.labels_include_all) return "labelsIncludeAll"; + return "labelsExcludeAny"; }; // Used in EditSoftwareModal and PackageForm @@ -106,14 +107,23 @@ export const generateSelectedLabels = ( if ( !softwareInstaller || (!softwareInstaller.labels_include_any && + !softwareInstaller.labels_include_all && !softwareInstaller.labels_exclude_any) ) { return {}; } - const customTypeKey = softwareInstaller.labels_include_any - ? "labels_include_any" - : "labels_exclude_any"; + let customTypeKey: + | "labels_include_any" + | "labels_include_all" + | "labels_exclude_any"; + if (softwareInstaller.labels_include_any) { + customTypeKey = "labels_include_any"; + } else if (softwareInstaller.labels_include_all) { + customTypeKey = "labels_include_all"; + } else { + customTypeKey = "labels_exclude_any"; + } return ( softwareInstaller[customTypeKey]?.reduce>( @@ -145,7 +155,21 @@ export const generateHelpText = ( ); } - // this is the case for labelsExcludeAny + if (customTarget === "labelsIncludeAll") { + return !automaticInstall ? ( + <> + Software will only be available for install on hosts that{" "} + have all of these labels: + + ) : ( + <> + Software will only be installed on hosts that have all of these + labels: + + ); + } + + // labelsExcludeAny return !automaticInstall ? ( <> Software will only be available for install on hosts that{" "} @@ -164,11 +188,19 @@ export const CUSTOM_TARGET_OPTIONS: IDropdownOption[] = [ { value: "labelsIncludeAny", label: "Include any", + helpText: "Hosts that have any of the selected labels.", + disabled: false, + }, + { + value: "labelsIncludeAll", + label: "Include all", + helpText: "Hosts that have all of the selected labels.", disabled: false, }, { value: "labelsExcludeAny", label: "Exclude any", + helpText: "Hosts that don't have any of the selected labels.", disabled: false, }, ]; diff --git a/frontend/services/entities/software.ts b/frontend/services/entities/software.ts index e3542d7217..ebd60aa310 100644 --- a/frontend/services/entities/software.ts +++ b/frontend/services/entities/software.ts @@ -162,6 +162,7 @@ interface IAddFleetMaintainedAppPostBody { self_service?: boolean; automatic_install?: boolean; labels_include_any?: string[]; + labels_include_all?: string[]; labels_exclude_any?: string[]; categories: string[]; } @@ -175,6 +176,7 @@ export interface IAddAppStoreAppPostBody { // No automatic_install on add Android app automatic_install?: boolean; labels_include_any?: string[]; + labels_include_all?: string[]; labels_exclude_any?: string[]; categories?: SoftwareCategory[]; } @@ -185,6 +187,7 @@ export interface IEditAppStoreAppPostBody { self_service?: boolean; // No automatic_install on edit VPP or android app labels_include_any?: string[]; + labels_include_all?: string[]; labels_exclude_any?: string[]; categories?: SoftwareCategory[]; display_name?: string; @@ -219,6 +222,8 @@ const handleAndroidForm = ( const selectedLabels = listNamesFromSelectedLabels(formData.labelTargets); if (formData.customTarget === "labelsIncludeAny") { body.labels_include_any = selectedLabels; + } else if (formData.customTarget === "labelsIncludeAll") { + body.labels_include_all = selectedLabels; } else { body.labels_exclude_any = selectedLabels; } @@ -250,6 +255,8 @@ const handleVppAppForm = (teamId: number, formData: ISoftwareVppFormData) => { const selectedLabels = listNamesFromSelectedLabels(formData.labelTargets); if (formData.customTarget === "labelsIncludeAny") { body.labels_include_any = selectedLabels; + } else if (formData.customTarget === "labelsIncludeAll") { + body.labels_include_all = selectedLabels; } else { body.labels_exclude_any = selectedLabels; } @@ -299,6 +306,8 @@ const handleEditPackageForm = ( if (data.targetType === "All hosts") { if (orignalPackage.labels_include_any) { formData.append("labels_include_any", ""); + } else if (orignalPackage.labels_include_all) { + formData.append("labels_include_all", ""); } else { formData.append("labels_exclude_any", ""); } @@ -310,6 +319,8 @@ const handleEditPackageForm = ( let labelKey = ""; if (data.customTarget === "labelsIncludeAny") { labelKey = "labels_include_any"; + } else if (data.customTarget === "labelsIncludeAll") { + labelKey = "labels_include_all"; } else { labelKey = "labels_exclude_any"; } @@ -346,12 +357,15 @@ const handleAutoUpdateConfigAppStoreAppForm = ( const selectedLabels = listNamesFromSelectedLabels(formData.labelTargets); if (formData.customTarget === "labelsIncludeAny") { body.labels_include_any = selectedLabels; + } else if (formData.customTarget === "labelsIncludeAll") { + body.labels_include_all = selectedLabels; } else { body.labels_exclude_any = selectedLabels; } } else { body.labels_exclude_any = []; body.labels_include_any = []; + body.labels_include_all = []; } }; @@ -371,12 +385,15 @@ const handleEditAppStoreAppForm = ( const selectedLabels = listNamesFromSelectedLabels(formData.labelTargets); if (formData.customTarget === "labelsIncludeAny") { body.labels_include_any = selectedLabels; + } else if (formData.customTarget === "labelsIncludeAll") { + body.labels_include_all = selectedLabels; } else { body.labels_exclude_any = selectedLabels; } } else { body.labels_exclude_any = []; body.labels_include_any = []; + body.labels_include_all = []; } }; @@ -552,6 +569,8 @@ export default { let labelKey = ""; if (data.customTarget === "labelsIncludeAny") { labelKey = "labels_include_any"; + } else if (data.customTarget === "labelsIncludeAll") { + labelKey = "labels_include_all"; } else { labelKey = "labels_exclude_any"; } @@ -796,6 +815,8 @@ export default { const selectedLabels = listNamesFromSelectedLabels(formData.labelTargets); if (formData.customTarget === "labelsIncludeAny") { body.labels_include_any = selectedLabels; + } else if (formData.customTarget === "labelsIncludeAll") { + body.labels_include_all = selectedLabels; } else { body.labels_exclude_any = selectedLabels; }