Skip to content

Commit

Permalink
Merge pull request #284 from jaredh159/optional-suspension-indication
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredh159 authored Jan 2, 2024
2 parents 7514391 + ba75f03 commit 3c4872a
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 45 deletions.
21 changes: 18 additions & 3 deletions dash/app/cypress/e2e/activity.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,21 @@ describe(`activity screens`, () => {
it(`activity feed displays items in correct order`, () => {
cy.interceptPql(`CombinedUsersActivityFeed`, [
{
showSuspensionActivity: false,
userName: `Bob`,
numDeleted: 0,
items: [mock.screenshotActivityItem({ id: `screenshot-123` })],
items: [
mock.screenshotActivityItem({ id: `screenshot-123`, duringSuspension: true }),
],
},
{
showSuspensionActivity: true,
userName: `Suzy`,
numDeleted: 1,
items: [
mock.screenshotActivityItem({ id: `screenshot-234` }),
mock.screenshotActivityItem({ id: `screenshot-345` }),
mock.screenshotActivityItem({ id: `screenshot-456` }),
mock.screenshotActivityItem({ id: `screenshot-345`, duringSuspension: true }),
mock.screenshotActivityItem({ id: `screenshot-456`, duringSuspension: true }),
mock.keystrokeActivityItem({
id: `ks-1`,
ids: [`ks-1`, `ks-2`, `ks-3`], // <-- aggregated ids
Expand All @@ -66,6 +70,13 @@ describe(`activity screens`, () => {
cy.testId(`page-heading`).first().should(`have.text`, `Suzy’s Activity`);
cy.testId(`page-heading`).last().should(`have.text`, `Bob’s Activity`);

cy.testId(`single-user-sub-feed`).first().contains(`During filter suspension`);
// bob has `showSuspensionActivity: false`, so his feed should not have any highlighted suspension items
cy.testId(`single-user-sub-feed`)
.last()
.contains(`During filter suspension`)
.should(`not.exist`);

cy.contains(`Approve all child activity`).click();

cy.wait(`@DeleteActivityItems_v2`)
Expand All @@ -92,11 +103,13 @@ describe(`activity screens`, () => {
);
cy.interceptPql(`CombinedUsersActivityFeed`, [
{
showSuspensionActivity: true,
userName: `suzy`,
numDeleted: 0,
items: suzysItems,
},
{
showSuspensionActivity: true,
userName: `jimmy`,
numDeleted: 0,
items: Array.from({ length: 130 }, (_, i) =>
Expand All @@ -117,11 +130,13 @@ describe(`activity screens`, () => {

cy.interceptPql(`CombinedUsersActivityFeed`, [
{
showSuspensionActivity: true,
userName: `suzy`,
numDeleted: 0,
items: suzysItems,
},
{
showSuspensionActivity: true,
userName: `jimmy`,
numDeleted: 0,
// vv -- only 30 remain
Expand Down
1 change: 1 addition & 0 deletions dash/app/cypress/e2e/onboarding.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe(`dashboard onboarding nudges`, () => {
screenshotsEnabled: false,
screenshotsResolution: 1200,
screenshotsFrequency: 30,
showSuspensionActivity: true,
keychains: [],
devices: [],
createdAt: new Date().toISOString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const CombinedUsersActivityFeedRoute: React.FC = () => {
.filter((user) => user.items.length > 0)
.map((user) => ({
userName: user.userName,
highlightSuspensionActivity: user.showSuspensionActivity,
items: user.items.map(outputItemToActivityFeedItem),
}))}
numDeleted={query.data.reduce((acc, user) => acc + user.numDeleted, 0)}
Expand Down
6 changes: 5 additions & 1 deletion dash/app/src/components/routes/User.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const UserRoute: React.FC = () => {
screenshotsEnabled: editableUser.draft.screenshotsEnabled,
screenshotsFrequency: editableUser.draft.screenshotsFrequency,
screenshotsResolution: editableUser.draft.screenshotsResolution,
showSuspensionActivity: true, // configurable in #209
showSuspensionActivity: editableUser.draft.showSuspensionActivity,
isNew: editableUser.isNew ?? false,
keychainIds: editableUser.draft.keychains.map(({ id }) => id),
}),
Expand Down Expand Up @@ -101,6 +101,10 @@ const UserRoute: React.FC = () => {
setScreenshotsFrequency={(frequency) =>
dispatch({ type: `setScreenshotsFrequency`, frequency })
}
showSuspensionActivity={draft.showSuspensionActivity}
setShowSuspensionActivity={(show) =>
dispatch({ type: `setShowSuspensionActivity`, show })
}
removeKeychain={(id) => dispatch({ type: `removeKeychain`, id })}
keychains={draft.keychains}
devices={original.devices.map(deviceProps)}
Expand Down
1 change: 1 addition & 0 deletions dash/app/src/components/routes/UserActivityFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const UserActivityFeedRoute: React.FC = () => {
items={query.data.items
.map(outputItemToActivityFeedItem)
.filter((item) => !item.deleted)}
highlightSuspensionActivity={query.data.showSuspensionActivity}
/>
);
};
Expand Down
4 changes: 4 additions & 0 deletions dash/app/src/reducers/user-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type Action =
| { type: 'setScreenshotsResolution'; resolution: number }
| { type: 'setScreenshotsFrequency'; frequency: number }
| { type: 'setKeyloggingEnabled'; enabled: boolean }
| { type: 'setShowSuspensionActivity'; show: boolean }
| { type: 'removeKeychain'; id: UUID }
| { type: 'addKeychain'; keychain: KeychainSummary }
| { type: 'setAddingKeychain'; keychain?: KeychainSummary | null };
Expand Down Expand Up @@ -50,6 +51,9 @@ function reducer(state: State, action: Action): State | undefined {
case `setScreenshotsFrequency`:
state.user.draft.screenshotsFrequency = action.frequency;
return;
case `setShowSuspensionActivity`:
state.user.draft.showSuspensionActivity = action.show;
return;
case `removeKeychain`:
state.user.draft.keychains = state.user.draft.keychains.filter(
(keychain) => keychain.id !== action.id,
Expand Down
49 changes: 29 additions & 20 deletions dash/components/src/Users/Activity/CombinedUsersActivityFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import cx from 'classnames';
import { Button } from '@shared/components';
import { posessive } from '@shared/string';
import type { ActivityFeedItem } from './UserActivityFeed';
Expand All @@ -11,7 +12,11 @@ import UserActivityHeader from './UserActivityHeader';

interface Props {
date: Date;
activity: Array<{ userName: string; items: ActivityFeedItem[] }>;
activity: Array<{
userName: string;
highlightSuspensionActivity: boolean;
items: ActivityFeedItem[];
}>;
numDeleted: number;
deleteItems(ids: UUID[]): unknown;
chunkSize?: number;
Expand Down Expand Up @@ -51,30 +56,34 @@ const CombinedUsersActivityFeed: React.FC<Props> = ({
<FeedHeader date={date} numItems={items.length} numDeleted={numDeleted} />
{items.length > 0 ? (
<ReviewDayWrapper>
{activity.map(({ userName, items }) => (
{activity.map(({ userName, highlightSuspensionActivity, items }) => (
<div
key={userName}
className="flex flex-col justify-center space-y-4 md:border md:border-slate-200 md:rounded-3xl md:pt-6 lg:pt-8 md:px-4 lg:px-8 md:pb-0 md:bg-white/50"
data-test="single-user-sub-feed"
>
<UserActivityHeader>{userName}</UserActivityHeader>
<DeletableActivityChunks
items={items}
chunkSize={chunkSize}
deleteItems={deleteItems}
/>
{items.length > 1 && (
<div className="flex justify-center pb-8">
<Button
type="button"
onClick={() => deleteItems(items.map((item) => item.id))}
color="secondary"
className="ScrollTop mt-4"
>
<i className="fa-solid fa-thumbs-up mr-2" />
Approve all {posessive(userName)} activity
</Button>
</div>
)}
<div className={cx(`flex flex-col gap-4`, items.length === 1 && `pb-2`)}>
<DeletableActivityChunks
items={items}
chunkSize={chunkSize}
deleteItems={deleteItems}
highlightSuspensionActivity={highlightSuspensionActivity}
/>
{items.length > 1 && (
<div className="flex justify-center pb-8">
<Button
type="button"
onClick={() => deleteItems(items.map((item) => item.id))}
color="secondary"
className="ScrollTop mt-4"
>
<i className="fa-solid fa-thumbs-up mr-2" />
Approve all {posessive(userName)} activity
</Button>
</div>
)}
</div>
</div>
))}
<Button
Expand Down
12 changes: 7 additions & 5 deletions dash/components/src/Users/Activity/DeletableActivityChunks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ const DeletableActivityChunks: React.FC<{
items: ActivityFeedItem[];
chunkSize: number;
deleteItems: (ids: UUID[]) => unknown;
}> = ({ items, deleteItems, chunkSize }) => (
highlightSuspensionActivity: boolean;
}> = ({ items, deleteItems, chunkSize, highlightSuspensionActivity }) => (
<>
{chunkedRenderTasks(items, chunkSize)
{chunkedRenderTasks(items, chunkSize, highlightSuspensionActivity)
.flat(1)
.map((item) => {
switch (item.type) {
Expand Down Expand Up @@ -110,6 +111,7 @@ type ActivityRenderTask<T extends Chunkable> =
export function chunkedRenderTasks<T extends Chunkable>(
items: T[],
chunkSize: number,
highlightSuspensionActivity: boolean,
): Array<ActivityRenderTask<T>[]> {
const ids: UUID[] = [];
const chunkedTasks: Array<ActivityRenderTask<T>[]> = [];
Expand All @@ -129,11 +131,11 @@ export function chunkedRenderTasks<T extends Chunkable>(
(!item.duringSuspension && suspensionBuffer.length > 0) ||
(isLastItem && item.duringSuspension);

if (item.duringSuspension) {
if (item.duringSuspension && highlightSuspensionActivity) {
suspensionBuffer.push(item);
}

if (finishingSuspension) {
if (finishingSuspension && highlightSuspensionActivity) {
if (shouldBeMergedIntoSuspensionGroup(item, chunkItems, i)) {
item.duringSuspension = true;
suspensionBuffer.push(item);
Expand All @@ -143,7 +145,7 @@ export function chunkedRenderTasks<T extends Chunkable>(
}
}

if (!item.duringSuspension) {
if (!item.duringSuspension || !highlightSuspensionActivity) {
tasks.push({ type: `item`, item });
}

Expand Down
3 changes: 3 additions & 0 deletions dash/components/src/Users/Activity/UserActivityFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface Props {
numDeleted: number;
deleteItems(ids: UUID[]): unknown;
chunkSize?: number;
highlightSuspensionActivity: boolean;
}

const UserActivityFeed: React.FC<Props> = ({
Expand All @@ -43,6 +44,7 @@ const UserActivityFeed: React.FC<Props> = ({
numDeleted,
deleteItems,
chunkSize = 100,
highlightSuspensionActivity,
}) => {
const navigate = useNavigate();
return (
Expand All @@ -54,6 +56,7 @@ const UserActivityFeed: React.FC<Props> = ({
items={items}
chunkSize={chunkSize}
deleteItems={deleteItems}
highlightSuspensionActivity={highlightSuspensionActivity}
/>
<Button
className="ScrollTop self-center"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe(`chunkedRenderTasks()`, () => {
const chunks = chunkedRenderTasks(
[screenshot(), screenshot(), screenshot(), screenshot()],
2,
true,
);
expect(simplify(chunks)).toMatchInlineSnapshot(`
[
Expand All @@ -30,6 +31,7 @@ describe(`chunkedRenderTasks()`, () => {
screenshot(),
],
2,
true,
);
expect(simplify(chunks)).toMatchInlineSnapshot(`
[
Expand All @@ -41,6 +43,28 @@ describe(`chunkedRenderTasks()`, () => {
`);
});

it(`should not highlight suspension activity when setting is off`, () => {
const chunks = chunkedRenderTasks(
[
screenshot({ duringSuspension: true }),
screenshot({ duringSuspension: true }),
screenshot(),
screenshot({ duringSuspension: true }),
],
2,
false,
);
expect(simplify(chunks)).toMatchInlineSnapshot(`
[
"item(Screenshot)",
"item(Screenshot)",
"delete_btn",
"item(Screenshot)",
"item(Screenshot)",
]
`);
});

it(`handles suspensions across chunk boundaries`, () => {
const chunks = chunkedRenderTasks(
[
Expand All @@ -50,6 +74,7 @@ describe(`chunkedRenderTasks()`, () => {
screenshot(),
],
2,
true,
);
expect(simplify(chunks)).toMatchInlineSnapshot(`
[
Expand All @@ -70,6 +95,7 @@ describe(`chunkedRenderTasks()`, () => {
screenshot({ duringSuspension: true }),
],
2,
true,
);
expect(simplify(chunks)).toMatchInlineSnapshot(`
[
Expand Down Expand Up @@ -98,6 +124,7 @@ describe(`chunkedRenderTasks()`, () => {
screenshot(),
],
100,
true,
);
expect(simplify(chunks)).toMatchInlineSnapshot(`
[
Expand Down Expand Up @@ -125,6 +152,7 @@ describe(`chunkedRenderTasks()`, () => {
screenshot(),
],
100,
true,
);
expect(simplify(chunks)).toMatchInlineSnapshot(`
[
Expand Down
Loading

0 comments on commit 3c4872a

Please sign in to comment.