Skip to content

Commit

Permalink
chore: use dropdown for snippet host selection
Browse files Browse the repository at this point in the history
  • Loading branch information
eldemcan authored and svedova committed Feb 21, 2024
1 parent b1cc367 commit 48fc5f3
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 21 deletions.
81 changes: 81 additions & 0 deletions src/components/MultiSelect/MultiSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { SelectProps } from "@mui/material/Select";
import React, { useState } from "react";
import FilledInput from "@mui/material/FilledInput";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import ListItemText from "@mui/material/ListItemText";
import Select from "@mui/material/Select";
import Checkbox from "@mui/material/Checkbox";
import FormHelperText from "@mui/material/FormHelperText";

interface MenuItem {
value: string;
text: string;
}

interface Props extends Omit<SelectProps, "onSelect"> {
items: MenuItem[];
selected: string[];
onSelect: (v: string[]) => void;
helperText?: React.ReactNode;
label?: string;
}

export default function MultiSelect({
items,
helperText,
selected = [],
label,
onSelect,
}: Props) {
const [selectedItems, setSelectedItems] = useState<string[]>(selected);

return (
<FormControl variant="standard" fullWidth>
<InputLabel id="multiple-checkbox-label" sx={{ pl: 2, pt: 1 }} shrink>
{label}
</InputLabel>
<Select
labelId="multiple-checkbox-label"
multiple
variant="filled"
value={selectedItems.length > 0 ? selectedItems : [""]}
input={<FilledInput />}
onChange={e => {
let values = [
...new Set(
typeof e.target.value === "string"
? e.target.value.split(",")
: e.target.value
),
];

const lastClicked = values.at(-1);

if (typeof lastClicked === "undefined") {
values = [];
}

setSelectedItems(values);
onSelect(values.filter(i => i));
}}
renderValue={selected => {
return selected.filter(s => s !== "").join(", ") || "All hosts";
}}
>
<MenuItem>
<Checkbox checked={!selectedItems.filter(i => i).length} />
<ListItemText primary={"All hosts"} />
</MenuItem>
{items.map(item => (
<MenuItem key={item.value} value={item.value}>
<Checkbox checked={selectedItems.includes(item.value)} />
<ListItemText primary={item.text} />
</MenuItem>
))}
</Select>
{helperText && <FormHelperText>{helperText}</FormHelperText>}
</FormControl>
);
}
1 change: 1 addition & 0 deletions src/components/MultiSelect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./MultiSelect";
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { RenderResult } from "@testing-library/react";
import { MemoryRouter } from "react-router";
import { waitFor, fireEvent, render } from "@testing-library/react";
import { waitFor, fireEvent, render, getByText } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { AppContext } from "~/pages/apps/[id]/App.context";
import { EnvironmentContext } from "~/pages/apps/[id]/environments/Environment.context";
Expand Down Expand Up @@ -41,6 +41,9 @@ describe("~/pages/apps/[id]/environments/[env-id]/snippets/SnippetModal.tsx", ()
location: "head",
};

const findDropdown = () => wrapper.getByLabelText("Hosts");
const findOption = (text: string) => getByText(document.body, text);

const createWrapper = ({ app, env, closeModal, snippet }: Props) => {
wrapper = render(
<MemoryRouter>
Expand All @@ -55,6 +58,7 @@ describe("~/pages/apps/[id]/environments/[env-id]/snippets/SnippetModal.tsx", ()
<SnippetModal
setRefreshToken={setRefreshToken}
closeModal={closeModal}
domains={["www.e.org", "e.org"]}
snippet={snippet}
/>
</EnvironmentContext.Provider>
Expand Down Expand Up @@ -83,20 +87,28 @@ describe("~/pages/apps/[id]/environments/[env-id]/snippets/SnippetModal.tsx", ()
const scope = mockInsertSnippet({
appId: currentApp.id,
envId: currentEnv.id!,
snippets: [{ ...snippet, rules: { hosts: ["www.e.org", "e.org"] } }],
snippets: [
{ ...snippet, rules: { hosts: ["*.dev", "www.e.org", "e.org"] } },
],
});

await userEvent.type(wrapper.getByLabelText("Title"), "Google Analytics");
await userEvent.type(wrapper.getByLabelText("Hosts"), "www.e.org, e.org");

const selector = findDropdown();
expect(selector).toBeTruthy();
fireEvent.mouseDown(selector);

fireEvent.click(findOption("All development endpoints (*.dev)"));
fireEvent.click(findOption("www.e.org"));
fireEvent.click(findOption("e.org"));

await fireEvent.click(wrapper.getByText("Create"));

await waitFor(() => {
expect(scope.isDone()).toBe(true);
expect(closeModal).toHaveBeenCalled();
expect(setRefreshToken).toHaveBeenCalled();
});

expect(closeModal).toHaveBeenCalled();
expect(setRefreshToken).toHaveBeenCalled();
});
});

Expand All @@ -122,7 +134,12 @@ describe("~/pages/apps/[id]/environments/[env-id]/snippets/SnippetModal.tsx", ()
const scope = mockUpdateSnippet({
appId: currentApp.id,
envId: currentEnv.id!,
snippet: { ...snippet, location: "body", title: "Hotjar", id: 1 },
snippet: {
...snippet,
location: "body",
title: "Hotjar",
id: 1,
},
});

await userEvent.clear(wrapper.getByLabelText("Title"));
Expand Down
32 changes: 18 additions & 14 deletions src/pages/apps/[id]/environments/[env-id]/snippets/SnippetModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Option from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import FormControlLabel from "@mui/material/FormControlLabel";
import InputLabel from "@mui/material/InputLabel";
import MultiSelect from "~/components/MultiSelect";
import Card from "~/components/Card";
import CardHeader from "~/components/CardHeader";
import CardFooter from "~/components/CardFooter";
Expand All @@ -21,6 +22,7 @@ import { addSnippet, updateSnippet } from "./actions";

interface Props {
snippet?: Snippet;
domains?: string[];
closeModal: () => void;
setRefreshToken: (t: number) => void;
}
Expand All @@ -41,11 +43,15 @@ const SnippetModal: React.FC<Props> = ({
closeModal,
snippet,
setRefreshToken,
domains,
}): React.ReactElement => {
const { app } = useContext(AppContext);
const { environment } = useContext(EnvironmentContext);
const [error, setError] = useState<string>();
const [loading, setLoading] = useState(false);
const [selectedHosts, setSelectedHosts] = useState<string[]>(
snippet?.rules?.hosts || []
);
const [codeContent, setCodeContent] = useState(
snippet?.content || "<script>\n console.log('Hello world');\n</script>"
);
Expand All @@ -62,10 +68,6 @@ const SnippetModal: React.FC<Props> = ({
) as unknown as FormValues;

const [location, prependOrAppend] = values.injectLocation.split("_");
const hosts = values.hosts
.split(",")
.map(i => i.trim().toLowerCase())
.filter(i => i);

handler({
appId: app.id,
Expand All @@ -77,7 +79,7 @@ const SnippetModal: React.FC<Props> = ({
enabled: values.enabled === "on",
location: location === "head" ? "head" : "body",
prepend: prependOrAppend === "prepend",
rules: hosts?.length ? { hosts } : undefined,
rules: selectedHosts?.length ? { hosts: selectedHosts } : undefined,
},
})
.then(() => {
Expand Down Expand Up @@ -110,6 +112,7 @@ const SnippetModal: React.FC<Props> = ({
</>
}
/>

<Box sx={{ mb: 4 }}>
<TextField
label="Title"
Expand Down Expand Up @@ -167,16 +170,17 @@ const SnippetModal: React.FC<Props> = ({
</FormControl>
</Box>
<Box sx={{ mb: 4 }}>
<TextField
<MultiSelect
label="Hosts"
name="hosts"
fullWidth
defaultValue={snippet?.rules?.hosts?.join(", ") || ""}
variant="filled"
autoComplete="off"
helperText="Limit this snippet to specified hosts. Separate multiple hosts with a comma `,`."
InputLabelProps={{
shrink: true,
items={[
{ value: "*.dev", text: "All development endpoints (*.dev)" },
...(domains?.length
? domains?.map(d => ({ value: d, text: d }))
: []),
]}
selected={selectedHosts}
onSelect={value => {
setSelectedHosts(value);
}}
/>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { waitFor, fireEvent, render } from "@testing-library/react";
import { AppContext } from "~/pages/apps/[id]/App.context";
import { EnvironmentContext } from "~/pages/apps/[id]/environments/Environment.context";
import { mockFetchSnippets } from "~/testing/nocks/nock_snippets";
import { mockFetchDomains } from "~/testing/nocks/nock_domains";
import mockSnippets from "~/testing/data/mock_snippets";
import mockApp from "~/testing/data/mock_app";
import mockEnvironment from "~/testing/data/mock_environment";
Expand All @@ -17,12 +18,19 @@ interface Props {

describe("~/pages/apps/[id]/environments/[env-id]/snippets/Snippets.tsx", () => {
let fetchSnippetsScope: Scope;
let fetchDomainsScope: Scope;
let wrapper: RenderResult;
let currentApp: App;
let currentEnv: Environment;
let snippets = mockSnippets();

const createWrapper = ({ app, env }: Props) => {
fetchDomainsScope = mockFetchDomains({
appId: app.id,
envId: env.id!,
response: { domains: [] },
});

wrapper = render(
<MemoryRouter>
<AppContext.Provider
Expand Down Expand Up @@ -54,6 +62,12 @@ describe("~/pages/apps/[id]/environments/[env-id]/snippets/Snippets.tsx", () =>
createWrapper({ app: currentApp, env: currentEnv });
});

test("should fetch domains", async () => {
await waitFor(() => {
expect(fetchDomainsScope.isDone()).toBe(true);
});
});

test("should load snippets", async () => {
const s1 = snippets[0];
const s2 = snippets[0];
Expand Down
14 changes: 14 additions & 0 deletions src/pages/apps/[id]/environments/[env-id]/snippets/Snippets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CardFooter from "~/components/CardFooter";
import CardRow from "~/components/CardRow";
import EmptyPage from "~/components/EmptyPage";
import ConfirmModal from "~/components/ConfirmModal";
import { useFetchDomains } from "~/shared/domains/actions";
import { useFetchSnippets, deleteSnippet, updateSnippet } from "./actions";
import SnippetModal from "./SnippetModal";

Expand All @@ -30,6 +31,15 @@ export default function Snippets() {
refreshToken,
});

const domainsRes = useFetchDomains({
appId: app.id,
envId: env.id!,
});

const domains = domainsRes.domains
.filter(d => d.verified === true)
.map(d => d.domainName);

return (
<Card
error={error}
Expand Down Expand Up @@ -90,6 +100,9 @@ export default function Snippets() {
{`<${snippet.prepend ? "/" : ""}${snippet.location}>`}
</Typography>
</Typography>
<Typography sx={{ color: grey[500] }}>
{snippet.rules?.hosts?.join(", ") || "All hosts"}
</Typography>
</Box>
<FormControlLabel
sx={{ pl: 0, ml: 0 }}
Expand Down Expand Up @@ -130,6 +143,7 @@ export default function Snippets() {
</CardFooter>
{isSnippetModalOpen && snippets && (
<SnippetModal
domains={domains}
snippet={toBeModified}
setRefreshToken={setRefreshToken}
closeModal={() => {
Expand Down

0 comments on commit 48fc5f3

Please sign in to comment.