<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** For #44077 # 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 - [X] Added/updated automated tests - [ ] QA'd all new/changed functionality manually ### Manual QA — Chart disabling (frontend) Setup notes: - Test as a global admin and (where indicated) a fleet admin - Need a Premium deployment to test per-fleet behavior - Have at least one fleet (e.g. "Engineering") for fleet-scoped checks - Be able to flip `appConfig.features.historical_data.{uptime,vulnerabilities}` and the equivalent per-fleet keys (via API or settings UI) #### Advanced settings page — `/settings/organization/advanced` - [ ] "Activity & data retention" subheading appears on the page - [ ] Section contains a "Disable hosts online" checkbox - [ ] Section contains a "Disable vulnerabilities" checkbox - [ ] Section is visible on Free tier - [ ] Section is visible on Premium tier - [ ] With `historical_data.uptime = true` in appConfig, "Disable hosts online" loads **unchecked** - [ ] With `historical_data.uptime = false` in appConfig, "Disable hosts online" loads **checked** - [ ] With `historical_data.vulnerabilities = false` in appConfig, "Disable vulnerabilities" loads **checked** - [ ] Saving with "Disable hosts online" checked sends `features.historical_data.uptime = false` in the `PATCH /api/v1/fleet/config` payload (verify in network tab) - [ ] Saving with "Disable vulnerabilities" unchecked sends `features.historical_data.vulnerabilities = true` #### Per-fleet settings page (Premium only) - [ ] Fleet settings page shows an "Activity & data retention" section - [ ] Section contains "Disable hosts online" and "Disable vulnerabilities" checkboxes scoped to the active fleet - [ ] On **Free** tier, the per-fleet TeamSettings page is not reachable (and therefore the section is not visible) - [ ] On **Premium** tier, the section is visible to fleet admins - [ ] Saving with "Disable vulnerabilities" checked sends `features.historical_data.vulnerabilities = false` in `PATCH /api/v1/fleet/teams/{id}` (verify in network tab) #### Per-fleet checkbox locks when global is disabled - [ ] With global `historical_data.uptime = false`, fleet's "Disable hosts online" is **disabled / non-interactive** - [ ] Hovering the locked fleet checkbox shows the tooltip "Disabled globally" - [ ] **Stored value preserved across lockout**: - [ ] Set fleet `historical_data.uptime = false` (saved) - [ ] Then set global to `false` — fleet checkbox renders **checked but disabled** - [ ] Flip global back to `true` — fleet checkbox renders **checked and interactive** (fleet's `false` survived the round trip) #### Confirmation modal on Save (both global and per-fleet) - [ ] Loading with `uptime = true`, then checking "Disable hosts online" and clicking Save → modal opens listing **"Hosts online"** - [ ] No PATCH is issued until the user confirms - [ ] No-op save (page loaded with `uptime = false`, no changes, click Save) → modal does **not** open - [ ] Re-enable (page loaded with `uptime = false`, uncheck "Disable hosts online", Save) → modal does **not** open; PATCH is issued - [ ] Disabling **both** datasets at once → modal lists **both** "Hosts online" and "Vulnerabilities" - [ ] Modal "Save and disable" button issues the PATCH and modal closes once it resolves successfully - [ ] Modal Cancel closes the modal, no PATCH is issued, checkboxes remain checked (form state unchanged, ready to retry or revert) #### Confirmation modal copy by scope - [ ] Modal opened from Advanced page → body describes the change as affecting the **entire Fleet deployment** - [ ] Modal opened from fleet "Engineering" settings → body **references "Engineering" by name** and describes the change as affecting **only that fleet** #### GitOps mode lockout - [ ] With GitOps mode enabled, on `/settings/organization/advanced`: - [ ] "Disable hosts online" is disabled with the GitOps tooltip - [ ] "Disable vulnerabilities" is disabled with the GitOps tooltip - [ ] With GitOps mode enabled, on a fleet's settings page: - [ ] "Disable hosts online" is disabled with the GitOps tooltip - [ ] "Disable vulnerabilities" is disabled with the GitOps tooltip - [ ] When **both** GitOps mode and "Disabled globally" would apply, the fleet checkbox shows the **GitOps tooltip** (GitOps wins) #### Dashboard chart card — empty state - [ ] With `appConfig.features.historical_data.uptime = false`, viewing dashboard in "All teams" with the **uptime** dataset selected → visualization area shows the **"Data collection disabled"** empty state - [ ] Dataset selector, time range selector, and filter gear remain visible **and functional** while the empty state is shown - [ ] With global `vulnerabilities = true` but the active fleet's `historical_data.vulnerabilities = false`, viewing the dashboard scoped to that fleet with a vulnerabilities-driven dataset selected → empty state shown - [ ] With both global and fleet sub-keys `true` (or fleet sub-key absent in All-teams scope) → visualization renders normally; empty state does **not** appear - [ ] Switching from a disabled dataset (A) to an enabled dataset (B) replaces the empty state with B's visualization - [ ] A dataset whose internal name has no `DATASET_CONFIG_KEY` mapping renders normally (no empty state — implicitly enabled) #### Empty-state content - [ ] When the empty state is rendered for the "Hosts online" dataset, "Hosts online" appears as the dataset name - [ ] Link text describes re-enabling collection (e.g. "Manage data collection in Advanced settings") - [ ] Empty state includes a link to `/settings/organization/advanced` when viewing All Fleets as a global admin - [ ] Empty state includes a link to the correct Fleet settings page when viewing a specific fleet as a global or team admin - [ ] Empty state includes no link or call to action when viewed as a non-admin #### Activity feed rendering - [ ] `enabled_historical_dataset` with `dataset = "uptime"`, `fleet_id = null` → renders **"Enabled data collection for Hosts online."** ("Hosts online" bolded) - [ ] `disabled_historical_dataset` with `dataset = "vulnerabilities"`, `fleet_id = null` → renders **"Disabled data collection for Vulnerabilities."** - [ ] `enabled_historical_dataset` with `dataset = "uptime"`, `fleet_id = 7`, `fleet_name = "Engineering"` → renders **"Enabled data collection for Hosts online for the Engineering fleet."** - [ ] `disabled_historical_dataset` with `dataset = "vulnerabilities"`, `fleet_id = 7`, `fleet_name = "Engineering"` → renders **"Disabled data collection for Vulnerabilities for the Engineering fleet."** - [ ] `disabled_historical_dataset` with an **unknown** `dataset = "policy_compliance"` → does **not** throw; renders **"Disabled data collection for Policy compliance."** (raw key sentence-cased — `_` → space, first letter capitalized) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Org- and team-level controls to enable/disable historical data collection for "Hosts online" and "Vulnerabilities." * Updated chart label: "Hosts active" → "Hosts online." * Confirmation modal and a disabled-state UI when data collection is turned off; per-team confirmation flow to prevent accidental data loss. * Activity feed now records enable/disable actions for historical dataset collection with readable dataset labels and scope. * **Tests** * Added comprehensive tests for controls, confirmation flows, disabled states, chart headings, and activity items. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import {
|
|
isHistoricalDataEnabled,
|
|
IHistoricalDataSettings,
|
|
HISTORICAL_DATA_CONFIG_KEYS,
|
|
DATASET_CONFIG_KEY,
|
|
DATASET_LABEL,
|
|
} from "./charts";
|
|
|
|
describe("isHistoricalDataEnabled", () => {
|
|
const enabled: IHistoricalDataSettings = {
|
|
uptime: true,
|
|
vulnerabilities: true,
|
|
};
|
|
const uptimeOff: IHistoricalDataSettings = {
|
|
uptime: false,
|
|
vulnerabilities: true,
|
|
};
|
|
const vulnsOff: IHistoricalDataSettings = {
|
|
uptime: true,
|
|
vulnerabilities: false,
|
|
};
|
|
|
|
it("returns true when both global and fleet are undefined", () => {
|
|
expect(isHistoricalDataEnabled(undefined, undefined, "uptime")).toBe(true);
|
|
expect(
|
|
isHistoricalDataEnabled(undefined, undefined, "vulnerabilities")
|
|
).toBe(true);
|
|
});
|
|
|
|
it("returns true when global is enabled and fleet is undefined", () => {
|
|
expect(isHistoricalDataEnabled(enabled, undefined, "uptime")).toBe(true);
|
|
});
|
|
|
|
it("returns true when fleet is enabled and global is undefined", () => {
|
|
expect(isHistoricalDataEnabled(undefined, enabled, "uptime")).toBe(true);
|
|
});
|
|
|
|
it("returns true when both global and fleet are explicitly enabled", () => {
|
|
expect(isHistoricalDataEnabled(enabled, enabled, "uptime")).toBe(true);
|
|
expect(isHistoricalDataEnabled(enabled, enabled, "vulnerabilities")).toBe(
|
|
true
|
|
);
|
|
});
|
|
|
|
it("returns false when only the global side is disabled", () => {
|
|
expect(isHistoricalDataEnabled(uptimeOff, enabled, "uptime")).toBe(false);
|
|
});
|
|
|
|
it("returns false when only the fleet side is disabled", () => {
|
|
expect(isHistoricalDataEnabled(enabled, uptimeOff, "uptime")).toBe(false);
|
|
});
|
|
|
|
it("returns false when both sides are disabled", () => {
|
|
expect(isHistoricalDataEnabled(uptimeOff, uptimeOff, "uptime")).toBe(false);
|
|
});
|
|
|
|
it("returns false when global is disabled and fleet is undefined", () => {
|
|
expect(isHistoricalDataEnabled(uptimeOff, undefined, "uptime")).toBe(false);
|
|
});
|
|
|
|
it("returns false when fleet is disabled and global is undefined", () => {
|
|
expect(isHistoricalDataEnabled(undefined, uptimeOff, "uptime")).toBe(false);
|
|
});
|
|
|
|
it("does not let an unrelated-key disable bleed across keys", () => {
|
|
expect(isHistoricalDataEnabled(vulnsOff, undefined, "uptime")).toBe(true);
|
|
expect(
|
|
isHistoricalDataEnabled(vulnsOff, undefined, "vulnerabilities")
|
|
).toBe(false);
|
|
});
|
|
|
|
it("evaluates per-key independently", () => {
|
|
expect(isHistoricalDataEnabled(vulnsOff, enabled, "uptime")).toBe(true);
|
|
expect(isHistoricalDataEnabled(vulnsOff, enabled, "vulnerabilities")).toBe(
|
|
false
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("HISTORICAL_DATA_CONFIG_KEYS", () => {
|
|
it("includes uptime and vulnerabilities", () => {
|
|
expect(HISTORICAL_DATA_CONFIG_KEYS).toEqual(["uptime", "vulnerabilities"]);
|
|
});
|
|
});
|
|
|
|
describe("DATASET_CONFIG_KEY", () => {
|
|
it("maps the uptime internal name to the uptime config key", () => {
|
|
expect(DATASET_CONFIG_KEY.uptime).toBe("uptime");
|
|
});
|
|
|
|
it("maps the cve internal name to the vulnerabilities config key", () => {
|
|
expect(DATASET_CONFIG_KEY.cve).toBe("vulnerabilities");
|
|
});
|
|
|
|
it("returns undefined for unknown internal names", () => {
|
|
expect(DATASET_CONFIG_KEY.unknown).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("DATASET_LABEL", () => {
|
|
it("provides a human-readable label for each config key", () => {
|
|
expect(DATASET_LABEL.uptime).toBe("Hosts online");
|
|
expect(DATASET_LABEL.vulnerabilities).toBe("Vulnerability exposure");
|
|
});
|
|
});
|