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

Inline filter save #615

Merged
merged 4 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -0,0 +1,48 @@
import React, { useState } from "react"
import { LoadingButton } from "@mui/lab"
import { TextField } from "@mui/material"
import Form from "components/form/Form"

export type Values = {
name: string
}

type CreateFilterFormProps = {
loading?: boolean
onSubmit: (values: Values) => void
}

const CreateFilterForm: React.FC<CreateFilterFormProps> = ({
loading,
onSubmit,
}) => {
const [values, setValues] = useState<Values>({
name: "",
})

const handleSubmit = () => onSubmit(values)

return (
<Form onSubmit={handleSubmit}>
<TextField
label="Name"
value={values.name}
onChange={e => setValues({ ...values, name: e.target.value })}
fullWidth
required
margin="normal"
/>
<LoadingButton
loading={loading}
type="submit"
variant="contained"
fullWidth
sx={{ mt: 2, height: 56 }}
>
Save
</LoadingButton>
</Form>
)
}

export default CreateFilterForm
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from "react"
import { gql, useQuery } from "@apollo/client"
import { Save } from "@mui/icons-material"
import { Box, Button, CircularProgress, Stack } from "@mui/material"
import { Box, CircularProgress } from "@mui/material"
import NotFound from "pages/NotFound"
import useWorkspace from "helpers/useWorkspace"
import { Filter, getProperties } from "components/filters/filters"
import GraphError from "components/utils/GraphError"
import AddButton from "./AddButton"
import FilterRow from "./FilterRow"
import SaveButton from "./SaveButton"
import {
GetWorkspaceFilterInline,
GetWorkspaceFilterInlineVariables,
Expand Down Expand Up @@ -87,11 +87,7 @@ const GraphFilterInline: React.FC<GraphFilterInlineProps> = ({

return (
<Box sx={{ p: 1 }}>
<Stack direction="row" spacing={1} sx={{ mb: 1 }}>
<Button variant="outlined" fullWidth startIcon={<Save />}>
Save
</Button>
</Stack>
<SaveButton inlineFilters={inlineFilters} workspaceId={workspace.id} />
{inlineFilters.map((filter, index) => (
<FilterRow
key={index}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import userEvent from "@testing-library/user-event"
import { act, render, screen, waitFor } from "testing"
import SaveButton from "./SaveButton"

const defaultProps = {
workspaceId: "1",
inlineFilters: [
{
type: "table",
field: null,
operator: null,
value: null,
},
],
}

test("renders", async () => {
render(<SaveButton {...defaultProps} />)

await waitFor(() => {
expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument()
})
})

test("open and close", async () => {
const user = userEvent.setup()

render(<SaveButton {...defaultProps} />)

await waitFor(() => {
expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument()
})

await act(
async () => await user.click(screen.getByRole("button", { name: /save/i })),
)

await waitFor(() => {
expect(screen.getByTestId("CloseIcon")).toBeInTheDocument()
})

await act(async () => await user.click(screen.getByTestId("CloseIcon")))
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { useState } from "react"
import { Save } from "@mui/icons-material"
import { Button } from "@mui/material"
import { Filter } from "components/filters/filters"
import SaveDialog from "./SaveDialog"

type SaveButtonProps = {
workspaceId: string
inlineFilters: Filter[]
}

const SaveButton: React.FC<SaveButtonProps> = ({
workspaceId,
inlineFilters,
}) => {
const [open, setOpen] = useState(false)

const handleOpen = () => setOpen(true)
const handleClose = () => setOpen(false)

return (
<>
<Button
variant="outlined"
fullWidth
startIcon={<Save />}
sx={{ mb: 1 }}
onClick={handleOpen}
disabled={inlineFilters.length === 0}
>
Save
</Button>
<SaveDialog
open={open}
onClose={handleClose}
workspaceId={workspaceId}
inlineFilters={inlineFilters}
/>
</>
)
}

export default SaveButton
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import userEvent from "@testing-library/user-event"
import { GraphQLError } from "graphql"
import { act, render, screen, waitFor } from "testing"
import SaveDialog, { CREATE_FILTER } from "./SaveDialog"

const defaultProps = {
open: true,
onClose: jest.fn(),
workspaceId: "1",
inlineFilters: [],
}

test("renders", async () => {
render(<SaveDialog {...defaultProps} />)

await waitFor(() => {
expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument()
})
})

test("submit", async () => {
const user = userEvent.setup()

render(<SaveDialog {...defaultProps} />)

await waitFor(() => {
expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument()
})

await act(async () => user.type(screen.getByLabelText(/name/i), "test"))

await act(
async () => await user.click(screen.getByRole("button", { name: /save/i })),
)
})

test("error", async () => {
const user = userEvent.setup()

const mocks = [
{
request: {
query: CREATE_FILTER,
variables: {
workspaceId: "1",
name: "test",
metadata: [],
},
},
result: {
errors: [new GraphQLError("Error!")],
},
},
]

render(<SaveDialog {...defaultProps} />, { mocks })

await waitFor(() => {
expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument()
})

await act(async () => user.type(screen.getByLabelText(/name/i), "test"))

await act(
async () => await user.click(screen.getByRole("button", { name: /save/i })),
)

await waitFor(() => {
expect(screen.getByText("Error!")).toBeInTheDocument()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React from "react"
import { gql, useMutation } from "@apollo/client"
import { Dialog, DialogContent } from "@mui/material"
import { useSnackbar } from "notistack"
import DialogTitle from "components/dialogs/DialogTitle"
import { NewFilter } from "components/filters/__generated__/NewFilter"
import { Filter } from "components/filters/filters"
import GraphError from "components/utils/GraphError"
import {
CreateFilterInline,
CreateFilterInlineVariables,
} from "./__generated__/CreateFilterInline"
import CreateFilterForm, { Values } from "./CreateFilterForm"

export const CREATE_FILTER = gql`
mutation CreateFilterInline(
$workspaceId: ID!
$name: String!
$metadata: JSON!
) {
createFilter(workspaceId: $workspaceId, name: $name, metadata: $metadata) {
id
name
metadata
created_at
}
}
`

type SaveDialogProps = {
workspaceId: string
inlineFilters: Filter[]
open: boolean
onClose: () => void
}

const SaveDialog: React.FC<SaveDialogProps> = ({
workspaceId,
inlineFilters,
open,
onClose,
}) => {
const { enqueueSnackbar } = useSnackbar()

/* istanbul ignore next */
const [createFilter, { loading, error }] = useMutation<
CreateFilterInline,
CreateFilterInlineVariables
>(CREATE_FILTER, {
update(cache, { data }) {
cache.modify({
id: cache.identify({
id: workspaceId,
__typename: "Workspace",
}),
fields: {
filters(existingFilters = { data: [] }) {
if (!data?.createFilter) return

const newFilter = cache.writeFragment<NewFilter>({
data: data.createFilter,
fragment: gql`
fragment NewFilter on Filter {
id
name
metadata
created_at
}
`,
})
return {
data: [...existingFilters.data, newFilter],
meta: {
total: (existingFilters.meta?.total ?? 0) + 1,
__typename: "FilterPagination",
},
}
},
},
})
},
})

const handleSave = (values: Values) =>
createFilter({
variables: { workspaceId, metadata: inlineFilters, ...values },
})
.then(() => enqueueSnackbar("Filter created", { variant: "success" }))
.then(onClose)
.catch(() => {})

return (
<Dialog open={open} onClose={onClose} fullWidth maxWidth="sm">
<DialogTitle onClose={onClose}>Save Filter</DialogTitle>
<DialogContent>
{error && <GraphError error={error} />}
<CreateFilterForm onSubmit={handleSave} loading={loading} />
</DialogContent>
</Dialog>
)
}

export default SaveDialog

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading