UI – Clarify activity items for JIT provisioned SSO user initial logins (#15192)

## Addresses #14345 

![Screenshot 2023-11-17 at 11 43
59 AM](https://github.com/fleetdm/fleet/assets/61553566/b97634dc-53c1-4ddd-910e-8dd7112c2623)

- [x] Changes file added for user-visible changes in `changes/`
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
Jacob Shandling
2023-11-20 10:29:36 -08:00
committed by GitHub
co-authored by Jacob Shandling
parent 9984bc6894
commit 32706a732b
4 changed files with 204 additions and 13 deletions
@@ -0,0 +1,2 @@
- Update activity feed to elegantly communicate when a JIT-provisioned user logs in for the first
time, thereby creating their account.
+1
View File
@@ -80,6 +80,7 @@ export interface IActivityDetails {
specs?: IQuery[] | IPolicy[];
global?: boolean;
public_ip?: string;
user_id?: number;
user_email?: string;
email?: string;
role?: UserRole;
@@ -1,5 +1,5 @@
import React from "react";
import { render, screen, getDefaultNormalizer } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import createMockActivity from "__mocks__/activityMock";
import createMockQuery from "__mocks__/queryMock";
@@ -232,6 +232,10 @@ describe("Activity Feed", () => {
).toBeInTheDocument();
});
// // // // // // // // // // // //
// created_user tests
// // // // // //// // // // // //
it("renders a created_user type activity globally", () => {
const activity = createMockActivity({
type: ActivityType.UserCreated,
@@ -245,6 +249,24 @@ describe("Activity Feed", () => {
expect(screen.getByText("newuser@example.com")).toBeInTheDocument();
});
it("correctly renders a created_user type activity for a premium SSO user created by JIT provisioning", () => {
const activity = createMockActivity({
actor_full_name: "Jit Sso",
actor_id: 3,
type: ActivityType.UserCreated,
details: {
user_id: 3,
},
});
render(<ActivityItem activity={activity} isPremiumTier />);
// If actor_id is the same as user_id:
// "<name> activated their account."
expect(screen.getByText("Jit Sso")).toBeInTheDocument();
expect(screen.getByText(/activated their account\./)).toBeInTheDocument();
});
// // // // // //// // // // // //
it("renders a deleted_user type activity globally", () => {
const activity = createMockActivity({
type: ActivityType.UserDeleted,
@@ -258,6 +280,10 @@ describe("Activity Feed", () => {
expect(screen.getByText("newuser@example.com")).toBeInTheDocument();
});
// // // // // // // // // // // //
// changed_user_global_role tests
// // // // // //// // // // // //
it("renders a changed_user_global_role type activity globally for premium users", () => {
const activity = createMockActivity({
type: ActivityType.UserChangedGlobalRole,
@@ -287,7 +313,78 @@ describe("Activity Feed", () => {
expect(forAllTeams).toBeNull();
});
it("renders a changed_user_team_role type activity globally", () => {
it("correctly renders a changed_user_global_role type activity for a premium SSO user created by JIT provisioning", () => {
const activity = createMockActivity({
actor_id: 3,
type: ActivityType.UserChangedGlobalRole,
details: {
user_id: 3,
user_email: "jit@sso.com",
role: "observer",
},
});
render(<ActivityItem activity={activity} isPremiumTier />);
// If actor_id is the same as user_id:
// "<user_email> was assigned the <role> for all teams."
expect(screen.getByText("jit@sso.com")).toBeInTheDocument();
expect(screen.getByText(/was assigned the/)).toBeInTheDocument();
expect(screen.getByText("observer")).toBeInTheDocument();
expect(screen.getByText(/role for all teams./)).toBeInTheDocument();
});
it("correctly renders a changed_user_global_role type activity when changing an existing user's global role, premium", () => {
const activity = createMockActivity({
actor_id: 1,
actor_full_name: "Ally Admin",
type: ActivityType.UserChangedGlobalRole,
details: {
user_id: 3,
user_email: "user@example.com",
role: "maintainer",
},
});
render(<ActivityItem activity={activity} isPremiumTier />);
// If actor_id is different from user_id on premium:
// "<actor_full_name> changed <user_email> to <role> for all teams."
expect(screen.getByText("Ally Admin")).toBeInTheDocument();
expect(screen.getByText(/changed/)).toBeInTheDocument();
expect(screen.getByText("user@example.com")).toBeInTheDocument();
expect(screen.getByText(/to/)).toBeInTheDocument();
expect(screen.getByText("maintainer")).toBeInTheDocument();
expect(screen.getByText(/for all teams/)).toBeInTheDocument();
});
it("correctly renders a changed_user_global_role type activity when changing an existing user's global role, free", () => {
const activity = createMockActivity({
actor_id: 1,
actor_full_name: "Ally Admin",
type: ActivityType.UserChangedGlobalRole,
details: {
user_id: 3,
user_email: "user@example.com",
role: "maintainer",
},
});
render(<ActivityItem activity={activity} isPremiumTier={false} />);
// If actor_id is different from user_id on free:
// "<actor_full_name> changed <user_email> to <role>."
expect(screen.getByText("Ally Admin")).toBeInTheDocument();
expect(screen.getByText("changed", { exact: false })).toBeInTheDocument();
expect(screen.getByText("user@example.com")).toBeInTheDocument();
expect(screen.getByText("to", { exact: false })).toBeInTheDocument();
expect(screen.getByText("maintainer")).toBeInTheDocument();
const forAllTeams = screen.queryByText("for all teams.");
expect(forAllTeams).toBeNull();
});
// // // // // // // // // // // //
// changed_user_team_role tests
// // // // // //// // // // // //
it("renders a changed_user_team_role type activity", () => {
const activity = createMockActivity({
type: ActivityType.UserChangedTeamRole,
details: {
@@ -304,6 +401,63 @@ describe("Activity Feed", () => {
expect(screen.getByText("Test Team")).toBeInTheDocument();
});
it("correctly renders a changed_user_team_role type activity when a new SSO team user is created via JIT provisioning", () => {
const activity = createMockActivity({
actor_id: 1,
actor_full_name: "Ally Admin",
type: ActivityType.UserChangedTeamRole,
details: {
user_id: 1,
user_email: "jit@sso.com",
role: "maintainer",
team_name: "Test Team",
},
});
render(<ActivityItem activity={activity} isPremiumTier />);
// If actor_id is the same as user_id:
// "<user_email> was assigned the <role> role for the <team_name> team."
expect(screen.getByText("jit@sso.com")).toBeInTheDocument();
expect(screen.getByText(/was assigned the/)).toBeInTheDocument();
expect(screen.getByText("maintainer")).toBeInTheDocument();
expect(screen.getByText(/role for the/)).toBeInTheDocument();
expect(screen.getByText(/Test Team/)).toBeInTheDocument();
expect(screen.getByText(/team\./)).toBeInTheDocument();
expect(screen.queryByText("Ally Admin")).toBeNull();
const forAllTeams = screen.queryByText("for all teams.");
expect(forAllTeams).toBeNull();
});
it("correctly renders a changed_user_team_role type activity when changing an existing user's team role", () => {
const activity = createMockActivity({
actor_id: 1,
actor_full_name: "Ally Admin",
type: ActivityType.UserChangedTeamRole,
details: {
user_id: 3,
user_email: "user@example.com",
role: "maintainer",
team_name: "Test Team",
},
});
render(<ActivityItem activity={activity} isPremiumTier />);
// If actor_id is different from user_id:
// "<actor_full_name> changed <user_email> to <role> for the <team_name> team."
expect(screen.getByText("Ally Admin")).toBeInTheDocument();
expect(screen.getByText(/changed/)).toBeInTheDocument();
expect(screen.getByText("user@example.com")).toBeInTheDocument();
expect(screen.getByText(/to/)).toBeInTheDocument();
expect(screen.getByText("maintainer")).toBeInTheDocument();
expect(screen.getByText(/for the/)).toBeInTheDocument();
expect(screen.getByText(/Test Team/)).toBeInTheDocument();
expect(screen.getByText(/team\./)).toBeInTheDocument();
expect(screen.queryByText("for all teams.")).toBeNull();
});
// // // // // // // // // // // //
it("renders a deleted_user_team_role type activity globally", () => {
const activity = createMockActivity({
type: ActivityType.UserDeletedTeamRole,
@@ -173,7 +173,9 @@ const TAGGED_TEMPLATES = {
);
},
userCreated: (activity: IActivity) => {
return (
return activity.actor_id === activity.details?.user_id ? (
<>activated their account.</>
) : (
<>
created a user <b> {activity.details?.user_email}</b>.
</>
@@ -187,10 +189,22 @@ const TAGGED_TEMPLATES = {
);
},
userChangedGlobalRole: (activity: IActivity, isPremiumTier: boolean) => {
const { actor_id } = activity;
const { user_id, user_email, role } = activity.details || {};
if (actor_id === user_id) {
// this is the case when SSO user is crated via JIT provisioning
// should only be possible for premium tier, but check anyway
return (
<>
was assigned the <b>{role}</b> role{isPremiumTier && " for all teams"}
.
</>
);
}
return (
<>
changed <b>{activity.details?.user_email}</b> to{" "}
<b>{activity.details?.role}</b>
changed <b>{user_email}</b> to <b>{activity.details?.role}</b>
{isPremiumTier && " for all teams"}.
</>
);
@@ -205,11 +219,22 @@ const TAGGED_TEMPLATES = {
);
},
userChangedTeamRole: (activity: IActivity) => {
const { actor_id } = activity;
const { user_id, user_email, role, team_name } = activity.details || {};
const varText =
actor_id === user_id ? (
<>
was assigned the <b>{role}</b> role
</>
) : (
<>
changed <b>{user_email}</b> to <b>{role}</b>
</>
);
return (
<>
changed <b>{activity.details?.user_email}</b> to{" "}
<b>{activity.details?.role}</b> for the{" "}
<b>{activity.details?.team_name}</b> team.
{varText} for the <b>{team_name}</b> team.
</>
);
},
@@ -751,6 +776,19 @@ const ActivityItem = ({
const indicatePremiumFeature =
isSandboxMode && PREMIUM_ACTIVITIES.has(activity.type);
const renderActivityPrefix = () => {
if (activity.type === ActivityType.UserLoggedIn) {
return <b>{activity.actor_email} </b>;
}
if (
(activity.type === ActivityType.UserChangedGlobalRole ||
activity.type === ActivityType.UserChangedTeamRole) &&
activity.actor_id === activity.details?.user_id
) {
return <b>{activity.details?.user_email} </b>;
}
return <b>{activity.actor_full_name} </b>;
};
return (
<div className={baseClass}>
<Avatar
@@ -763,11 +801,7 @@ const ActivityItem = ({
<div className={"activity-details"}>
{indicatePremiumFeature && <PremiumFeatureIconWithTooltip />}
<span className={`${baseClass}__details-topline`}>
{activity.type === ActivityType.UserLoggedIn ? (
<b>{activity.actor_email} </b>
) : (
<b>{activity.actor_full_name} </b>
)}
{renderActivityPrefix()}
{getDetail(activity, isPremiumTier, onDetailsClick)}
</span>
<br />