-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add inline filter save * Add disable * Fix code style issues with ESLint * Lint fixes --------- Co-authored-by: Lint Action <github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
e810f00
commit 553342a
Showing
8 changed files
with
384 additions
and
7 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
grai-frontend/src/components/graph/drawer/filters-inline/CreateFilterForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
grai-frontend/src/components/graph/drawer/filters-inline/SaveButton.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"))) | ||
}) |
43 changes: 43 additions & 0 deletions
43
grai-frontend/src/components/graph/drawer/filters-inline/SaveButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
71 changes: 71 additions & 0 deletions
71
grai-frontend/src/components/graph/drawer/filters-inline/SaveDialog.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}) | ||
}) |
103 changes: 103 additions & 0 deletions
103
grai-frontend/src/components/graph/drawer/filters-inline/SaveDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
26 changes: 26 additions & 0 deletions
26
grai-frontend/src/components/graph/drawer/filters-inline/__generated__/CreateFilterInline.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.