From ccbdf46119c57fd6abd7d0c518fe270937f88941 Mon Sep 17 00:00:00 2001
From: Gabriel Hernandez
Date: Thu, 31 Oct 2024 15:10:49 +0000
Subject: [PATCH] add UI to add the include any option for custom profile
custom label target (#23390)
relates to #22575
Add the UI for allowing custom profiles to be uploaded to hosts that
have any of specified labels. I also spent some time cleaning up the
custom settings card and its components. I was able to remove a lot of
unused styles.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [ ] Added/updated tests
- [x] Manual QA for all new/changed functionality
---
changes/22575-ui-for-include-any-labels | 2 +
frontend/interfaces/mdm.ts | 1 +
.../cards/CustomSettings/CustomSettings.tsx | 23 +--
.../cards/CustomSettings/_styles.scss | 149 ------------------
.../ProfileLabelsModal/ProfileLabelsModal.tsx | 93 +++++------
.../ProfileLabelsModal/_styles.scss | 32 ++++
.../ProfileListItem/ProfileListItem.tsx | 3 +-
.../{ => AddProfileCard}/AddProfileCard.tsx | 16 +-
.../components/AddProfileCard/_styles.scss | 21 +++
.../components/AddProfileCard/index.ts | 1 +
.../AddProfileModal/AddProfileModal.tsx | 17 +-
.../components/AddProfileModal/helpers.tsx | 31 +++-
frontend/services/entities/mdm.ts | 19 ++-
13 files changed, 158 insertions(+), 250 deletions(-)
create mode 100644 changes/22575-ui-for-include-any-labels
create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/_styles.scss
rename frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/{ => AddProfileCard}/AddProfileCard.tsx (69%)
create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/index.ts
diff --git a/changes/22575-ui-for-include-any-labels b/changes/22575-ui-for-include-any-labels
new file mode 100644
index 0000000000..5f66f8396b
--- /dev/null
+++ b/changes/22575-ui-for-include-any-labels
@@ -0,0 +1,2 @@
+- add UI for allowing users to install custom profiles on hosts that include any of the defined
+labels
diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts
index 2f822eb968..07fe2cffb9 100644
--- a/frontend/interfaces/mdm.ts
+++ b/frontend/interfaces/mdm.ts
@@ -111,6 +111,7 @@ export interface IMdmProfile {
updated_at: string;
checksum: string | null; // null for windows profiles
labels_include_all?: IProfileLabel[];
+ labels_include_any?: IProfileLabel[];
labels_exclude_any?: IProfileLabel[];
}
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx
index 89887e9b83..00cc7e199b 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/CustomSettings.tsx
@@ -20,7 +20,7 @@ import Pagination from "pages/ManageControlsPage/components/Pagination";
import UploadList from "../../../components/UploadList";
import AddProfileCard from "./components/ProfileUploader/components/AddProfileCard";
-import AddProfileModal from "./components/ProfileUploader/components/AddProfileModal/AddProfileModal";
+import AddProfileModal from "./components/ProfileUploader/components/AddProfileModal";
import DeleteProfileModal from "./components/DeleteProfileModal/DeleteProfileModal";
import ProfileLabelsModal from "./components/ProfileLabelsModal/ProfileLabelsModal";
import ProfileListItem from "./components/ProfileListItem";
@@ -136,7 +136,7 @@ const CustomSettings = ({
}
if (!profiles?.length) {
- return null;
+ return ;
}
return (
@@ -144,11 +144,11 @@ const CustomSettings = ({
- ProfileListHeading({
- onClickAddProfile: () => setShowAddProfileModal(true),
- })
- }
+ HeadingComponent={() => (
+ setShowAddProfileModal(true)}
+ />
+ )}
ListItemComponent={({ listItem }) => (
- {renderProfileList()}
- {!isLoadingProfiles && !isErrorProfiles && !profiles?.length && (
-
- )}
+ <>{renderProfileList()}>
{showAddProfileModal && (
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss
index 30a35b38fb..b758f3c777 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/_styles.scss
@@ -1,69 +1,15 @@
.custom-settings {
- .section-header {
- margin: 0;
- padding: 0 0 12px 0;
-
- h2 {
- padding-bottom: 0;
- border-bottom: none;
- margin: 0;
- }
- }
-
&__description {
font-size: $x-small;
margin: $pad-large 0;
}
- &__profiles-header {
- padding: $pad-medium $pad-large;
- display: flex;
- justify-content: space-between;
- font-size: $x-small;
- font-weight: $bold;
- border-bottom: 1px solid $ui-fleet-black-10;
- }
-
- &__profile-list {
- list-style: none;
- padding: 0;
- margin: 0;
- }
-
&__pagination-controls {
display: flex;
justify-content: flex-end;
margin: $pad-large 0;
}
- &__file-uploader {
- margin-top: $pad-xxlarge;
- }
-
- &__labels-list {
- border-radius: 6px;
- border: 1px solid $ui-fleet-black-10;
-
- &--label {
- display: flex;
- height: 41px;
- padding: 0 $pad-large;
- align-items: center;
- justify-content: space-between;
- border-bottom: 1px solid $ui-fleet-black-10;
-
- .warning {
- display: flex;
- padding: 0;
- gap: $pad-small;
- }
-
- &:last-of-type {
- border-bottom: none;
- }
- }
- }
-
.upload-list {
&__list {
.list-item__label-count {
@@ -84,99 +30,4 @@
}
}
}
-
- .add-profile {
- &__card--content-wrap {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: $pad-medium;
- padding: 28.5px 0;
- }
-
- &__profile-graphic {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: $pad-small;
-
- &--message {
- text-align: center;
- line-height: 20px;
- }
- }
-
- &__button-wrap {
- display: flex;
- justify-content: flex-end;
- padding-top: $pad-medium;
- }
-
- &__target {
- margin: $pad-large 0 $pad-small 0;
- }
-
- &__description {
- margin: $pad-medium 0;
- }
-
- &__no-labels {
- display: flex;
- height: 187px;
- flex-direction: column;
- align-items: center;
- gap: $pad-small;
- justify-content: center;
-
- span {
- color: $ui-fleet-black-75;
- }
- }
-
- &__checkboxes {
- display: flex;
- max-height: 187px;
- flex-direction: column;
- border-radius: $border-radius;
- border: 1px solid $ui-fleet-black-10;
- overflow-y: auto;
-
- .loading-spinner {
- margin: 69.5px auto;
- }
- }
-
- &__label {
- width: 100%;
- padding: $pad-small $pad-medium;
- box-sizing: border-box;
- display: flex;
- align-items: center;
-
- &:not(:last-child) {
- border-bottom: 1px solid $ui-fleet-black-10;
- }
-
- .form-field--checkbox {
- width: auto;
- }
- }
-
- &__label-name {
- padding-left: $pad-large;
- }
-
- .fleet-checkbox {
- height: 20px;
- display: flex;
- align-items: center;
-
- &__label {
- width: 490px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- }
}
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/ProfileLabelsModal.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/ProfileLabelsModal.tsx
index 4128a5e826..f5590c83af 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/ProfileLabelsModal.tsx
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/ProfileLabelsModal.tsx
@@ -6,35 +6,7 @@ import InfoBanner from "components/InfoBanner";
import TooltipWrapper from "components/TooltipWrapper";
import Icon from "components/Icon";
-interface IModalDescriptionProps {
- baseClass: string;
- profileName: string;
- targetType: "includeAll" | "excludeAny";
-}
-
-const ModalDescription = ({
- baseClass,
- profileName,
- targetType,
-}: IModalDescriptionProps) => {
- const targetTypeText =
- targetType === "includeAll" ? (
- <>
- have all
- >
- ) : (
- <>
- don't have any
- >
- );
-
- return (
-
- {profileName} profile only applies to hosts that {targetTypeText}{" "}
- of these labels:
-
- );
-};
+const baseClass = "profile-labels-modal";
const BrokenLabelWarning = () => (
@@ -51,16 +23,10 @@ const BrokenLabelWarning = () => (
);
-const LabelsList = ({
- baseClass,
- labels,
-}: {
- baseClass: string;
- labels: IProfileLabel[];
-}) => (
-
+const LabelsList = ({ labels }: { labels: IProfileLabel[] }) => (
+
{labels.map((label) => (
-
+
-
{label.name}
{label.broken && (
@@ -68,19 +34,17 @@ const LabelsList = ({
Label deleted
)}
-
+
))}
-
+
);
interface IProfileLabelsModalProps {
- baseClass: string;
profile: IMdmProfile | null;
setModalData: React.Dispatch>;
}
const ProfileLabelsModal = ({
- baseClass,
profile,
setModalData,
}: IProfileLabelsModalProps) => {
@@ -88,30 +52,53 @@ const ProfileLabelsModal = ({
return null;
}
- const { name, labels_include_all, labels_exclude_any } = profile;
- const labels = labels_include_all || labels_exclude_any;
+ const {
+ name,
+ labels_include_all,
+ labels_include_any,
+ labels_exclude_any,
+ } = profile;
+ const labels = labels_include_all || labels_include_any || labels_exclude_any;
if (!labels?.length) {
// caller ensures this never happens
return null;
}
+ const renderlabelDescription = () => {
+ let targetTypeText = <>>;
+ if (labels_include_all) {
+ targetTypeText = have all;
+ } else if (labels_include_any) {
+ targetTypeText = have all;
+ } else {
+ targetTypeText = don't have any;
+ }
+
+ return (
+
+ {name} profile only applies to hosts that {targetTypeText} of
+ these labels:
+
+ );
+ };
+
return (
- setModalData(null)}>
-
+
setModalData(null)}
+ >
+ <>
{labels.some((label) => label.broken) && }
-
-
+ <>{renderlabelDescription()}>
+
-
+ >
);
};
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/_styles.scss
new file mode 100644
index 0000000000..1bac831a22
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileLabelsModal/_styles.scss
@@ -0,0 +1,32 @@
+.profile-labels-modal {
+ &__description {
+ font-size: $x-small;
+ margin: $pad-large 0 $pad-medium;
+ }
+
+ &__labels-list {
+ border-radius: 6px;
+ border: 1px solid $ui-fleet-black-10;
+ padding: 0;
+ margin: 0;
+
+ &--label {
+ display: flex;
+ height: 41px;
+ padding: 0 $pad-large;
+ align-items: center;
+ justify-content: space-between;
+ border-bottom: 1px solid $ui-fleet-black-10;
+
+ .warning {
+ display: flex;
+ padding: 0;
+ gap: $pad-small;
+ }
+
+ &:last-of-type {
+ border-bottom: none;
+ }
+ }
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx
index 9fb0039d28..b62437d8d8 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx
@@ -87,6 +87,7 @@ const ProfileListItem = ({
const {
created_at,
labels_include_all,
+ labels_include_any,
labels_exclude_any,
name,
platform,
@@ -102,7 +103,7 @@ const ProfileListItem = ({
FileSaver.saveAs(file);
};
- const labels = labels_include_all || labels_exclude_any;
+ const labels = labels_include_all || labels_include_any || labels_exclude_any;
const renderLabelInfo = () => {
if (!isPremium || labels === undefined || labels.length === 0) {
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/AddProfileCard.tsx
similarity index 69%
rename from frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard.tsx
rename to frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/AddProfileCard.tsx
index c3ab102ce7..d9d09f081f 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard.tsx
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileCard/AddProfileCard.tsx
@@ -2,16 +2,16 @@ import React from "react";
import Card from "components/Card";
import Button from "components/buttons/Button";
-import ProfileGraphic from "./AddProfileGraphic";
+import ProfileGraphic from "../AddProfileGraphic";
-const AddProfileCard = ({
- baseClass,
- setShowModal,
-}: {
- baseClass: string;
+const baseClass = "add-profile-card";
+
+interface IAddProfileCardProps {
setShowModal: React.Dispatch>;
-}) => (
-
+}
+
+const AddProfileCard = ({ setShowModal }: IAddProfileCardProps) => (
+
);
diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx
index d6d61c5628..a63c121023 100644
--- a/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx
+++ b/frontend/pages/ManageControlsPage/OSSettings/cards/CustomSettings/components/ProfileUploader/components/AddProfileModal/helpers.tsx
@@ -5,11 +5,22 @@ import { IDropdownOption } from "interfaces/dropdownOption";
export const CUSTOM_TARGET_OPTIONS: IDropdownOption[] = [
{
value: "labelsIncludeAll",
- label: "Include all ",
+ label: "Include all",
helpText: (
<>
- Profile will only be applied to hosts that have all of these
- labels{" "}
+ Profile will only be applied to hosts that have all of these
+ labels.
+ >
+ ),
+ disabled: false,
+ },
+ {
+ value: "labelsIncludeAny",
+ label: "Include any",
+ helpText: (
+ <>
+ Profile will only be applied to hosts that have any of these
+ labels.
>
),
disabled: false,
@@ -19,8 +30,8 @@ export const CUSTOM_TARGET_OPTIONS: IDropdownOption[] = [
label: "Exclude any",
helpText: (
<>
- Profile will be applied to hosts that don't have any of
- these labels{" "}
+ Profile will only be applied to hosts that don't have any of
+ these labels.
>
),
disabled: false,
@@ -36,7 +47,10 @@ export const listNamesFromSelectedLabels = (dict: Record) => {
}, [] as string[]);
};
-export type CustomTargetOption = "labelsIncludeAll" | "labelsExcludeAny";
+export type CustomTargetOption =
+ | "labelsIncludeAll"
+ | "labelsIncldeAny"
+ | "labelsExcludeAny";
export const generateLabelKey = (
target: string,
@@ -51,3 +65,8 @@ export const generateLabelKey = (
[customTargetOption]: listNamesFromSelectedLabels(selectedLabels),
};
};
+
+export const getDescriptionText = (value: string) => {
+ return CUSTOM_TARGET_OPTIONS.find((option) => option.value === value)
+ ?.helpText;
+};
diff --git a/frontend/services/entities/mdm.ts b/frontend/services/entities/mdm.ts
index 285973d7ff..50ed3bd428 100644
--- a/frontend/services/entities/mdm.ts
+++ b/frontend/services/entities/mdm.ts
@@ -49,6 +49,7 @@ export interface IUploadProfileApiParams {
file: File;
teamId?: number;
labelsIncludeAll?: string[];
+ labelsIncludeAny?: string[];
labelsExcludeAny?: string[];
}
@@ -132,6 +133,7 @@ const mdmService = {
file,
teamId,
labelsIncludeAll,
+ labelsIncludeAny,
labelsExcludeAny,
}: IUploadProfileApiParams) => {
const { MDM_PROFILES } = endpoints;
@@ -143,11 +145,18 @@ const mdmService = {
formData.append("team_id", teamId.toString());
}
- if (labelsIncludeAll || labelsExcludeAny) {
- const labels = labelsIncludeAll || labelsExcludeAny;
- const labelKey = labelsIncludeAll
- ? "labels_include_all"
- : "labels_exclude_any";
+ if (labelsIncludeAll || labelsIncludeAny || labelsExcludeAny) {
+ const labels = labelsIncludeAll || labelsIncludeAny || labelsExcludeAny;
+
+ let labelKey = "";
+ if (labelsIncludeAll) {
+ labelKey = "labels_include_all";
+ } else if (labelsIncludeAny) {
+ labelKey = "labels_include_any";
+ } else {
+ labelKey = "labels_exclude_any";
+ }
+
labels?.forEach((label) => {
formData.append(labelKey, label);
});