mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Update default fleet selected on dashboard and controls (#42688)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #40317 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] 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. ## Testing - [ ] Added/updated automated tests With the current router we have in place, we can't really test `<Link>` elements, so our ability to make useful automated tests is pretty limited here. I extracted the fleet name sorting code into an exported function and added some tests for that. - [X] QA'd all new/changed functionality manually - [X] verified that when All Fleets is selected in dropdown, navigating to Controls switches to Workstations - [X] verified that when another fleet is selected in dropdown, navigating to Controls maintains that selection - [X] verified that when a fleet is selected in dropdown, navigating to the dashboard changes to All Fleets - [X] verified that when "Unassigned" is present in the fleets dropdown, it is at the bottom - [X] verified that when using a permalink to the dashboard with a fleet selected (e.g. `?fleet_id=1`), the correct fleet shows as selected
This commit is contained in:
parent
e62bdf17b6
commit
29aa39a392
5 changed files with 80 additions and 26 deletions
1
changes/40137-update-default-fleet
Normal file
1
changes/40137-update-default-fleet
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Updated the default fleet selected when navigating to the dashboard and to Controls
|
||||
|
|
@ -151,16 +151,14 @@ const SiteTopNav = ({
|
|||
if (iconName && iconName === "logo") {
|
||||
return (
|
||||
<li className={navItemClasses} key={`nav-item-${name}`}>
|
||||
<LinkWithContext
|
||||
<Link
|
||||
className={`${navItemBaseClass}__logo-wrapper`}
|
||||
currentQueryParams={currentQueryParams}
|
||||
to={navItem.location.pathname}
|
||||
withParams={{ type: "query", names: ["fleet_id"] }}
|
||||
>
|
||||
<div className={`${navItemBaseClass}__logo`}>
|
||||
<OrgLogoIcon className="logo" src={orgLogoURL} />
|
||||
</div>
|
||||
</LinkWithContext>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
48
frontend/context/app.tests.ts
Normal file
48
frontend/context/app.tests.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import createMockUser from "__mocks__/userMock";
|
||||
|
||||
import { sortAvailableTeams } from "./app";
|
||||
|
||||
describe("sortAvailableTeams", () => {
|
||||
it("places Unassigned last for global team users", () => {
|
||||
const teams = [
|
||||
{ id: 0, name: "Unassigned" },
|
||||
{ id: 2, name: "Zebra" },
|
||||
{ id: 1, name: "Alpha" },
|
||||
{ id: -1, name: "All fleets" },
|
||||
];
|
||||
const result = sortAvailableTeams(teams, createMockUser());
|
||||
expect(result.map((t) => t.name)).toEqual([
|
||||
"All fleets",
|
||||
"Alpha",
|
||||
"Zebra",
|
||||
"Unassigned",
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not include All fleets or Unassigned for non-global users", () => {
|
||||
const teams = [
|
||||
{ id: 0, name: "Unassigned" },
|
||||
{ id: 2, name: "Zebra" },
|
||||
{ id: 1, name: "Alpha" },
|
||||
{ id: -1, name: "All fleets" },
|
||||
];
|
||||
const result = sortAvailableTeams(
|
||||
teams,
|
||||
createMockUser({ global_role: null })
|
||||
);
|
||||
expect(result.map((t) => t.name)).toEqual(["Alpha", "Zebra"]);
|
||||
});
|
||||
|
||||
it("sorts named teams alphabetically (case-insensitive)", () => {
|
||||
const teams = [
|
||||
{ id: 3, name: "charlie" },
|
||||
{ id: 1, name: "Alpha" },
|
||||
{ id: 2, name: "Bravo" },
|
||||
];
|
||||
const result = sortAvailableTeams(
|
||||
teams,
|
||||
createMockUser({ global_role: null })
|
||||
);
|
||||
expect(result.map((t) => t.name)).toEqual(["Alpha", "Bravo", "charlie"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -339,6 +339,24 @@ const setPermissions = (
|
|||
};
|
||||
};
|
||||
|
||||
export const sortAvailableTeams = (
|
||||
availableFleets: ITeamSummary[],
|
||||
user: IUser | null
|
||||
): ITeamSummary[] => {
|
||||
const sortedFleets = [...availableFleets]
|
||||
.sort((a, b) => sort.caseInsensitiveAsc(a.name, b.name))
|
||||
.filter(
|
||||
(t) =>
|
||||
t.name !== APP_CONTEXT_ALL_TEAMS_SUMMARY.name &&
|
||||
t.name !== APP_CONTEXT_NO_TEAM_SUMMARY.name
|
||||
);
|
||||
if (user && permissions.isOnGlobalTeam(user)) {
|
||||
sortedFleets.unshift(APP_CONTEXT_ALL_TEAMS_SUMMARY);
|
||||
sortedFleets.push(APP_CONTEXT_NO_TEAM_SUMMARY);
|
||||
}
|
||||
return sortedFleets;
|
||||
};
|
||||
|
||||
const reducer = (state: InitialStateType, action: IAction) => {
|
||||
switch (action.type) {
|
||||
case ACTIONS.SET_USER_SETTINGS: {
|
||||
|
|
@ -349,27 +367,9 @@ const reducer = (state: InitialStateType, action: IAction) => {
|
|||
};
|
||||
}
|
||||
case ACTIONS.SET_AVAILABLE_TEAMS: {
|
||||
const { user, availableTeams } = action;
|
||||
|
||||
let sortedTeams = availableTeams.sort(
|
||||
(a: ITeamSummary, b: ITeamSummary) =>
|
||||
sort.caseInsensitiveAsc(a.name, b.name)
|
||||
);
|
||||
sortedTeams = sortedTeams.filter(
|
||||
(t) =>
|
||||
t.name !== APP_CONTEXT_ALL_TEAMS_SUMMARY.name &&
|
||||
t.name !== APP_CONTEXT_NO_TEAM_SUMMARY.name
|
||||
);
|
||||
if (user && permissions.isOnGlobalTeam(user)) {
|
||||
sortedTeams.unshift(
|
||||
APP_CONTEXT_ALL_TEAMS_SUMMARY,
|
||||
APP_CONTEXT_NO_TEAM_SUMMARY
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
availableTeams: sortedTeams,
|
||||
availableTeams: sortAvailableTeams(action.availableTeams, action.user),
|
||||
};
|
||||
}
|
||||
case ACTIONS.SET_CURRENT_USER: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useContext, useEffect, useMemo } from "react";
|
||||
import { InjectedRouter } from "react-router";
|
||||
import { findLastIndex, trimStart } from "lodash";
|
||||
import { findLastIndex, sortBy, trimStart } from "lodash";
|
||||
|
||||
import { AppContext } from "context/app";
|
||||
import { TableContext } from "context/table";
|
||||
|
|
@ -221,8 +221,15 @@ const getDefaultTeam = ({
|
|||
defaultTeam = userTeams.find((t) => t.id === APP_CONTEXT_ALL_TEAMS_ID);
|
||||
}
|
||||
if (!defaultTeam && includeNoTeam) {
|
||||
// default to No team when "All teams" not included and no team is included
|
||||
defaultTeam = userTeams.find((t) => t.id === APP_CONTEXT_NO_TEAM_ID);
|
||||
// prefer the real fleet with the lowest ID over "Unassigned"
|
||||
const realFleets = userTeams.filter(
|
||||
(t) => t.id > APP_CONTEXT_NO_TEAM_ID
|
||||
);
|
||||
if (realFleets.length > 0) {
|
||||
defaultTeam = sortBy(realFleets, (t) => t.id)[0];
|
||||
} else {
|
||||
defaultTeam = userTeams.find((t) => t.id === APP_CONTEXT_NO_TEAM_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue