Don't show vulns chart on free tier (#45440)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #45434

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
n/a, unreleased

## Testing

- [ ] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
**Free tier**
<img width="796" height="500" alt="image"
src="https://github.com/user-attachments/assets/d03df638-529b-45b7-ae7d-c07a7f690a29"
/>

<img width="513" height="425" alt="image"
src="https://github.com/user-attachments/assets/ec3e9fc9-9183-41df-83ea-d89ac4bf7c39"
/>

<img width="507" height="419" alt="image"
src="https://github.com/user-attachments/assets/248ceed8-4032-4ca9-b6c5-80e69c2d5a24"
/>

**Premium tier**

<img width="690" height="534" alt="image"
src="https://github.com/user-attachments/assets/bea25fc7-a603-406c-936b-9d6743d042ce"
/>

<img width="501" height="433" alt="image"
src="https://github.com/user-attachments/assets/4a09bb9d-7662-438a-9538-2f6d215692ff"
/>

<img width="499" height="421" alt="image"
src="https://github.com/user-attachments/assets/00d8ceb4-4b9e-4432-add2-5e0770be851d"
/>


For unreleased bug fixes in a release candidate, one of:

- [X] Confirmed that the fix is not expected to adversely impact load
test results


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
- Chart datasets now adapt to subscription tier: standard accounts see a
simplified chart (uptime only); premium accounts see additional dataset
options.
- Vulnerabilities toggle in activity/data retention settings is shown
only for premium accounts.
* **Tests**
- Admin settings tests updated to run with premium-tier context and
verify related behaviors.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45440)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Scott Gress
2026-05-13 18:28:18 -05:00
committed by GitHub
parent 2607475fae
commit 2c13d05d79
3 changed files with 104 additions and 88 deletions
@@ -1,4 +1,4 @@
import React, { useEffect, useState, useMemo } from "react";
import React, { useContext, useEffect, useState, useMemo } from "react";
import { useQuery } from "react-query";
import { format, parseISO } from "date-fns";
import { SingleValue } from "react-select-5";
@@ -27,6 +27,8 @@ import {
HistoricalDataConfigKey,
} from "interfaces/charts";
import { AppContext } from "context/app";
import ChartFilterModal, { IChartFilterState } from "./ChartFilterModal";
import LineChartViz from "./LineChartViz";
import CheckerboardViz from "./CheckerboardViz";
@@ -38,64 +40,6 @@ const baseClass = "chart-card";
// configurable ranges we'll add UI and request-param plumbing for this.
const CHART_DAYS = 30;
const DATASETS: IDataSet[] = [
{
name: "uptime",
label: "Hosts online",
defaultChartType: "checkerboard",
description: (
<>
The number of hosts detected online (checking in to Fleet) during a
given hour.
<br />
<br />
Currently, only macOS, Windows, Linux, and ChromeOS are supported.
</>
),
tooltipFormatter: ({ value }: { value: number }) =>
`${value.toLocaleString()} host${value === 1 ? "" : "s"} online`,
relativeScale: true,
},
{
name: "cve",
label: "Vulnerability exposure",
defaultChartType: "checkerboard",
description: (
<>
The number of hosts with critical vulnerabilities detected in browsers
and{" "}
<CustomLink
newTab
text="other common software "
variant="tooltip-link"
url="https://fleetdm.com/learn-more-about/vulnerability-exposure-cves"
/>
<br />
<br />
Want more control? Comprehensive vulnerability filtering is{" "}
<CustomLink
newTab
text="coming soon "
variant="tooltip-link"
url="https://github.com/fleetdm/fleet/issues/44746"
/>
</>
),
tooltipFormatter: ({ value }: { value: number }) =>
`${value.toLocaleString()} host${value === 1 ? "" : "s"}`,
theme: "red",
relativeScale: true,
},
];
const DATASET_OPTIONS: CustomOptionType[] = DATASETS.map((ds) => ({
label: ds.label,
value: ds.name,
}));
const getDataset = (name: string): IDataSet =>
DATASETS.find((ds) => ds.name === name) || DATASETS[0];
const hasActiveFilters = (filters: IChartFilterState): boolean => {
const hasHostFilter =
filters.hostFilterMode !== "none" && filters.selectedHosts.length > 0;
@@ -122,6 +66,69 @@ const ChartCard = ({
selectedHosts: [],
});
const { isPremiumTier } = useContext(AppContext);
const DATASETS: IDataSet[] = [
{
name: "uptime",
label: "Hosts online",
defaultChartType: "checkerboard",
description: (
<>
The number of hosts detected online (checking in to Fleet) during a
given hour.
<br />
<br />
Currently, only macOS, Windows, Linux, and ChromeOS are supported.
</>
),
tooltipFormatter: ({ value }: { value: number }) =>
`${value.toLocaleString()} host${value === 1 ? "" : "s"} online`,
relativeScale: true,
},
];
const getDataset = (name: string): IDataSet =>
DATASETS.find((ds) => ds.name === name) || DATASETS[0];
if (isPremiumTier) {
DATASETS.push({
name: "cve",
label: "Vulnerability exposure",
defaultChartType: "checkerboard",
description: (
<>
The number of hosts with critical vulnerabilities detected in browsers
and{" "}
<CustomLink
newTab
text="other common software "
variant="tooltip-link"
url="https://fleetdm.com/learn-more-about/vulnerability-exposure-cves"
/>
<br />
<br />
Want more control? Comprehensive vulnerability filtering is{" "}
<CustomLink
newTab
text="coming soon "
variant="tooltip-link"
url="https://github.com/fleetdm/fleet/issues/44746"
/>
</>
),
tooltipFormatter: ({ value }: { value: number }) =>
`${value.toLocaleString()} host${value === 1 ? "" : "s"}`,
theme: "red",
relativeScale: true,
});
}
const DATASET_OPTIONS: CustomOptionType[] = DATASETS.map((ds) => ({
label: ds.label,
value: ds.name,
}));
// Labels and selected hosts are team-scoped, so clear filters when the
// active fleet changes to avoid submitting stale IDs under the new scope.
useEffect(() => {
@@ -1,6 +1,6 @@
import React from "react";
import { screen } from "@testing-library/react";
import { renderWithSetup, createMockRouter } from "test/test-utils";
import { createCustomRenderer, createMockRouter } from "test/test-utils";
import createMockConfig from "__mocks__/configMock";
@@ -15,6 +15,7 @@ const renderAdvanced = (
const baseConfig = createMockConfig();
const config = {
...baseConfig,
license: { ...baseConfig.license, tier: "premium" },
features: {
...baseConfig.features,
historical_data:
@@ -23,7 +24,10 @@ const renderAdvanced = (
};
const handleSubmit =
overrides.handleSubmit ?? jest.fn().mockResolvedValue(true);
const utils = renderWithSetup(
const render = createCustomRenderer({
context: { app: { isPremiumTier: true } },
});
const utils = render(
<Advanced
appConfig={config}
handleSubmit={handleSubmit}
@@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React, { useContext, useMemo } from "react";
import SettingsSection from "pages/admin/components/SettingsSection";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
import Checkbox from "components/forms/fields/Checkbox";
@@ -6,6 +6,7 @@ import Checkbox from "components/forms/fields/Checkbox";
import Dropdown from "components/forms/fields/Dropdown";
import { getCustomDropdownOptions } from "utilities/helpers";
import { ACTIVITY_EXPIRY_WINDOW_DROPDOWN_OPTIONS } from "utilities/constants";
import { AppContext } from "context/app";
import { IAdvancedSectionProps } from "../../Advanced";
@@ -13,6 +14,8 @@ const ActivityDataRetentionSection = ({
onInputChange,
formData,
}: IAdvancedSectionProps) => {
const { isPremiumTier } = useContext(AppContext);
const {
disableQueryReports,
deleteActivities,
@@ -181,32 +184,34 @@ const ActivityDataRetentionSection = ({
</Checkbox>
)}
/>
<GitOpsModeTooltipWrapper
position="left"
renderChildren={(disableChildren) => (
<Checkbox
disabled={disableChildren}
onChange={onInputChange}
name="disableVulnerabilities"
value={disableVulnerabilities}
parseTarget
labelTooltipContent={
!disableChildren && (
<>
When enabled, Fleet stops collecting historical
<br />
vulnerability-exposure data used by the dashboard chart.{" "}
<em>
(Default: <strong>Off</strong>)
</em>
</>
)
}
>
Disable vulnerabilities
</Checkbox>
)}
/>
{isPremiumTier && (
<GitOpsModeTooltipWrapper
position="left"
renderChildren={(disableChildren) => (
<Checkbox
disabled={disableChildren}
onChange={onInputChange}
name="disableVulnerabilities"
value={disableVulnerabilities}
parseTarget
labelTooltipContent={
!disableChildren && (
<>
When enabled, Fleet stops collecting historical
<br />
vulnerability-exposure data used by the dashboard chart.{" "}
<em>
(Default: <strong>Off</strong>)
</em>
</>
)
}
>
Disable vulnerabilities
</Checkbox>
)}
/>
)}
</SettingsSection>
);
};