Skip to content

Commit

Permalink
Add notice to web UI that users arent equal to MAU
Browse files Browse the repository at this point in the history
This adds a dismissible notice to the Users page for usage based billing
users that notifies them that the user count here isn't an accurate
reflection of MAU
  • Loading branch information
avatus committed Sep 18, 2024
1 parent 0d80b7d commit 70439f7
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 3 deletions.
10 changes: 10 additions & 0 deletions web/packages/teleport/src/Users/Users.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ export const Loaded = () => {
);
};

export const UsersNotEqualMauNotice = () => {
return (
<MemoryRouter>
<Users {...sample} showMauInfo={true} />
</MemoryRouter>
);
};

export const Failed = () => {
const attempt = {
isProcessing: false,
Expand Down Expand Up @@ -139,4 +147,6 @@ const sample = {
InviteCollaborators: null,
onEmailPasswordResetClose: () => null,
EmailPasswordReset: null,
showMauInfo: false,
onDismissUsersMauNotice: () => null,
};
55 changes: 54 additions & 1 deletion web/packages/teleport/src/Users/Users.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import React from 'react';
import { MemoryRouter } from 'react-router';
import { render, screen } from 'design/utils/testing';
import { render, screen, userEvent } from 'design/utils/testing';

import { ContextProvider } from 'teleport';
import { createTeleportContext } from 'teleport/mocks/contexts';
Expand Down Expand Up @@ -57,6 +57,8 @@ describe('invite collaborators integration', () => {
inviteCollaboratorsOpen: false,
onEmailPasswordResetClose: () => undefined,
EmailPasswordReset: null,
showMauInfo: false,
onDismissUsersMauNotice: () => null,
};
});

Expand Down Expand Up @@ -105,6 +107,55 @@ describe('invite collaborators integration', () => {
});
});

test('Users not equal to MAU Notice', async () => {
const ctx = createTeleportContext();
let props: State;

props = {
attempt: {
message: 'success',
isSuccess: true,
isProcessing: false,
isFailed: false,
},
users: [],
fetchRoles: async () => [],
operation: { type: 'invite-collaborators' },
onStartCreate: () => undefined,
onStartDelete: () => undefined,
onStartEdit: () => undefined,
onStartReset: () => undefined,
onStartInviteCollaborators: () => undefined,
onClose: () => undefined,
onDelete: () => undefined,
onCreate: () => undefined,
onUpdate: () => undefined,
onReset: () => undefined,
onInviteCollaboratorsClose: () => undefined,
InviteCollaborators: null,
inviteCollaboratorsOpen: false,
onEmailPasswordResetClose: () => undefined,
EmailPasswordReset: null,
showMauInfo: true,
onDismissUsersMauNotice: jest.fn(),
};

const user = userEvent.setup();

render(
<MemoryRouter>
<ContextProvider ctx={ctx}>
<Users {...props} />
</ContextProvider>
</MemoryRouter>
);

expect(screen.getByTestId('users-not-mau-alert')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'Dismiss' }));
expect(props.onDismissUsersMauNotice).toHaveBeenCalled();
expect(screen.queryByTestId('users-not-mau-alert')).not.toBeInTheDocument();
});

describe('email password reset integration', () => {
const ctx = createTeleportContext();

Expand Down Expand Up @@ -139,6 +190,8 @@ describe('email password reset integration', () => {
inviteCollaboratorsOpen: false,
onEmailPasswordResetClose: () => undefined,
EmailPasswordReset: null,
showMauInfo: false,
onDismissUsersMauNotice: () => null,
};
});

Expand Down
38 changes: 37 additions & 1 deletion web/packages/teleport/src/Users/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

import React from 'react';
import { Indicator, Box, Alert, Button } from 'design';
import { Indicator, Box, Alert, Button, Link } from 'design';

import {
FeatureBox,
Expand Down Expand Up @@ -46,6 +46,8 @@ export function Users(props: State) {
onStartDelete,
onStartEdit,
onStartReset,
showMauInfo,
onDismissUsersMauNotice,
onClose,
onCreate,
onUpdate,
Expand Down Expand Up @@ -98,6 +100,40 @@ export function Users(props: State) {
<Indicator />
</Box>
)}
{showMauInfo && (
<Alert
data-testid="users-not-mau-alert"
dismissible
onDismiss={onDismissUsersMauNotice}
kind="info"
css={`
a.external-link {
color: ${({ theme }) => theme.colors.buttons.link.default};
}
`}
>
The users displayed here are not an accurate reflection of Monthly
Active Users (MAU). For example, users who log in through Single
Sign-On (SSO) providers such as Okta may only appear here temporarily
and disappear once their sessions expire. For more information, read
our documentation on{' '}
<Link
target="_blank"
href="https://goteleport.com/docs/usage-billing/#monthly-active-users"
className="external-link"
>
MAU
</Link>{' '}
and{' '}
<Link
href="https://goteleport.com/docs/reference/user-types/"
className="external-link"
>
User Types
</Link>
.
</Alert>
)}
{attempt.isFailed && <Alert kind="danger" children={attempt.message} />}
{attempt.isSuccess && (
<UserList
Expand Down
15 changes: 15 additions & 0 deletions web/packages/teleport/src/Users/useUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { useAttempt } from 'shared/hooks';

import { ExcludeUserField, User } from 'teleport/services/user';
import useTeleport from 'teleport/useTeleport';
import cfg from 'teleport/config';
import { storageService } from 'teleport/services/storageService';
import auth from 'teleport/services/auth/auth';

export default function useUsers({
Expand Down Expand Up @@ -119,10 +121,21 @@ export default function useUsers({
return items.map(r => r.name);
}

function onDismissUsersMauNotice() {
storageService.setUsersMAUAcknowledged();
}

useEffect(() => {
attemptActions.do(() => ctx.userService.fetchUsers().then(setUsers));
}, []);

// if the cluster has billing enabled, and usageBasedBilling, and they haven't acknowledged
// the info yet
const showMauInfo =
ctx.getFeatureFlags().billing &&
cfg.isUsageBasedBilling &&
!storageService.getUsersMauAcknowledged();

return {
attempt,
users,
Expand All @@ -143,6 +156,8 @@ export default function useUsers({
inviteCollaboratorsOpen,
onEmailPasswordResetClose,
EmailPasswordReset,
showMauInfo,
onDismissUsersMauNotice,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const KEEP_LOCALSTORAGE_KEYS_ON_LOGOUT = [
KeysEnum.USER_PREFERENCES,
KeysEnum.RECOMMEND_FEATURE,
KeysEnum.LICENSE_ACKNOWLEDGED,
KeysEnum.USERS_NOT_EQUAL_TO_MAU_ACKNOWLEDGED,
];

export const storageService = {
Expand Down Expand Up @@ -207,6 +208,21 @@ export const storageService = {
window.localStorage.setItem(KeysEnum.LICENSE_ACKNOWLEDGED, 'true');
},

getUsersMauAcknowledged(): boolean {
return (
window.localStorage.getItem(
KeysEnum.USERS_NOT_EQUAL_TO_MAU_ACKNOWLEDGED
) === 'true'
);
},

setUsersMAUAcknowledged() {
window.localStorage.setItem(
KeysEnum.USERS_NOT_EQUAL_TO_MAU_ACKNOWLEDGED,
'true'
);
},

broadcast(messageType, messageBody) {
window.localStorage.setItem(messageType, messageBody);
window.localStorage.removeItem(messageType);
Expand Down
3 changes: 2 additions & 1 deletion web/packages/teleport/src/services/storageService/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export const KeysEnum = {
EXTERNAL_AUDIT_STORAGE_CTA_DISABLED:
'grv_teleport_external_audit_storage_disabled',
LICENSE_ACKNOWLEDGED: 'grv_teleport_license_acknowledged',

USERS_NOT_EQUAL_TO_MAU_ACKNOWLEDGED:
'grv_users_not_equal_to_mau_acknowledged',
LOCAL_NOTIFICATION_STATES: 'grv_teleport_notification_states',
};

Expand Down

0 comments on commit 70439f7

Please sign in to comment.