Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tidy: release add Team Editor feature #3580

Merged
merged 50 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
a4d60bf
fix: refactor Team component folder structure
jamdelion Aug 5, 2024
f7e87fb
Basic "add new editor" button on team editors table
jamdelion Aug 5, 2024
5f38c8a
Merge main
jamdelion Aug 5, 2024
d2f9c59
Hide button behind feature flag
jamdelion Aug 6, 2024
0a566a1
Rename test
jamdelion Aug 6, 2024
edc221b
Test button appears within team editors table only
jamdelion Aug 6, 2024
fe7ce77
Style button correctly
jamdelion Aug 6, 2024
12df25c
Basic open modal functionality
jamdelion Aug 6, 2024
2975369
Move modal to new component and add static input fields
jamdelion Aug 7, 2024
3240488
Extract setup helper from test
jamdelion Aug 7, 2024
50c0a9c
Extract props type
jamdelion Aug 7, 2024
f6d7f25
Use formik for form validation and submission
jamdelion Aug 8, 2024
b419666
Implement changes requested
jamdelion Aug 8, 2024
5d189eb
Merge branch 'jh/add-team-editor-button' into jh/validate-add-team-ed…
jamdelion Aug 8, 2024
c5f31a8
Get basic createUser working
jamdelion Aug 8, 2024
0f368f5
Merge branch 'main' into jh/validate-add-team-editor
jamdelion Aug 12, 2024
4a27c5c
Basic addUserToTeam mutation
jamdelion Aug 12, 2024
3f81d7b
Split out queries into own folder
jamdelion Aug 12, 2024
fc91022
Tidy up - move type defs and schemas
jamdelion Aug 12, 2024
4ec6cb6
Fix import
jamdelion Aug 12, 2024
e616c02
Start writing a test for form submission - close modal
jamdelion Aug 12, 2024
237a0ee
Optimistically update the table when fetching complete, plus refactors
jamdelion Aug 14, 2024
29624c1
Test that new row added to team editors table
jamdelion Aug 14, 2024
522e2d7
Update editor.planx.uk/src/pages/FlowEditor/components/Team/component…
jamdelion Aug 19, 2024
37c8a3f
Use teamId from store instead of fetchCurrentTeam
jamdelion Aug 19, 2024
b6a12d2
Combine createUser and addToTeam mutations into one
jamdelion Aug 19, 2024
2e1fedc
Use store to optimistically update, plus refactor member filtering
jamdelion Aug 20, 2024
8a3459d
Fix tests
jamdelion Aug 20, 2024
126d6d4
Merge branch 'main' into jh/validate-add-team-editor
jamdelion Aug 20, 2024
a5dfb84
Show toast message on success
jamdelion Aug 21, 2024
a78a86d
Handle user already exists er
jamdelion Aug 21, 2024
a4904bb
Refactor tests
jamdelion Aug 21, 2024
0a1aee5
Extract error logic out
jamdelion Aug 21, 2024
034d4d4
Add in correct close-modal test
jamdelion Aug 21, 2024
45ef05c
Improve platformAdmins filter
jamdelion Aug 22, 2024
752fb93
Use returned userId as key in table update
jamdelion Aug 22, 2024
99b38c6
Fix teamMember store setting
jamdelion Aug 22, 2024
55c558b
Refetch query when user created and added to team
jamdelion Aug 23, 2024
1c5651f
Merge branch 'main' into jh/validate-add-team-editor
jamdelion Aug 26, 2024
096f824
Merge branch 'jh/validate-add-team-editor' into jh/add-editor-error-h…
jamdelion Aug 26, 2024
c54b7c7
Merge branch 'main' into jh/add-editor-error-handling
jamdelion Aug 26, 2024
0cfbd19
Delete duplicate files from merge
jamdelion Aug 26, 2024
0cb25f9
Use user.type instead of fireEvent
jamdelion Aug 27, 2024
06547b7
Add axe test
jamdelion Aug 27, 2024
600125e
Release from feature flag
jamdelion Aug 27, 2024
25e53a8
Merge branch 'main' into jh/tidy-up-add-editor
jamdelion Aug 28, 2024
91f6de5
Remove redundant exampleTeamMembersData
jamdelion Aug 28, 2024
e7007a1
Show error toast if server side error
jamdelion Aug 29, 2024
1799a78
Fix modal props in test
jamdelion Aug 29, 2024
eafac04
Change form background colour
jamdelion Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import { optimisticallyUpdateMembersTable } from "./lib/optimisticallyUpdateMemb
export const AddNewEditorModal = ({
showModal,
setShowModal,
setShowToast,
setShowSuccessToast,
setShowErrorToast,
Comment on lines +27 to +28
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the sort of thing I hope to get rid of with a useToast hook in another PR 😁

}: AddNewEditorModalProps) => {
const [showUserAlreadyExistsError, setShowUserAlreadyExistsError] =
useState<boolean>(false);

const clearErrors = () => {
setShowUserAlreadyExistsError(false);
setShowErrorToast(false);
};

const handleSubmit = async (
Expand All @@ -39,7 +41,7 @@ export const AddNewEditorModal = ({
) => {
const { teamId, teamSlug } = useStore.getState();

const newUserId = await createAndAddUserToTeam(
const createUserResult = await createAndAddUserToTeam(
values.email,
values.firstName,
values.lastName,
Expand All @@ -49,16 +51,19 @@ export const AddNewEditorModal = ({
if (isUserAlreadyExistsError(err.message)) {
setShowUserAlreadyExistsError(true);
}
if (err.message === "Unable to create user") {
setShowErrorToast(true);
}
Comment on lines +54 to +56
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we'll ever reach this due to the error handling already in src/lib/graphql.ts but hopefully a good fallback

console.error(err);
});

if (!newUserId) {
if (!createUserResult) {
return;
}
clearErrors();
optimisticallyUpdateMembersTable(values, newUserId);
optimisticallyUpdateMembersTable(values, createUserResult.id);
setShowModal(false);
setShowToast(true);
setShowSuccessToast(true);
resetForm({ values });
};

Expand All @@ -82,7 +87,7 @@ export const AddNewEditorModal = ({
maxWidth: theme.breakpoints.values.md,
borderRadius: 0,
borderTop: `20px solid ${theme.palette.primary.main}`,
background: "#FFF",
background: theme.palette.background.paper,
margin: theme.spacing(2),
}),
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import { hasFeatureFlag } from "lib/featureFlags";
import { AddButton } from "pages/Team";
import React, { useState } from "react";

Expand All @@ -20,17 +19,29 @@ export const MembersTable = ({
showAddMemberButton,
}: MembersTableProps) => {
const [showModal, setShowModal] = useState(false);
const [showToast, setShowToast] = useState(false);
const [showSuccessToast, setShowSuccessToast] = useState(false);
const [showErrorToast, setShowErrorToast] = useState(false);

const handleCloseToast = (
const handleCloseSuccessToast = (
_event?: React.SyntheticEvent | Event,
reason?: string,
) => {
if (reason === "clickaway") {
return;
}

setShowToast(false);
setShowSuccessToast(false);
};

const handleCloseErrorToast = (
_event?: React.SyntheticEvent | Event,
reason?: string,
) => {
if (reason === "clickaway") {
return;
}

setShowErrorToast(false);
Comment on lines +36 to +44
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as duplicated for now and will revisit with a useToast hook 🤞

};

const roleLabels: Record<string, string> = {
Expand Down Expand Up @@ -102,7 +113,7 @@ export const MembersTable = ({
<TableCell>{member.email}</TableCell>
</StyledTableRow>
))}
{showAddMemberButton && hasFeatureFlag("ADD_NEW_EDITOR") && (
{showAddMemberButton && (
<TableRow>
<TableCell colSpan={3}>
<AddButton onClick={() => setShowModal(true)}>
Expand All @@ -115,23 +126,39 @@ export const MembersTable = ({
</Table>
{showAddMemberButton && (
<Snackbar
open={showToast}
open={showSuccessToast}
autoHideDuration={6000}
onClose={handleCloseToast}
onClose={handleCloseSuccessToast}
>
<Alert
onClose={handleCloseToast}
onClose={handleCloseSuccessToast}
severity="success"
sx={{ width: "100%" }}
>
Successfully added a user
</Alert>
</Snackbar>
)}
{showAddMemberButton && (
<Snackbar
open={showErrorToast}
autoHideDuration={6000}
onClose={handleCloseErrorToast}
>
<Alert
onClose={handleCloseErrorToast}
severity="error"
sx={{ width: "100%" }}
>
Failed to add new user, please try again
</Alert>
</Snackbar>
)}
</TableContainer>
{showModal && (
<AddNewEditorModal
setShowToast={setShowToast}
setShowSuccessToast={setShowSuccessToast}
setShowErrorToast={setShowErrorToast}
showModal={showModal}
setShowModal={setShowModal}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { gql } from "@apollo/client";
import { FetchResult, gql } from "@apollo/client";
import { GET_USERS_FOR_TEAM_QUERY } from "routes/teamMembers";

import { client } from "../../../../../lib/graphql";

type CreateAndAddUserResponse = FetchResult<{
insert_users_one: { id: number; __typename: "users" };
}>;

export const createAndAddUserToTeam = async (
email: string,
firstName: string,
Expand All @@ -11,7 +15,7 @@ export const createAndAddUserToTeam = async (
teamSlug: string,
) => {
// NB: the user is hard-coded with the 'teamEditor' role for now
const response = (await client.mutate({
const response: CreateAndAddUserResponse = await client.mutate({
mutation: gql`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: client.mutate also accepts a generic which will automatically wrap the provided type with FetchResult, e.g. client.mutate<MyType>

mutation CreateAndAddUserToTeam(
$email: String!
Expand Down Expand Up @@ -40,6 +44,9 @@ export const createAndAddUserToTeam = async (
refetchQueries: [
{ query: GET_USERS_FOR_TEAM_QUERY, variables: { teamSlug } },
],
})) as any;
return response.data.insert_users_one;
});
if (response.data) {
return response.data.insert_users_one;
}
throw new Error("Unable to create user");
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { screen } from "@testing-library/react";
import { FullStore, useStore } from "pages/FlowEditor/lib/store";
import { vi } from "vitest";

import { setupTeamMembersScreen } from "./helpers/setupTeamMembersScreen";
import { userTriesToAddNewEditor } from "./helpers/userTriesToAddNewEditor";
import { mockTeamMembersData } from "./mocks/mockTeamMembersData";
import { alreadyExistingUser } from "./mocks/mockUsers";

let initialState: FullStore;
vi.mock(
"pages/FlowEditor/components/Team/queries/createAndAddUserToTeam.tsx",
() => ({
createAndAddUserToTeam: vi.fn().mockRejectedValue({
message: "Unable to create user",
}),
}),
);

describe("when a user fills in the 'add a new editor' form correctly but there is a server-side error", () => {
afterAll(() => useStore.setState(initialState));
beforeEach(async () => {
useStore.setState({
teamMembers: [...mockTeamMembersData, alreadyExistingUser],
});

const { user } = await setupTeamMembersScreen();
await userTriesToAddNewEditor(user);
});

it("shows an appropriate error message", async () => {
expect(
await screen.findByText(/Failed to add new user, please try again/),
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import { userTriesToAddNewEditor } from "./helpers/userTriesToAddNewEditor";
import { mockTeamMembersData } from "./mocks/mockTeamMembersData";
import { alreadyExistingUser } from "./mocks/mockUsers";

vi.mock("lib/featureFlags.ts", () => ({
hasFeatureFlag: vi.fn().mockReturnValue(true),
}));

vi.mock(
"pages/FlowEditor/components/Team/queries/createAndAddUserToTeam.tsx",
() => ({
Expand All @@ -20,7 +16,6 @@ vi.mock(
}),
}),
);

let initialState: FullStore;

describe("when a user fills in the 'add a new editor' form correctly but the user already exists", () => {
Expand All @@ -30,7 +25,7 @@ describe("when a user fills in the 'add a new editor' form correctly but the use
teamMembers: [...mockTeamMembersData, alreadyExistingUser],
});

const user = await setupTeamMembersScreen();
const { user } = await setupTeamMembersScreen();
await userTriesToAddNewEditor(user);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { screen, waitFor, within } from "@testing-library/react";
import { FullStore, useStore } from "pages/FlowEditor/lib/store";
import React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { vi } from "vitest";
import { axe } from "vitest-axe";

import { setup } from "../../../../../testUtils";
import { AddNewEditorModal } from "../components/AddNewEditorModal";
import { setupTeamMembersScreen } from "./helpers/setupTeamMembersScreen";
import { userTriesToAddNewEditor } from "./helpers/userTriesToAddNewEditor";
import { mockTeamMembersData } from "./mocks/mockTeamMembersData";

vi.mock("lib/featureFlags.ts", () => ({
hasFeatureFlag: vi.fn().mockReturnValue(true),
}));

vi.mock(
"pages/FlowEditor/components/Team/queries/createAndAddUserToTeam.tsx",
() => ({
Expand All @@ -22,10 +24,10 @@ vi.mock(

let initialState: FullStore;

describe("when a user with the ADD_NEW_EDITOR feature flag enabled presses 'add a new editor'", () => {
describe("when a user presses 'add a new editor'", () => {
beforeEach(async () => {
useStore.setState({ teamMembers: mockTeamMembersData });
const user = await setupTeamMembersScreen();
const { user } = await setupTeamMembersScreen();

const teamEditorsTable = screen.getByTestId("team-editors");
const addEditorButton = await within(teamEditorsTable).findByText(
Expand All @@ -44,7 +46,7 @@ describe("when a user fills in the 'add a new editor' form correctly", () => {
afterAll(() => useStore.setState(initialState));
beforeEach(async () => {
useStore.setState({ teamMembers: mockTeamMembersData });
const user = await setupTeamMembersScreen();
const { user } = await setupTeamMembersScreen();
await userTriesToAddNewEditor(user);
});

Expand All @@ -70,3 +72,22 @@ describe("when a user fills in the 'add a new editor' form correctly", () => {
).toBeInTheDocument();
});
});

describe("when the addNewEditor modal is rendered", () => {
it("should not have any accessibility issues", async () => {
const { container } = setup(
<DndProvider backend={HTML5Backend}>
<AddNewEditorModal
setShowErrorToast={() => {}}
showModal={true}
setShowModal={() => {}}
setShowSuccessToast={() => {}}
/>
</DndProvider>,
);
await screen.findByTestId("modal-create-user");

const results = await axe(container);
expect(results).toHaveNoViolations();
});
});

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { setup } from "../../../../../../testUtils";
import { TeamMembers } from "../../TeamMembers";

export const setupTeamMembersScreen = async () => {
const { user } = setup(
const setupResult = setup(
<DndProvider backend={HTML5Backend}>
<TeamMembers />
</DndProvider>,
);
await screen.findByText("Team editors");
return user;
await screen.findByTestId("team-editors");
return setupResult;
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export interface MembersTableProps {
export interface AddNewEditorModalProps {
showModal: boolean;
setShowModal: React.Dispatch<SetStateAction<boolean>>;
setShowToast: React.Dispatch<SetStateAction<boolean>>;
setShowSuccessToast: React.Dispatch<SetStateAction<boolean>>;
setShowErrorToast: React.Dispatch<SetStateAction<boolean>>;
}

export interface AddNewEditorFormValues {
Expand Down
Loading