From b371ae10d5c81589151986d475968dd2a4e050de Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Mon, 11 Apr 2022 02:05:20 -0500 Subject: [PATCH 01/51] Start setup on user management feature --- .gitignore | 1 + .../features/user-management/AddNewUser.tsx | 184 ++++++++++++++++++ .../user-management/UserManagementRow.tsx | 89 +++++++++ .../user-management/UserManagementTable.tsx | 109 +++++++++++ .../UserManagementTableActions.tsx | 82 ++++++++ clients/admin-ui/src/pages/index.tsx | 12 +- 6 files changed, 476 insertions(+), 1 deletion(-) create mode 100644 clients/admin-ui/src/features/user-management/AddNewUser.tsx create mode 100644 clients/admin-ui/src/features/user-management/UserManagementRow.tsx create mode 100644 clients/admin-ui/src/features/user-management/UserManagementTable.tsx create mode 100644 clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx diff --git a/.gitignore b/.gitignore index 7297fd0a1..19ec29a7d 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,7 @@ __pypackages__/ # Environments .venv +.env.local env/ venv/ ENV/ diff --git a/clients/admin-ui/src/features/user-management/AddNewUser.tsx b/clients/admin-ui/src/features/user-management/AddNewUser.tsx new file mode 100644 index 000000000..1b9d2bdbd --- /dev/null +++ b/clients/admin-ui/src/features/user-management/AddNewUser.tsx @@ -0,0 +1,184 @@ +import React, { useState } from 'react'; +import type { NextPage } from 'next'; +import Head from 'next/head'; +import { useFormik } from 'formik'; + +import { + Stack, + Heading, + FormControl, + FormLabel, + Input, + Button, + FormErrorMessage, + chakra, + // useToast, +} from '@fidesui/react'; + +const useAddNewUserForm = () => { + const [isLoading, setIsLoading] = useState(false); + const formik = useFormik({ + initialValues: { + username: '', + name: '', + password: '', + }, + onSubmit: async (values) => { + setIsLoading(true); + // const response = await + // }); + setIsLoading(false); + // if (response && response.ok) { + // } else { + // toast({ + // status: 'error', + // description: + // 'Creating new user failed.', + // }); + // } + }, + validate: (values) => { + const errors: { + username?: string; + name?: string; + password?: string; + } = {}; + + if (!values.username) { + errors.username = 'Username is required'; + } + + if (!values.name) { + errors.username = 'Name is required'; + } + + if (!values.password) { + errors.password = 'Password is required'; + } + + return errors; + }, + }); + + return { ...formik, isLoading }; +}; + +const AddNewUser: NextPage = () => { + const { + errors, + handleBlur, + handleChange, + handleSubmit, + isLoading, + touched, + values, + } = useAddNewUserForm(); + return ( +
+ + FidesUI App - User Management - Add New User + + + +
+ {/* Breadcrumb component here */} + + Profile + + + + + + Username + + + {errors.username} + + + + + Name + + + {errors.name} + + + + + Password + + + {errors.password} + + + {/* Preferences checkboxes here */} + + + + +
+
+ ); +}; + +export default AddNewUser; diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx new file mode 100644 index 000000000..304681ddd --- /dev/null +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -0,0 +1,89 @@ +import React, { useRef, useState } from 'react'; +import { + Tag, + Text, + Tr, + Td, + Button, + ButtonGroup, + Menu, + MenuButton, + MenuList, + MenuItem, + Portal, +} from '@fidesui/react'; + +import { MoreIcon } from '../common/Icon'; +const useUserManagementRow = () => { + const [menuOpen, setMenuOpen] = useState(false); + const handleMenuOpen = () => setMenuOpen(true); + const handleMenuClose = () => setMenuOpen(false); + + return { + menuOpen, + handleMenuClose, + handleMenuOpen, + }; +}; + +const UserManagementRow: React.FC = (user) => { + const { + handleMenuOpen, + handleMenuClose, + menuOpen, + } = useUserManagementRow(); + const showMenu = menuOpen; + + return ( + <> + + + + {user.name} + + + + + + + + + + + View + + + Delete + + + + + + + + ); +}; + +export default UserManagementRow; diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx new file mode 100644 index 000000000..0b469800f --- /dev/null +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { + Table, + Text, + Thead, + Tbody, + Tr, + Th, + Button, + Flex, +} from '@fidesui/react'; +import { useDispatch, useSelector } from 'react-redux'; + +import debounce from 'lodash.debounce'; + +import { PrivacyRequest } from './types'; +import { + selectPrivacyRequestFilters, + useGetAllPrivacyRequestsQuery, + setPage, +} from './privacy-requests.slice'; + +import UserManagementRow from './UserManagementRow'; + +// const useUserManagementTable = () => { +// const dispatch = useDispatch(); + +// pagination ? +// const filters = useSelector(selectPrivacyRequestFilters); +// const [cachedFilters, setCachedFilters] = useState(filters); +// const updateCachedFilters = useRef( +// debounce((updatedFilters) => setCachedFilters(updatedFilters), 250) +// ); +// useEffect(() => { +// updateCachedFilters.current(filters); +// }, [setCachedFilters, filters]); + +// const handlePreviousPage = () => { +// dispatch(setPage(filters.page - 1)); +// }; + +// const handleNextPage = () => { +// dispatch(setPage(filters.page + 1)); +// }; + +// const { data, isLoading } = useGetAllPrivacyRequestsQuery(cachedFilters); +// const { items: requests, total } = data || { items: [], total: 0 }; +// return { +// ...filters, +// isLoading, +// requests, +// total, +// handleNextPage, +// handlePreviousPage, +// }; +// }; + +const UserManagementTable: React.FC = () => { +// const { requests, total, page, size, handleNextPage, handlePreviousPage } = +// useUserManagementTable(); +// const startingItem = (page - 1) * size + 1; +// const endingItem = Math.min(total, page * size); + return ( + <> + + + + + + + + {users.map((user) => ( + + ))} + +
User
+ {/* + + Showing {Number.isNaN(startingItem) ? 0 : startingItem} to{' '} + {Number.isNaN(endingItem) ? 0 : endingItem} of{' '} + {Number.isNaN(total) ? 0 : total} results + +
+ + +
+
*/} + + ); +}; + +// RequestTable.defaultProps = { +// requests: [], +// }; + +export default UserManagementTable; diff --git a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx new file mode 100644 index 000000000..bde586187 --- /dev/null +++ b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { + Flex, + Text, + Button, + Select, + Input, + InputGroup, + InputLeftElement, + InputLeftAddon, + Stack, + useToast, +} from '@fidesui/react'; + +import { + SearchLineIcon, +} from '../common/Icon'; +// import { statusPropMap } from './RequestBadge'; + +// import { PrivacyRequestStatus } from './types'; +// import { +// setRequestStatus, +// setRequestId, +// setRequestFrom, +// setRequestTo, +// clearAllFilters, +// selectPrivacyRequestFilters, +// requestCSVDownload, +// } from './privacy-requests.slice'; +// import { selectUserToken } from '../user/user.slice'; + +const useUserManagementTableActions = () => { + const filters = useSelector(selectPrivacyRequestFilters); + const token = useSelector(selectUserToken); + const dispatch = useDispatch(); + const toast = useToast(); + const handleSearchChange = (event: React.ChangeEvent) => { + dispatch(setRequestId(event.target.value)); + }; + + return { + handleSearchChange, + ...filters, + }; +}; + +const UserManagementTableActions: React.FC = () => { + const { + handleSearchChange, + id, + } = useUserManagementTableActions(); + return ( + + + + + + + + + + ); +}; + +export default UserManagementTableActions; diff --git a/clients/admin-ui/src/pages/index.tsx b/clients/admin-ui/src/pages/index.tsx index 1109d01ef..f9aef296d 100644 --- a/clients/admin-ui/src/pages/index.tsx +++ b/clients/admin-ui/src/pages/index.tsx @@ -12,6 +12,9 @@ import { ArrowDownLineIcon } from '../features/common/Icon'; import RequestTable from '../features/privacy-requests/RequestTable'; import RequestFilters from '../features/privacy-requests/RequestFilters'; +import UserManagementTable from '../features/user-management/UserManagementTable'; +import UserManagementTableActions from '../features/user-management/UserManagementTableActions'; + import { assignToken } from '../features/user/user.slice'; const Home: NextPage<{ session: { username: string } }> = ({ session }) => ( @@ -38,7 +41,7 @@ const Home: NextPage<{ session: { username: string } }> = ({ session }) => ( - @@ -181,4 +183,4 @@ const AddNewUser: NextPage = () => { ); }; -export default AddNewUser; +export default UserForm; From 1913522f666e048cace60326b73eea293e520beb Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Thu, 14 Apr 2022 01:03:14 -0500 Subject: [PATCH 03/51] Add set-up for some actions and profile page with form --- .../admin-ui/src/features/common/NavBar.tsx | 47 +++++++++++ .../src/features/user-management/UserForm.tsx | 20 ++--- .../user-management/UserManagementRow.tsx | 9 +- .../user-management/UserManagementTable.tsx | 84 +------------------ .../UserManagementTableActions.tsx | 50 ++--------- clients/admin-ui/src/pages/index.tsx | 38 +-------- .../src/pages/user-management/index.tsx | 68 +++++++++++++++ .../pages/user-management/user-profile.tsx | 44 ++++++++++ fidesops.toml | 2 +- 9 files changed, 185 insertions(+), 177 deletions(-) create mode 100644 clients/admin-ui/src/features/common/NavBar.tsx create mode 100644 clients/admin-ui/src/pages/user-management/index.tsx create mode 100644 clients/admin-ui/src/pages/user-management/user-profile.tsx diff --git a/clients/admin-ui/src/features/common/NavBar.tsx b/clients/admin-ui/src/features/common/NavBar.tsx new file mode 100644 index 000000000..a59c86cae --- /dev/null +++ b/clients/admin-ui/src/features/common/NavBar.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import {Flex, Button } from '@fidesui/react'; +import type { NextPage } from 'next'; +import NextLink from 'next/link' + +import { ArrowDownLineIcon } from '../../features/common/Icon'; + +import Header from './Header'; + +const NavBar: NextPage<{ session: { username: string } }> = ({ session }) => ( + <> +
+ + + + + + + + + + + + + + + + + + +); + +export default NavBar; \ No newline at end of file diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index 31a11da88..6155f3c4f 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import type { NextPage } from 'next'; -import Head from 'next/head'; +// import Head from 'next/head'; import { useFormik } from 'formik'; import { @@ -12,11 +12,8 @@ import { Button, FormErrorMessage, chakra, - // useToast, } from '@fidesui/react'; -// Can we use the same form for the create, view, and edit pages? The only difference is breadcrumbs - const useUserForm = () => { const [isLoading, setIsLoading] = useState(false); const formik = useFormik({ @@ -28,6 +25,7 @@ const useUserForm = () => { onSubmit: async (values) => { setIsLoading(true); // const response = await + // will create or update the user // }); setIsLoading(false); // if (response && response.ok) { @@ -35,7 +33,7 @@ const useUserForm = () => { // toast({ // status: 'error', // description: - // 'Creating new user failed.', + // 'Creating/Updating new user failed.', // }); // } }, @@ -77,13 +75,7 @@ const UserForm: NextPage = () => { } = useUserForm(); return (
- - FidesUI App - User Management - Add New User - - -
- {/* Breadcrumb component here */} Profile @@ -155,12 +147,14 @@ const UserForm: NextPage = () => { {/* Preferences checkboxes here */} + {/* These buttons should only exist when creating or updating user */} @@ -171,7 +165,7 @@ const UserForm: NextPage = () => { _hover={{ bg: 'primary.400' }} _active={{ bg: 'primary.500' }} colorScheme="primary" - disabled={!(isValid && dirty)} + // disabled={!(isValid && dirty)} size="sm" > Save diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index 304681ddd..ffbea5824 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -37,7 +37,7 @@ const UserManagementRow: React.FC = (user) => { return ( <> { size="sm" fontWeight="medium" > - {user.name} + {/* {user.name} */} + Name @@ -67,13 +68,13 @@ const UserManagementRow: React.FC = (user) => { View Delete diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index 0b469800f..4b832d41b 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -1,65 +1,15 @@ import React, { useEffect, useRef, useState } from 'react'; import { Table, - Text, Thead, Tbody, Tr, Th, - Button, - Flex, } from '@fidesui/react'; -import { useDispatch, useSelector } from 'react-redux'; -import debounce from 'lodash.debounce'; - -import { PrivacyRequest } from './types'; -import { - selectPrivacyRequestFilters, - useGetAllPrivacyRequestsQuery, - setPage, -} from './privacy-requests.slice'; - -import UserManagementRow from './UserManagementRow'; - -// const useUserManagementTable = () => { -// const dispatch = useDispatch(); - -// pagination ? -// const filters = useSelector(selectPrivacyRequestFilters); -// const [cachedFilters, setCachedFilters] = useState(filters); -// const updateCachedFilters = useRef( -// debounce((updatedFilters) => setCachedFilters(updatedFilters), 250) -// ); -// useEffect(() => { -// updateCachedFilters.current(filters); -// }, [setCachedFilters, filters]); - -// const handlePreviousPage = () => { -// dispatch(setPage(filters.page - 1)); -// }; - -// const handleNextPage = () => { -// dispatch(setPage(filters.page + 1)); -// }; - -// const { data, isLoading } = useGetAllPrivacyRequestsQuery(cachedFilters); -// const { items: requests, total } = data || { items: [], total: 0 }; -// return { -// ...filters, -// isLoading, -// requests, -// total, -// handleNextPage, -// handlePreviousPage, -// }; -// }; +// import UserManagementRow from './UserManagementRow'; const UserManagementTable: React.FC = () => { -// const { requests, total, page, size, handleNextPage, handlePreviousPage } = -// useUserManagementTable(); -// const startingItem = (page - 1) * size + 1; -// const endingItem = Math.min(total, page * size); return ( <> @@ -69,41 +19,13 @@ const UserManagementTable: React.FC = () => { - {users.map((user) => ( + {/* {users.map((user) => ( - ))} + ))} */}
- {/* - - Showing {Number.isNaN(startingItem) ? 0 : startingItem} to{' '} - {Number.isNaN(endingItem) ? 0 : endingItem} of{' '} - {Number.isNaN(total) ? 0 : total} results - -
- - -
-
*/} ); }; -// RequestTable.defaultProps = { -// requests: [], -// }; - export default UserManagementTable; diff --git a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx index bde586187..8c2f2142b 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx @@ -1,55 +1,17 @@ import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; import { - Flex, - Text, Button, - Select, Input, InputGroup, InputLeftElement, - InputLeftAddon, Stack, - useToast, } from '@fidesui/react'; import { SearchLineIcon, } from '../common/Icon'; -// import { statusPropMap } from './RequestBadge'; - -// import { PrivacyRequestStatus } from './types'; -// import { -// setRequestStatus, -// setRequestId, -// setRequestFrom, -// setRequestTo, -// clearAllFilters, -// selectPrivacyRequestFilters, -// requestCSVDownload, -// } from './privacy-requests.slice'; -// import { selectUserToken } from '../user/user.slice'; - -const useUserManagementTableActions = () => { - const filters = useSelector(selectPrivacyRequestFilters); - const token = useSelector(selectUserToken); - const dispatch = useDispatch(); - const toast = useToast(); - const handleSearchChange = (event: React.ChangeEvent) => { - dispatch(setRequestId(event.target.value)); - }; - - return { - handleSearchChange, - ...filters, - }; -}; const UserManagementTableActions: React.FC = () => { - const { - handleSearchChange, - id, - } = useUserManagementTableActions(); return ( @@ -59,19 +21,21 @@ const UserManagementTableActions: React.FC = () => { diff --git a/clients/admin-ui/src/pages/index.tsx b/clients/admin-ui/src/pages/index.tsx index f9aef296d..93ddb5859 100644 --- a/clients/admin-ui/src/pages/index.tsx +++ b/clients/admin-ui/src/pages/index.tsx @@ -2,19 +2,14 @@ import React from 'react'; import type { NextPage } from 'next'; import Head from 'next/head'; import { getSession } from 'next-auth/react'; -import { Flex, Heading, Button, Box } from '@fidesui/react'; +import { Heading, Box } from '@fidesui/react'; import { wrapper } from '../app/store'; -import Header from '../features/common/Header'; - -import { ArrowDownLineIcon } from '../features/common/Icon'; +import NavBar from '../features/common/NavBar'; import RequestTable from '../features/privacy-requests/RequestTable'; import RequestFilters from '../features/privacy-requests/RequestFilters'; -import UserManagementTable from '../features/user-management/UserManagementTable'; -import UserManagementTableActions from '../features/user-management/UserManagementTableActions'; - import { assignToken } from '../features/user/user.slice'; const Home: NextPage<{ session: { username: string } }> = ({ session }) => ( @@ -25,29 +20,9 @@ const Home: NextPage<{ session: { username: string } }> = ({ session }) => ( -
+
- - - - - - Subject Requests @@ -55,13 +30,6 @@ const Home: NextPage<{ session: { username: string } }> = ({ session }) => ( - - - User Management - - - -
); diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx new file mode 100644 index 000000000..8ccae86da --- /dev/null +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import type { NextPage } from 'next'; +import Head from 'next/head'; +import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Heading } from '@fidesui/react'; + +import NavBar from '../../features/common/NavBar'; + +import { getSession } from 'next-auth/react'; +import { wrapper } from '../../app/store'; +import { assignToken } from '../../features/user/user.slice'; + +// import UserManagementTable from '../features/user-management/UserManagementTable'; +import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; + +const UserManagement: NextPage<{ session: { username: string } }> = ({ session }) => ( +
+ + + Fides Admin UI - User Management + + + + + + +
+ + + User Management + + {/* + + User Management + + + + + + + + + + */} + + {/* */} + +
+
+); + +export const getServerSideProps = wrapper.getServerSideProps( + (store) => async (context) => { + const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { + await store.dispatch(assignToken(session.accessToken)); + return { props: { session } }; + } + + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } + ); + +export default UserManagement; diff --git a/clients/admin-ui/src/pages/user-management/user-profile.tsx b/clients/admin-ui/src/pages/user-management/user-profile.tsx new file mode 100644 index 000000000..13cedff78 --- /dev/null +++ b/clients/admin-ui/src/pages/user-management/user-profile.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import type { NextPage } from 'next'; +import Head from 'next/head'; +import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Heading } from '@fidesui/react'; + +import NavBar from '../../features/common/NavBar'; + +import { getSession } from 'next-auth/react'; +import { wrapper } from '../../app/store'; +import { assignToken } from '../../features/user/user.slice'; + +// import UserManagementTable from '../features/user-management/UserManagementTable'; +// import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; +import UserForm from '../../features/user-management/UserForm'; + +const UserProfile: NextPage<{ session: { username: string } }> = ({ session }) => ( +
+ +
+ + + +
+
+); + +export const getServerSideProps = wrapper.getServerSideProps( + (store) => async (context) => { + const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { + await store.dispatch(assignToken(session.accessToken)); + return { props: { session } }; + } + + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } + ); + +export default UserProfile; diff --git a/fidesops.toml b/fidesops.toml index fa5ec5bdc..e799ea622 100644 --- a/fidesops.toml +++ b/fidesops.toml @@ -24,5 +24,5 @@ OAUTH_ROOT_CLIENT_SECRET="fidesopsadminsecret" TASK_RETRY_COUNT=2 TASK_RETRY_DELAY=5 TASK_RETRY_BACKOFF=2 -REQUIRE_MANUAL_REQUEST_APPROVAL=false +REQUIRE_MANUAL_REQUEST_APPROVAL=true MASKING_STRICT=true From c885721fb434b2575ac0f41e25482d357c93fb01 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 15 Apr 2022 14:04:52 -0500 Subject: [PATCH 04/51] Work on new user --- .../{UserForm.tsx => NewUserForm.tsx} | 128 +++++++++++------- .../user-management/UserManagementRow.tsx | 2 + .../UserManagementTableActions.tsx | 31 +++-- .../user-management/config/config.json | 6 + .../{user-profile.tsx => new.tsx} | 9 +- .../pages/user-management/profile/[id].tsx | 43 ++++++ 6 files changed, 156 insertions(+), 63 deletions(-) rename clients/admin-ui/src/features/user-management/{UserForm.tsx => NewUserForm.tsx} (63%) create mode 100644 clients/admin-ui/src/features/user-management/config/config.json rename clients/admin-ui/src/pages/user-management/{user-profile.tsx => new.tsx} (83%) create mode 100644 clients/admin-ui/src/pages/user-management/profile/[id].tsx diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx similarity index 63% rename from clients/admin-ui/src/features/user-management/UserForm.tsx rename to clients/admin-ui/src/features/user-management/NewUserForm.tsx index 6155f3c4f..84ed89dda 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -1,20 +1,22 @@ import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; -// import Head from 'next/head'; import { useFormik } from 'formik'; - import { - Stack, - Heading, - FormControl, - FormLabel, - Input, Button, - FormErrorMessage, chakra, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + Input, + Stack, } from '@fidesui/react'; +import config from './config/config.json'; +import { selectUserToken } from '../user/user.slice'; -const useUserForm = () => { +const useNewUserForm = () => { + const token = useSelector(selectUserToken); const [isLoading, setIsLoading] = useState(false); const formik = useFormik({ initialValues: { @@ -24,18 +26,41 @@ const useUserForm = () => { }, onSubmit: async (values) => { setIsLoading(true); - // const response = await - // will create or update the user - // }); - setIsLoading(false); - // if (response && response.ok) { - // } else { - // toast({ - // status: 'error', - // description: - // 'Creating/Updating new user failed.', - // }); - // } + const host = + process.env.NODE_ENV === 'development' + ? config.fidesops_host_development + : config.fidesops_host_production; + + const body = + { + "username": values.username, + "name": values.name, + "password": values.password, + } + ; + + try { + const response = await fetch(`${host}/user`, { + method: 'POST', + headers: { + 'Access-Control-Allow-Origin': '*', + 'authorization': `Bearer ${token}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + const data = await response.json(); + + if (data.succeeded.length) { + console.log("Success") + } + + } catch (error) { + console.log("Error") + return; + } }, validate: (values) => { const errors: { @@ -49,7 +74,7 @@ const useUserForm = () => { } if (!values.name) { - errors.username = 'Name is required'; + errors.name = 'Name is required'; } if (!values.password) { @@ -63,16 +88,19 @@ const useUserForm = () => { return { ...formik, isLoading }; }; -const UserForm: NextPage = () => { +const NewUserForm: NextPage = () => { const { + dirty, errors, handleBlur, handleChange, handleSubmit, + isValid, isLoading, touched, values, - } = useUserForm(); + } = useNewUserForm(); + return (
@@ -146,30 +174,32 @@ const UserForm: NextPage = () => { {errors.password} - {/* Preferences checkboxes here */} - {/* These buttons should only exist when creating or updating user */} - - + {/* PREFERENCES BOX HERE */} + + <> + + +
@@ -177,4 +207,4 @@ const UserForm: NextPage = () => { ); }; -export default UserForm; +export default NewUserForm; diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index ffbea5824..ff11607ae 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -69,12 +69,14 @@ const UserManagementRow: React.FC = (user) => { View Delete diff --git a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx index 8c2f2142b..360532cc9 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import NextLink from 'next/link'; import { Button, Input, @@ -11,7 +12,16 @@ import { SearchLineIcon, } from '../common/Icon'; +// const useUserManagementTableActions = () => { +// return { + +// }; +// } + const UserManagementTableActions: React.FC = () => { + // const { + // } = useUserManagementTableActions(); + return ( @@ -29,16 +39,17 @@ const UserManagementTableActions: React.FC = () => { // onChange={handleSearch} /> - + + + ); }; diff --git a/clients/admin-ui/src/features/user-management/config/config.json b/clients/admin-ui/src/features/user-management/config/config.json new file mode 100644 index 000000000..8b7af2db0 --- /dev/null +++ b/clients/admin-ui/src/features/user-management/config/config.json @@ -0,0 +1,6 @@ +{ + "title": "User Management", + "description": "", + "fidesops_host_development": "http://localhost:8080/api/v1", + "fidesops_host_production": "" +} \ No newline at end of file diff --git a/clients/admin-ui/src/pages/user-management/user-profile.tsx b/clients/admin-ui/src/pages/user-management/new.tsx similarity index 83% rename from clients/admin-ui/src/pages/user-management/user-profile.tsx rename to clients/admin-ui/src/pages/user-management/new.tsx index 13cedff78..dffa00982 100644 --- a/clients/admin-ui/src/pages/user-management/user-profile.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -11,14 +11,15 @@ import { assignToken } from '../../features/user/user.slice'; // import UserManagementTable from '../features/user-management/UserManagementTable'; // import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; -import UserForm from '../../features/user-management/UserForm'; +import NewUserForm from '../../features/user-management/NewUserForm'; -const UserProfile: NextPage<{ session: { username: string } }> = ({ session }) => ( +const CreateNewUser: NextPage<{ session: { username: string } }> = ({ session }) => (
+ {/* BREADCRUMBS */} - +
@@ -41,4 +42,4 @@ export const getServerSideProps = wrapper.getServerSideProps( } ); -export default UserProfile; +export default CreateNewUser; diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx new file mode 100644 index 000000000..94fb2e5bd --- /dev/null +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import type { NextPage } from 'next'; +import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Heading } from '@fidesui/react'; + +import NavBar from '../../../features/common/NavBar'; + +import { getSession } from 'next-auth/react'; +import { wrapper } from '../../../app/store'; +import { assignToken } from '../../../features/user/user.slice'; + +// import UserForm from '../../features/user-management/UserForm'; + +const Profile: NextPage<{ session: { username: string } }> = ({ session }) => ( +
+ +
+ {/* BREADCRUMBS */} + + {/* */} + Profile page to view and edit user info + +
+
+); + +export const getServerSideProps = wrapper.getServerSideProps( + (store) => async (context) => { + const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { + await store.dispatch(assignToken(session.accessToken)); + return { props: { session } }; + } + + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } + ); + +export default Profile; From 533ecf1a033e3cf3543008604674753dc9cc402e Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 15 Apr 2022 14:10:10 -0500 Subject: [PATCH 05/51] Update row --- .../admin-ui/src/features/user-management/UserManagementRow.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index ff11607ae..b9c20a9a4 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -39,7 +39,6 @@ const UserManagementRow: React.FC = (user) => { From 04a1a2e026fddc5505a78aa4800468c5231db190 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 15 Apr 2022 14:34:13 -0500 Subject: [PATCH 06/51] Clean-up navbar --- .../admin-ui/src/features/common/NavBar.tsx | 12 ++++++---- clients/admin-ui/src/pages/index.tsx | 7 +++--- .../src/pages/user-management/index.tsx | 23 +----------------- .../src/pages/user-management/new.tsx | 24 +------------------ .../pages/user-management/profile/[id].tsx | 23 +----------------- 5 files changed, 14 insertions(+), 75 deletions(-) diff --git a/clients/admin-ui/src/features/common/NavBar.tsx b/clients/admin-ui/src/features/common/NavBar.tsx index a59c86cae..667fea75d 100644 --- a/clients/admin-ui/src/features/common/NavBar.tsx +++ b/clients/admin-ui/src/features/common/NavBar.tsx @@ -1,15 +1,18 @@ import React from 'react'; import {Flex, Button } from '@fidesui/react'; -import type { NextPage } from 'next'; +import { useSession } from "next-auth/react" import NextLink from 'next/link' import { ArrowDownLineIcon } from '../../features/common/Icon'; import Header from './Header'; -const NavBar: NextPage<{ session: { username: string } }> = ({ session }) => ( +const NavBar = () => { + const { data: session } = useSession() + + return ( <> -
+
= ({ session }) => ( -); + ) +} export default NavBar; \ No newline at end of file diff --git a/clients/admin-ui/src/pages/index.tsx b/clients/admin-ui/src/pages/index.tsx index 93ddb5859..e0614c04d 100644 --- a/clients/admin-ui/src/pages/index.tsx +++ b/clients/admin-ui/src/pages/index.tsx @@ -1,17 +1,16 @@ import React from 'react'; import type { NextPage } from 'next'; import Head from 'next/head'; -import { getSession } from 'next-auth/react'; import { Heading, Box } from '@fidesui/react'; +import { getSession } from 'next-auth/react'; import { wrapper } from '../app/store'; +import { assignToken } from '../features/user/user.slice'; import NavBar from '../features/common/NavBar'; import RequestTable from '../features/privacy-requests/RequestTable'; import RequestFilters from '../features/privacy-requests/RequestFilters'; -import { assignToken } from '../features/user/user.slice'; - const Home: NextPage<{ session: { username: string } }> = ({ session }) => (
@@ -20,7 +19,7 @@ const Home: NextPage<{ session: { username: string } }> = ({ session }) => ( - +
diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx index 8ccae86da..d82228457 100644 --- a/clients/admin-ui/src/pages/user-management/index.tsx +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -5,10 +5,6 @@ import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Heading } from '@fides import NavBar from '../../features/common/NavBar'; -import { getSession } from 'next-auth/react'; -import { wrapper } from '../../app/store'; -import { assignToken } from '../../features/user/user.slice'; - // import UserManagementTable from '../features/user-management/UserManagementTable'; import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; @@ -21,7 +17,7 @@ const UserManagement: NextPage<{ session: { username: string } }> = ({ session } - +
@@ -48,21 +44,4 @@ const UserManagement: NextPage<{ session: { username: string } }> = ({ session }
); -export const getServerSideProps = wrapper.getServerSideProps( - (store) => async (context) => { - const session = await getSession(context); - if (session && typeof session.accessToken !== 'undefined') { - await store.dispatch(assignToken(session.accessToken)); - return { props: { session } }; - } - - return { - redirect: { - destination: '/login', - permanent: false, - }, - }; - } - ); - export default UserManagement; diff --git a/clients/admin-ui/src/pages/user-management/new.tsx b/clients/admin-ui/src/pages/user-management/new.tsx index dffa00982..59d46c475 100644 --- a/clients/admin-ui/src/pages/user-management/new.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -1,21 +1,16 @@ import React from 'react'; import type { NextPage } from 'next'; -import Head from 'next/head'; import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Heading } from '@fidesui/react'; import NavBar from '../../features/common/NavBar'; -import { getSession } from 'next-auth/react'; -import { wrapper } from '../../app/store'; -import { assignToken } from '../../features/user/user.slice'; - // import UserManagementTable from '../features/user-management/UserManagementTable'; // import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; import NewUserForm from '../../features/user-management/NewUserForm'; const CreateNewUser: NextPage<{ session: { username: string } }> = ({ session }) => (
- +
{/* BREADCRUMBS */} @@ -25,21 +20,4 @@ const CreateNewUser: NextPage<{ session: { username: string } }> = ({ session })
); -export const getServerSideProps = wrapper.getServerSideProps( - (store) => async (context) => { - const session = await getSession(context); - if (session && typeof session.accessToken !== 'undefined') { - await store.dispatch(assignToken(session.accessToken)); - return { props: { session } }; - } - - return { - redirect: { - destination: '/login', - permanent: false, - }, - }; - } - ); - export default CreateNewUser; diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index 94fb2e5bd..da09d4bf3 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -4,15 +4,11 @@ import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Heading } from '@fides import NavBar from '../../../features/common/NavBar'; -import { getSession } from 'next-auth/react'; -import { wrapper } from '../../../app/store'; -import { assignToken } from '../../../features/user/user.slice'; - // import UserForm from '../../features/user-management/UserForm'; const Profile: NextPage<{ session: { username: string } }> = ({ session }) => (
- +
{/* BREADCRUMBS */} @@ -23,21 +19,4 @@ const Profile: NextPage<{ session: { username: string } }> = ({ session }) => (
); -export const getServerSideProps = wrapper.getServerSideProps( - (store) => async (context) => { - const session = await getSession(context); - if (session && typeof session.accessToken !== 'undefined') { - await store.dispatch(assignToken(session.accessToken)); - return { props: { session } }; - } - - return { - redirect: { - destination: '/login', - permanent: false, - }, - }; - } - ); - export default Profile; From 037c892e4fa45995be453524efd6f9e5988ac2c4 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 15 Apr 2022 18:01:24 -0500 Subject: [PATCH 07/51] Set active classnames in nav links --- clients/admin-ui/src/features/common/NavBar.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/clients/admin-ui/src/features/common/NavBar.tsx b/clients/admin-ui/src/features/common/NavBar.tsx index 667fea75d..1b981abff 100644 --- a/clients/admin-ui/src/features/common/NavBar.tsx +++ b/clients/admin-ui/src/features/common/NavBar.tsx @@ -2,17 +2,20 @@ import React from 'react'; import {Flex, Button } from '@fidesui/react'; import { useSession } from "next-auth/react" import NextLink from 'next/link' +import { useRouter } from "next/router"; import { ArrowDownLineIcon } from '../../features/common/Icon'; import Header from './Header'; -const NavBar = () => { - const { data: session } = useSession() +const NavBar = (activePage: boolean) => { + const { data: session } = useSession(); + const router = useRouter(); + const username: string | any = session?.username return ( <> -
+
{ borderColor="gray.100" > - @@ -33,7 +36,7 @@ const NavBar = () => { - From eb9caa42ae4636edc04c0cd2e59bbdf85d76793c Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 15 Apr 2022 18:49:28 -0500 Subject: [PATCH 08/51] Add privileges --- .../admin-ui/src/features/common/NavBar.tsx | 2 +- .../features/user-management/NewUserForm.tsx | 106 +++++++++++++----- 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/clients/admin-ui/src/features/common/NavBar.tsx b/clients/admin-ui/src/features/common/NavBar.tsx index 1b981abff..6bfae0c03 100644 --- a/clients/admin-ui/src/features/common/NavBar.tsx +++ b/clients/admin-ui/src/features/common/NavBar.tsx @@ -8,7 +8,7 @@ import { ArrowDownLineIcon } from '../../features/common/Icon'; import Header from './Header'; -const NavBar = (activePage: boolean) => { +const NavBar = () => { const { data: session } = useSession(); const router = useRouter(); const username: string | any = session?.username diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx index 84ed89dda..969851ab2 100644 --- a/clients/admin-ui/src/features/user-management/NewUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -5,16 +5,59 @@ import { useFormik } from 'formik'; import { Button, chakra, + Checkbox, + CheckboxGroup, FormControl, FormErrorMessage, FormLabel, Heading, Input, Stack, + Text, } from '@fidesui/react'; import config from './config/config.json'; import { selectUserToken } from '../user/user.slice'; +interface Privilege { + privilege: string; + description: string; +} + +export const userPrivilegesArray: Privilege[] = [ + { + privilege: 'View subject requests', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Approve subject requests', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'View datastore connections', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Manage datastore connections', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'View policies', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Create policies', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Create users', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Create roles', + description: 'Instructional line about these particular user preferences', + }, +]; + const useNewUserForm = () => { const token = useSelector(selectUserToken); const [isLoading, setIsLoading] = useState(false); @@ -104,7 +147,7 @@ const NewUserForm: NextPage = () => { return (
- + Profile { {errors.password} - {/* PREFERENCES BOX HERE */} + + Preferences + + Select privileges to assign to this user + + + {userPrivilegesArray.map((policy, idx) => ( + <> + {policy.privilege} +
{policy.description}
+ + ))} +
+
- <> - - - + +
From c8913a24a4260f3dab082cfa982995b371772831 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 15 Apr 2022 18:59:39 -0500 Subject: [PATCH 09/51] Cancel functionality on new user form --- .../features/user-management/NewUserForm.tsx | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx index 969851ab2..904cc006f 100644 --- a/clients/admin-ui/src/features/user-management/NewUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; +import NextLink from 'next/link'; import { useFormik } from 'formik'; import { Button, @@ -231,20 +232,18 @@ const NewUserForm: NextPage = () => { ))} + - + + + - +
From abd4c586965968b22c4e1f76e71a9ff5423f28f5 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 22 Apr 2022 22:38:30 -0500 Subject: [PATCH 10/51] User slice --- clients/admin-ui/src/app/store.ts | 8 +- .../{NewUserForm.tsx => UserForm.tsx} | 24 ++++-- .../user-management/UserManagementTable.tsx | 1 + clients/admin-ui/src/features/user/types.ts | 25 ++++++ .../admin-ui/src/features/user/user.slice.ts | 86 +++++++++++++++++++ .../src/pages/user-management/new.tsx | 4 +- 6 files changed, 139 insertions(+), 9 deletions(-) rename clients/admin-ui/src/features/user-management/{NewUserForm.tsx => UserForm.tsx} (94%) create mode 100644 clients/admin-ui/src/features/user/types.ts diff --git a/clients/admin-ui/src/app/store.ts b/clients/admin-ui/src/app/store.ts index 2a07bff15..1a1876661 100644 --- a/clients/admin-ui/src/app/store.ts +++ b/clients/admin-ui/src/app/store.ts @@ -6,17 +6,21 @@ import { reducer as privacyRequestsReducer, privacyRequestApi, } from '../features/privacy-requests'; -import { reducer as userReducer } from '../features/user'; +import { + reducer as userReducer, + userApi, +} from '../features/user'; const makeStore = () => { const store = configureStore({ reducer: { [privacyRequestApi.reducerPath]: privacyRequestApi.reducer, subjectRequests: privacyRequestsReducer, + [userApi.reducerPath]: userApi.reducer, user: userReducer, }, middleware: (getDefaultMiddleware) => - getDefaultMiddleware().concat(privacyRequestApi.middleware), + getDefaultMiddleware().concat(privacyRequestApi.middleware, userApi.middleware), devTools: true, }); setupListeners(store.dispatch); diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx similarity index 94% rename from clients/admin-ui/src/features/user-management/NewUserForm.tsx rename to clients/admin-ui/src/features/user-management/UserForm.tsx index 904cc006f..68568a799 100644 --- a/clients/admin-ui/src/features/user-management/NewUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -59,9 +59,16 @@ export const userPrivilegesArray: Privilege[] = [ }, ]; -const useNewUserForm = () => { +const useUserForm = () => { + // const dispatch = useDispatch(); const token = useSelector(selectUserToken); const [isLoading, setIsLoading] = useState(false); + + // Initial values - GET individual user values if coming from the ID path + // useEffect(() => { + // // Get user values + // }, []); + const formik = useFormik({ initialValues: { username: '', @@ -129,10 +136,17 @@ const useNewUserForm = () => { }, }); - return { ...formik, isLoading }; + // const { data, isLoading } = useGetUserQuery(userId); + // const { items } = data || { items: [] }; + + return { + ...formik, + isLoading, + // items + }; }; -const NewUserForm: NextPage = () => { +const UserForm: NextPage = () => { const { dirty, errors, @@ -143,7 +157,7 @@ const NewUserForm: NextPage = () => { isLoading, touched, values, - } = useNewUserForm(); + } = useUserForm(); return (
@@ -259,4 +273,4 @@ const NewUserForm: NextPage = () => { ); }; -export default NewUserForm; +export default UserForm; diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index 4b832d41b..7ea2f33e5 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -19,6 +19,7 @@ const UserManagementTable: React.FC = () => { + {/* Blocked until GET users is implemented */} {/* {users.map((user) => ( ))} */} diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts new file mode 100644 index 000000000..1dcd28709 --- /dev/null +++ b/clients/admin-ui/src/features/user/types.ts @@ -0,0 +1,25 @@ +export interface User { + name: string; + username: string; + password: string; + id: string; +} + +export interface UsersResponse { + users: User[]; +} + +export interface UsersParams { + search: string; +} + +export interface UserResponse { + name: string; + username: string; + password: string; + id: string; +} + +export interface UserParams { + id: string; +} diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index b88da6c6f..de3d1e4d2 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -1,7 +1,16 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { HYDRATE } from 'next-redux-wrapper'; import type { AppState } from '../../app/store'; +import { + UserParams, + UserResponse, + UsersParams, + UsersResponse, + User, +} from './types'; + export interface State { token: string | null; } @@ -10,6 +19,83 @@ const initialState: State = { token: null, }; +// User API +export const userApi = createApi({ + reducerPath: 'userApi', + baseQuery: fetchBaseQuery({ + baseUrl: process.env.NEXT_PUBLIC_FIDESOPS_API!, + prepareHeaders: (headers, { getState }) => { + const { token } = (getState() as AppState).user; + headers.set('Access-Control-Allow-Origin', '*'); + if (token) { + headers.set('authorization', `Bearer ${token}`); + } + return headers; + }, + }), + tagTypes: ['User'], + endpoints: (build) => ({ + getAllUsers: build.query< + UsersResponse, + UsersParams + >({ + query: () => ({ url: `users` }), + providesTags: () => ['User'], + }), + getUserById: build.query< + UserResponse, + UserParams + >({ + query: (id) => ({ url: `user/${id}` }), + providesTags: () => ['User'], + }), + editUser: build.mutation< + User, + Partial & Pick + >({ + query: ({ id, ...patch }) => ({ + url: `user/${id}`, + method: 'PATCH', + body: patch, + }), + invalidatesTags: ['User'], + // For optimistic update + async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) { + const patchResult = dispatch( + userApi.util.updateQueryData('getUserById', { id }, (draft) => { + Object.assign(draft, patch) + }) + ) + try { + await queryFulfilled + } catch { + patchResult.undo() + /** + * Alternatively, on failure you can invalidate the corresponding cache tags + * to trigger a re-fetch: + * dispatch(api.util.invalidateTags(['User'])) + */ + } + }, + }), + deleteUser: build.mutation<{ success: boolean; id: number }, number>({ + query: (id) => ({ + url: `user/${id}`, + method: 'DELETE', + }), + // Invalidates all queries that subscribe to this User `id` only + invalidatesTags: (result, error, id) => [{ type: 'User', id }], + }), + }), +}); + +export const { + useGetAllUsersQuery, + useGetUserByIdQuery, + useEditUserMutation, + useDeleteUserMutation, +} = userApi; + export const userSlice = createSlice({ name: 'user', initialState, diff --git a/clients/admin-ui/src/pages/user-management/new.tsx b/clients/admin-ui/src/pages/user-management/new.tsx index 59d46c475..7deaf4a33 100644 --- a/clients/admin-ui/src/pages/user-management/new.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -6,7 +6,7 @@ import NavBar from '../../features/common/NavBar'; // import UserManagementTable from '../features/user-management/UserManagementTable'; // import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; -import NewUserForm from '../../features/user-management/NewUserForm'; +import UserForm from '../../features/user-management/UserForm'; const CreateNewUser: NextPage<{ session: { username: string } }> = ({ session }) => (
@@ -14,7 +14,7 @@ const CreateNewUser: NextPage<{ session: { username: string } }> = ({ session })
{/* BREADCRUMBS */} - +
From f8a7d7d174840fd4b0e06a8b0a1aee7cf4d9539a Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 22 Apr 2022 23:00:33 -0500 Subject: [PATCH 11/51] Delete user modal --- .../user-management/DeleteUserModal.tsx | 53 +++++++++++++++++++ .../user-management/UserManagementRow.tsx | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 clients/admin-ui/src/features/user-management/DeleteUserModal.tsx diff --git a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx new file mode 100644 index 000000000..a7c356283 --- /dev/null +++ b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx @@ -0,0 +1,53 @@ +import { + Button, + FormControl, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + useDisclosure, + } from '@fidesui/react'; + +function DeleteUserModal() { + const { isOpen, onOpen, onClose } = useDisclosure() + + return ( + <> + + + + + Delete User + + + + User name + + + + Confirm User name + + + + + + + + + + + + ) + } + +export default DeleteUserModal; diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index b9c20a9a4..565555589 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -70,7 +70,7 @@ const UserManagementRow: React.FC = (user) => { // onClick={handleViewUser} // redirects to specific profile/[id] page > - View + Edit Date: Sat, 23 Apr 2022 20:25:53 -0500 Subject: [PATCH 12/51] Breadcrumbs and delete user modal as menuitem --- .../user-management/DeleteUserModal.tsx | 20 +++++++++++-- .../user-management/UserManagementRow.tsx | 28 ++++++------------- .../user-management/UserManagementTable.tsx | 5 ++-- .../UserManagementTableActions.tsx | 28 +++++++++++++------ .../admin-ui/src/features/user/user.slice.ts | 13 ++++++++- .../src/pages/user-management/index.tsx | 19 ++----------- .../src/pages/user-management/new.tsx | 13 ++++++++- .../pages/user-management/profile/[id].tsx | 14 ++++++++-- 8 files changed, 86 insertions(+), 54 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx index a7c356283..6e04da14b 100644 --- a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx +++ b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx @@ -3,6 +3,7 @@ import { FormControl, FormLabel, Input, + MenuItem, Modal, ModalBody, ModalCloseButton, @@ -10,15 +11,24 @@ import { ModalFooter, ModalHeader, ModalOverlay, + Text, useDisclosure, } from '@fidesui/react'; -function DeleteUserModal() { +function DeleteUserModal(user) { const { isOpen, onOpen, onClose } = useDisclosure() + // const deleteUser(id) { + // // call delete user from API and delete by id here + // } return ( <> - + + Delete + - diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index 565555589..aa5cc9a70 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -1,6 +1,5 @@ -import React, { useRef, useState } from 'react'; +import React, { useState } from 'react'; import { - Tag, Text, Tr, Td, @@ -14,6 +13,8 @@ import { } from '@fidesui/react'; import { MoreIcon } from '../common/Icon'; +import DeleteUserModal from './DeleteUserModal'; + const useUserManagementRow = () => { const [menuOpen, setMenuOpen] = useState(false); const handleMenuOpen = () => setMenuOpen(true); @@ -42,18 +43,10 @@ const UserManagementRow: React.FC = (user) => { height="36px" > - {/* {user.name} */} Name - + { Edit - - Delete - + {DeleteUserModal(user)} + ); diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index 7ea2f33e5..d06edb59c 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -7,7 +7,7 @@ import { Th, } from '@fidesui/react'; -// import UserManagementRow from './UserManagementRow'; +import UserManagementRow from './UserManagementRow'; const UserManagementTable: React.FC = () => { return ( @@ -15,7 +15,7 @@ const UserManagementTable: React.FC = () => { - + @@ -23,6 +23,7 @@ const UserManagementTable: React.FC = () => { {/* {users.map((user) => ( ))} */} +
UserName
diff --git a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx index 360532cc9..b3d05bf1a 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import NextLink from 'next/link'; import { Button, @@ -12,15 +13,24 @@ import { SearchLineIcon, } from '../common/Icon'; -// const useUserManagementTableActions = () => { -// return { - -// }; -// } +import { setUserId } from '../user/user.slice'; + +const useUserManagementTableActions = () => { + const dispatch = useDispatch(); + const handleSearchChange = (event: React.ChangeEvent) => { + dispatch(setUserId(event.target.value)); + }; + + return { + handleSearchChange, + }; +} const UserManagementTableActions: React.FC = () => { - // const { - // } = useUserManagementTableActions(); + const { + handleSearchChange, + id, + } = useUserManagementTableActions(); return ( @@ -34,9 +44,9 @@ const UserManagementTableActions: React.FC = () => { placeholder="Search by Name or Username" size="sm" borderRadius="md" - // value={id} + value={id} name="search" - // onChange={handleSearch} + onChange={handleSearchChange} /> diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index de3d1e4d2..18d97556f 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -12,10 +12,16 @@ import { } from './types'; export interface State { + id: string; + page: number; + size: number; token: string | null; } const initialState: State = { + id: '', + page: 1, + size: 25, token: null, }; @@ -104,6 +110,11 @@ export const userSlice = createSlice({ ...state, token: action.payload, }), + setUserId: (state, action: PayloadAction) => ({ + ...state, + page: initialState.page, + id: action.payload, + }), }, extraReducers: { [HYDRATE]: (state, action) => ({ @@ -113,7 +124,7 @@ export const userSlice = createSlice({ }, }); -export const { assignToken } = userSlice.actions; +export const { assignToken, setUserId } = userSlice.actions; export const selectUserToken = (state: AppState) => state.user.token; diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx index d82228457..ce0d823a0 100644 --- a/clients/admin-ui/src/pages/user-management/index.tsx +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -1,11 +1,11 @@ import React from 'react'; import type { NextPage } from 'next'; import Head from 'next/head'; -import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Heading } from '@fidesui/react'; +import { Box, Heading } from '@fidesui/react'; import NavBar from '../../features/common/NavBar'; -// import UserManagementTable from '../features/user-management/UserManagementTable'; +import UserManagementTable from '../../features/user-management/UserManagementTable'; import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; const UserManagement: NextPage<{ session: { username: string } }> = ({ session }) => ( @@ -24,21 +24,8 @@ const UserManagement: NextPage<{ session: { username: string } }> = ({ session } User Management - {/* - - User Management - - - - - - - - - - */} - {/* */} +
diff --git a/clients/admin-ui/src/pages/user-management/new.tsx b/clients/admin-ui/src/pages/user-management/new.tsx index 7deaf4a33..851f95135 100644 --- a/clients/admin-ui/src/pages/user-management/new.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -12,8 +12,19 @@ const CreateNewUser: NextPage<{ session: { username: string } }> = ({ session })
- {/* BREADCRUMBS */} + + User Management + + + User Management + + + + Add New User + + +
diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index da09d4bf3..c743cd1a9 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -10,9 +10,19 @@ const Profile: NextPage<{ session: { username: string } }> = ({ session }) => (
- {/* BREADCRUMBS */} - {/* */} + + User Management + + + User Management + + + + Edit User + + + Profile page to view and edit user info
From 271e21bfa0fa070a71b5ce1081d3af0009ec72e5 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Sat, 23 Apr 2022 21:59:22 -0500 Subject: [PATCH 13/51] User POST in slice --- .../src/features/user-management/UserForm.tsx | 24 +------------ .../user-management/UserManagementTable.tsx | 35 ++++++++++++++++++- .../UserManagementTableActions.tsx | 8 +++-- clients/admin-ui/src/features/user/types.ts | 6 ++-- .../admin-ui/src/features/user/user.slice.ts | 33 ++++++++++++++++- 5 files changed, 76 insertions(+), 30 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index 68568a799..1d20ac5d5 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -89,29 +89,7 @@ const useUserForm = () => { "password": values.password, } ; - - try { - const response = await fetch(`${host}/user`, { - method: 'POST', - headers: { - 'Access-Control-Allow-Origin': '*', - 'authorization': `Bearer ${token}`, - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }); - - const data = await response.json(); - - if (data.succeeded.length) { - console.log("Success") - } - - } catch (error) { - console.log("Error") - return; - } + // use POST action }, validate: (values) => { const errors: { diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index d06edb59c..0478a7f5d 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -1,4 +1,5 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { Table, Thead, @@ -9,7 +10,39 @@ import { import UserManagementRow from './UserManagementRow'; +import { selectUserFilters, useGetAllUsersQuery } from '../user/user.slice'; + +const useUsersTable = () => { + const dispatch = useDispatch(); + const filters = useSelector(selectUserFilters); + + // const handlePreviousPage = () => { + // dispatch(setPage(filters.page - 1)); + // }; + + // const handleNextPage = () => { + // dispatch(setPage(filters.page + 1)); + // }; + + const { data, isLoading } = useGetAllUsersQuery(filters); + const { users } = data || { users: [] }; + return { + ...filters, + isLoading, + users, + // handleNextPage, + // handlePreviousPage, + }; +} + const UserManagementTable: React.FC = () => { + const { users, + // page, size, handleNextPage, handlePreviousPage + } = + useUsersTable(); + // const startingItem = (page - 1) * size + 1; + // const endingItem = Math.min(total, page * size); + return ( <> diff --git a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx index b3d05bf1a..0635fdfcb 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx @@ -13,9 +13,10 @@ import { SearchLineIcon, } from '../common/Icon'; -import { setUserId } from '../user/user.slice'; +import { selectUserFilters, setUserId } from '../user/user.slice'; const useUserManagementTableActions = () => { + const filters = useSelector(selectUserFilters); const dispatch = useDispatch(); const handleSearchChange = (event: React.ChangeEvent) => { dispatch(setUserId(event.target.value)); @@ -23,13 +24,14 @@ const useUserManagementTableActions = () => { return { handleSearchChange, + ...filters, }; } const UserManagementTableActions: React.FC = () => { const { handleSearchChange, - id, + user } = useUserManagementTableActions(); return ( @@ -44,7 +46,7 @@ const UserManagementTableActions: React.FC = () => { placeholder="Search by Name or Username" size="sm" borderRadius="md" - value={id} + value={user.id} name="search" onChange={handleSearchChange} /> diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 1dcd28709..5d7a5ba31 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -10,7 +10,9 @@ export interface UsersResponse { } export interface UsersParams { - search: string; + id: string; + page: number; + size: number; } export interface UserResponse { @@ -21,5 +23,5 @@ export interface UserResponse { } export interface UserParams { - id: string; + user: User } diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index 18d97556f..5669f043a 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -25,6 +25,18 @@ const initialState: State = { token: null, }; +// Helpers +export const mapFiltersToSearchParams = ({ + id, + page, + size, +}: Partial) => ({ + ...(id ? { id } : {}), + ...(page ? { page: `${page}` } : {}), + ...(typeof size !== 'undefined' ? { size: `${size}` } : {}), +}); + + // User API export const userApi = createApi({ reducerPath: 'userApi', @@ -45,7 +57,10 @@ export const userApi = createApi({ UsersResponse, UsersParams >({ - query: () => ({ url: `users` }), + query: (filters) => ({ + url: `users`, + params: mapFiltersToSearchParams(filters), + }), providesTags: () => ['User'], }), getUserById: build.query< @@ -55,6 +70,16 @@ export const userApi = createApi({ query: (id) => ({ url: `user/${id}` }), providesTags: () => ['User'], }), + createUser: build.mutation< + User, + Partial & Pick + >({ + query: (user) => ({ + url: 'user', + method: 'POST', + body: user, + }) + }), editUser: build.mutation< User, Partial & Pick @@ -128,4 +153,10 @@ export const { assignToken, setUserId } = userSlice.actions; export const selectUserToken = (state: AppState) => state.user.token; +export const selectUserFilters = ( + state: AppState +): UserParams => ({ + id: state.user.id, +}); + export const { reducer } = userSlice; From f6697b1094d3251968935e911f28d4167769bc70 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Sun, 24 Apr 2022 22:41:37 -0500 Subject: [PATCH 14/51] Update slice --- clients/admin-ui/src/features/user/types.ts | 22 +++------ .../admin-ui/src/features/user/user.slice.ts | 49 +++++++++---------- 2 files changed, 29 insertions(+), 42 deletions(-) diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 5d7a5ba31..56a201f4c 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -1,27 +1,19 @@ export interface User { - name: string; - username: string; - password: string; id: string; + name?: string; + username?: string; + password?: string; } -export interface UsersResponse { - users: User[]; +export interface UserResponse { + id: string; } export interface UsersParams { - id: string; page: number; size: number; } -export interface UserResponse { - name: string; - username: string; - password: string; - id: string; -} - -export interface UserParams { - user: User +export interface UsersResponse { + users: User[]; } diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index 5669f043a..82139b860 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -4,9 +4,8 @@ import { HYDRATE } from 'next-redux-wrapper'; import type { AppState } from '../../app/store'; import { - UserParams, - UserResponse, UsersParams, + UserResponse, UsersResponse, User, } from './types'; @@ -27,11 +26,9 @@ const initialState: State = { // Helpers export const mapFiltersToSearchParams = ({ - id, page, size, }: Partial) => ({ - ...(id ? { id } : {}), ...(page ? { page: `${page}` } : {}), ...(typeof size !== 'undefined' ? { size: `${size}` } : {}), }); @@ -53,47 +50,35 @@ export const userApi = createApi({ }), tagTypes: ['User'], endpoints: (build) => ({ - getAllUsers: build.query< - UsersResponse, - UsersParams - >({ + getAllUsers: build.query({ query: (filters) => ({ url: `users`, params: mapFiltersToSearchParams(filters), }), - providesTags: () => ['User'], + providesTags: ['User'], }), - getUserById: build.query< - UserResponse, - UserParams - >({ + getUserById: build.query({ query: (id) => ({ url: `user/${id}` }), - providesTags: () => ['User'], + providesTags: ['User'], }), - createUser: build.mutation< - User, - Partial & Pick - >({ + createUser: build.mutation>({ query: (user) => ({ url: 'user', method: 'POST', body: user, }) }), - editUser: build.mutation< - User, - Partial & Pick - >({ + editUser: build.mutation & Pick>({ query: ({ id, ...patch }) => ({ url: `user/${id}`, method: 'PATCH', body: patch, }), invalidatesTags: ['User'], - // For optimistic update + // For optimistic updates async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) { const patchResult = dispatch( - userApi.util.updateQueryData('getUserById', { id }, (draft) => { + userApi.util.updateQueryData('getUserById', {id, ...patch}, (draft) => { Object.assign(draft, patch) }) ) @@ -140,6 +125,15 @@ export const userSlice = createSlice({ page: initialState.page, id: action.payload, }), + setPage: (state, action: PayloadAction) => ({ + ...state, + page: action.payload, + }), + setSize: (state, action: PayloadAction) => ({ + ...state, + page: initialState.page, + size: action.payload, + }), }, extraReducers: { [HYDRATE]: (state, action) => ({ @@ -149,14 +143,15 @@ export const userSlice = createSlice({ }, }); -export const { assignToken, setUserId } = userSlice.actions; +export const { assignToken, setUserId, setPage } = userSlice.actions; export const selectUserToken = (state: AppState) => state.user.token; export const selectUserFilters = ( state: AppState -): UserParams => ({ - id: state.user.id, +): UsersParams => ({ + page: state.subjectRequests.page, + size: state.subjectRequests.size, }); export const { reducer } = userSlice; From 843f7fbb86d63ec7fd44d8743026e0a4c390f796 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Mon, 25 Apr 2022 00:44:21 -0500 Subject: [PATCH 15/51] Create through slice --- .../src/features/user-management/UserForm.tsx | 58 ++++--------------- .../user-management/UserManagementTable.tsx | 1 + .../UserManagementTableActions.tsx | 4 +- clients/admin-ui/src/features/user/types.ts | 42 +++++++++++++- .../admin-ui/src/features/user/user.slice.ts | 1 + 5 files changed, 56 insertions(+), 50 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index 1d20ac5d5..df1ba20c3 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -17,52 +17,13 @@ import { Text, } from '@fidesui/react'; import config from './config/config.json'; -import { selectUserToken } from '../user/user.slice'; - -interface Privilege { - privilege: string; - description: string; -} - -export const userPrivilegesArray: Privilege[] = [ - { - privilege: 'View subject requests', - description: 'Instructional line about these particular user preferences', - }, - { - privilege: 'Approve subject requests', - description: 'Instructional line about these particular user preferences', - }, - { - privilege: 'View datastore connections', - description: 'Instructional line about these particular user preferences', - }, - { - privilege: 'Manage datastore connections', - description: 'Instructional line about these particular user preferences', - }, - { - privilege: 'View policies', - description: 'Instructional line about these particular user preferences', - }, - { - privilege: 'Create policies', - description: 'Instructional line about these particular user preferences', - }, - { - privilege: 'Create users', - description: 'Instructional line about these particular user preferences', - }, - { - privilege: 'Create roles', - description: 'Instructional line about these particular user preferences', - }, -]; +import { selectUserToken, useCreateUserMutation } from '../user/user.slice'; const useUserForm = () => { // const dispatch = useDispatch(); const token = useSelector(selectUserToken); const [isLoading, setIsLoading] = useState(false); + const [createUser, createUserResult] = useCreateUserMutation(); // Initial values - GET individual user values if coming from the ID path // useEffect(() => { @@ -84,12 +45,15 @@ const useUserForm = () => { const body = { - "username": values.username, - "name": values.name, - "password": values.password, + username: values.username, + name: values.name, + password: values.password, } ; - // use POST action + + createUser(body); + setIsLoading(false); + // reset form or redirect after creating/editing? }, validate: (values) => { const errors: { @@ -216,12 +180,12 @@ const UserForm: NextPage = () => { Select privileges to assign to this user - {userPrivilegesArray.map((policy, idx) => ( + {/* {userPrivilegesArray.map((policy, idx) => ( <> {policy.privilege}
{policy.description}
- ))} + ))} */}
diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index 0478a7f5d..adf9ca9ae 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -26,6 +26,7 @@ const useUsersTable = () => { const { data, isLoading } = useGetAllUsersQuery(filters); const { users } = data || { users: [] }; + return { ...filters, isLoading, diff --git a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx index 0635fdfcb..05571c49e 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx @@ -19,6 +19,7 @@ const useUserManagementTableActions = () => { const filters = useSelector(selectUserFilters); const dispatch = useDispatch(); const handleSearchChange = (event: React.ChangeEvent) => { + console.log(event.target.value) dispatch(setUserId(event.target.value)); }; @@ -31,7 +32,6 @@ const useUserManagementTableActions = () => { const UserManagementTableActions: React.FC = () => { const { handleSearchChange, - user } = useUserManagementTableActions(); return ( @@ -46,7 +46,7 @@ const UserManagementTableActions: React.FC = () => { placeholder="Search by Name or Username" size="sm" borderRadius="md" - value={user.id} + // value={user.id} name="search" onChange={handleSearchChange} /> diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 56a201f4c..711951876 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -1,5 +1,5 @@ export interface User { - id: string; + id?: string; name?: string; username?: string; password?: string; @@ -17,3 +17,43 @@ export interface UsersParams { export interface UsersResponse { users: User[]; } + +export interface UserPrivileges { + privilege: string; + description: string; +} + +export const userPrivilegesArray: UserPrivileges[] = [ + { + privilege: 'View subject requests', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Approve subject requests', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'View datastore connections', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Manage datastore connections', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'View policies', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Create policies', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Create users', + description: 'Instructional line about these particular user preferences', + }, + { + privilege: 'Create roles', + description: 'Instructional line about these particular user preferences', + }, +]; \ No newline at end of file diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index 82139b860..ef0eb0933 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -108,6 +108,7 @@ export const userApi = createApi({ export const { useGetAllUsersQuery, useGetUserByIdQuery, + useCreateUserMutation, useEditUserMutation, useDeleteUserMutation, } = userApi; From 058dcd285f52576e65feec71aa084f7a0442611e Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Wed, 27 Apr 2022 02:49:47 -0500 Subject: [PATCH 16/51] Set-up for future api work --- .../user-management/DeleteUserModal.tsx | 14 +++- .../src/features/user-management/UserForm.tsx | 80 ++++++++++++------- .../user-management/UserManagementRow.tsx | 14 +++- clients/admin-ui/src/features/user/types.ts | 1 + .../admin-ui/src/features/user/user.slice.ts | 2 +- .../src/pages/user-management/new.tsx | 21 +++++ .../pages/user-management/profile/[id].tsx | 18 ++++- 7 files changed, 112 insertions(+), 38 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx index 6e04da14b..1abce64ff 100644 --- a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx +++ b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx @@ -15,10 +15,16 @@ import { useDisclosure, } from '@fidesui/react'; -function DeleteUserModal(user) { - const { isOpen, onOpen, onClose } = useDisclosure() - // const deleteUser(id) { - // // call delete user from API and delete by id here + import { User } from '../user/types'; + import { useDeleteUserMutation } from '../user/user.slice'; + +function DeleteUserModal(user: User) { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [deleteUser, deleteUserResult] = useDeleteUserMutation(); + // const deleteUser() { + // if(user.id) { + // deleteUser(user.id) + // } // } return ( diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index df1ba20c3..f02970566 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; import NextLink from 'next/link'; @@ -6,7 +6,6 @@ import { useFormik } from 'formik'; import { Button, chakra, - Checkbox, CheckboxGroup, FormControl, FormErrorMessage, @@ -17,27 +16,40 @@ import { Text, } from '@fidesui/react'; import config from './config/config.json'; -import { selectUserToken, useCreateUserMutation } from '../user/user.slice'; +import { selectUserToken, useEditUserMutation, useCreateUserMutation, useGetUserByIdQuery} from '../user/user.slice'; +import { useRouter } from 'next/router'; -const useUserForm = () => { - // const dispatch = useDispatch(); +const useUserForm = (existingId: string | null) => { const token = useSelector(selectUserToken); - const [isLoading, setIsLoading] = useState(false); const [createUser, createUserResult] = useCreateUserMutation(); + const [editUser, editUserResult] = useEditUserMutation(); + // const {getUser, getUserResult} = useGetUserByIdQuery(existingId || null); + const router = useRouter(); // Initial values - GET individual user values if coming from the ID path - // useEffect(() => { - // // Get user values - // }, []); + useEffect(() => { + console.log("initial") + // if(existingId) { + // getUser(existingId) + // } + }, []); + + const getUserResult = { + // username: "test", + // name: "test name", + // password: "test pass", + username: null, + name: null, + password: null, + } const formik = useFormik({ initialValues: { - username: '', - name: '', - password: '', + username: getUserResult?.username || '', + name: getUserResult?.name || '', + password: getUserResult?.password ? '********' : '', }, onSubmit: async (values) => { - setIsLoading(true); const host = process.env.NODE_ENV === 'development' ? config.fidesops_host_development @@ -51,9 +63,15 @@ const useUserForm = () => { } ; - createUser(body); - setIsLoading(false); - // reset form or redirect after creating/editing? + if(!getUserResult) { + createUser(body); + } + // else { + // console.log("editing") + // editUser({existingId, ...body}) + // } + // redirect after creating/editing? + router.push('/user-management') }, validate: (values) => { const errors: { @@ -79,16 +97,19 @@ const useUserForm = () => { }); // const { data, isLoading } = useGetUserQuery(userId); - // const { items } = data || { items: [] }; + // const { user } = data || { user: {} }; + const user = existingId ? getUserResult : null + + console.log(user) return { ...formik, - isLoading, - // items + // isLoading: createUserResult.isLoading, + user }; }; -const UserForm: NextPage = () => { +const UserForm: NextPage<{existingId: string}> = ({ existingId }) => { const { dirty, errors, @@ -96,10 +117,13 @@ const UserForm: NextPage = () => { handleChange, handleSubmit, isValid, - isLoading, + // isLoading, touched, values, - } = useUserForm(); + user, + } = useUserForm(existingId); + + console.log(existingId) return (
@@ -129,6 +153,8 @@ const UserForm: NextPage = () => { onBlur={handleBlur} value={values.username} isInvalid={touched.username && Boolean(errors.username)} + isReadOnly={existingId ? true : false} + isDisabled={existingId ? true : false} /> {errors.username} @@ -174,20 +200,20 @@ const UserForm: NextPage = () => { {errors.password} - + {/* Preferences Select privileges to assign to this user - + */} {/* {userPrivilegesArray.map((policy, idx) => ( <> {policy.privilege}
{policy.description}
))} */} -
-
+ {/* + */} @@ -204,7 +230,7 @@ const UserForm: NextPage = () => { _hover={{ bg: 'primary.400' }} _active={{ bg: 'primary.500' }} colorScheme="primary" - disabled={!(isValid && dirty)} + disabled={!existingId && !(isValid && dirty)} size="sm" > Save diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index aa5cc9a70..d2f846eb6 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -14,6 +14,12 @@ import { import { MoreIcon } from '../common/Icon'; import DeleteUserModal from './DeleteUserModal'; +import { User } from '../user/types'; +// import Router from 'next/router'; + +interface UserManagementRowProps { + user: User; +} const useUserManagementRow = () => { const [menuOpen, setMenuOpen] = useState(false); @@ -27,12 +33,17 @@ const useUserManagementRow = () => { }; }; -const UserManagementRow: React.FC = (user) => { +const UserManagementRow: React.FC = ({ user }) => { const { handleMenuOpen, handleMenuClose, menuOpen, } = useUserManagementRow(); + + // const handleEditUser = () => { + // Router.push(/user-management/profile/${user.id}) + // } + const showMenu = menuOpen; return ( @@ -61,7 +72,6 @@ const UserManagementRow: React.FC = (user) => { Edit diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 711951876..37c732a79 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -9,6 +9,7 @@ export interface UserResponse { id: string; } +// userslistparams ? export interface UsersParams { page: number; size: number; diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index ef0eb0933..f9d98ce7f 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -94,7 +94,7 @@ export const userApi = createApi({ } }, }), - deleteUser: build.mutation<{ success: boolean; id: number }, number>({ + deleteUser: build.mutation<{ success: boolean; id: string }, string>({ query: (id) => ({ url: `user/${id}`, method: 'DELETE', diff --git a/clients/admin-ui/src/pages/user-management/new.tsx b/clients/admin-ui/src/pages/user-management/new.tsx index 851f95135..c9fa6ff1a 100644 --- a/clients/admin-ui/src/pages/user-management/new.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -8,6 +8,10 @@ import NavBar from '../../features/common/NavBar'; // import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; import UserForm from '../../features/user-management/UserForm'; +import { assignToken } from '../../features/user/user.slice'; +import { getSession } from 'next-auth/react'; +import { wrapper } from '../../app/store'; + const CreateNewUser: NextPage<{ session: { username: string } }> = ({ session }) => (
@@ -32,3 +36,20 @@ const CreateNewUser: NextPage<{ session: { username: string } }> = ({ session }) ); export default CreateNewUser; + +export const getServerSideProps = wrapper.getServerSideProps( + (store) => async (context) => { + const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { + await store.dispatch(assignToken(session.accessToken)); + return { props: { session } }; + } + + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } +); diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index c743cd1a9..4115bc9d7 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -1,10 +1,8 @@ import React from 'react'; import type { NextPage } from 'next'; import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Heading } from '@fidesui/react'; - import NavBar from '../../../features/common/NavBar'; - -// import UserForm from '../../features/user-management/UserForm'; +import UserForm from '../../../features/user-management/UserForm'; const Profile: NextPage<{ session: { username: string } }> = ({ session }) => (
@@ -23,10 +21,22 @@ const Profile: NextPage<{ session: { username: string } }> = ({ session }) => ( - Profile page to view and edit user info +
); export default Profile; + +// export async function getServerSideProps(context) { + // const { id } = context.query; + // const res = await fetch(`https://restcountries.eu/rest/v2/name/${id}`); + // fetch is getUserById call + // const user = await res.json(); + + // return { props: { user } }; +// } From e19867edcd30bc0f3e20580e7eb117b91981ddd2 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Thu, 28 Apr 2022 23:17:07 -0500 Subject: [PATCH 17/51] Start get --- .../src/features/user-management/UserForm.tsx | 133 +++++++++--------- .../user-management/UserManagementTable.tsx | 23 ++- clients/admin-ui/src/features/user/types.ts | 3 +- .../admin-ui/src/features/user/user.slice.ts | 2 +- .../src/pages/user-management/index.tsx | 25 +++- 5 files changed, 101 insertions(+), 85 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index f02970566..1249fbfcd 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -11,12 +11,17 @@ import { FormErrorMessage, FormLabel, Heading, - Input, + Input, Stack, Text, } from '@fidesui/react'; import config from './config/config.json'; -import { selectUserToken, useEditUserMutation, useCreateUserMutation, useGetUserByIdQuery} from '../user/user.slice'; +import { + selectUserToken, + useEditUserMutation, + useCreateUserMutation, + useGetUserByIdQuery, +} from '../user/user.slice'; import { useRouter } from 'next/router'; const useUserForm = (existingId: string | null) => { @@ -28,9 +33,9 @@ const useUserForm = (existingId: string | null) => { // Initial values - GET individual user values if coming from the ID path useEffect(() => { - console.log("initial") + console.log('initial'); // if(existingId) { - // getUser(existingId) + // getUser(existingId) // } }, []); @@ -41,7 +46,7 @@ const useUserForm = (existingId: string | null) => { username: null, name: null, password: null, - } + }; const formik = useFormik({ initialValues: { @@ -55,15 +60,12 @@ const useUserForm = (existingId: string | null) => { ? config.fidesops_host_development : config.fidesops_host_production; - const body = - { - username: values.username, - name: values.name, - password: values.password, - } - ; - - if(!getUserResult) { + const body = { + username: values.username, + name: values.name, + password: values.password, + }; + if (!existingId) { createUser(body); } // else { @@ -71,7 +73,7 @@ const useUserForm = (existingId: string | null) => { // editUser({existingId, ...body}) // } // redirect after creating/editing? - router.push('/user-management') + router.push('/user-management'); }, validate: (values) => { const errors: { @@ -98,18 +100,18 @@ const useUserForm = (existingId: string | null) => { // const { data, isLoading } = useGetUserQuery(userId); // const { user } = data || { user: {} }; - const user = existingId ? getUserResult : null + const user = existingId ? getUserResult : null; - console.log(user) + console.log(user); return { - ...formik, + ...formik, // isLoading: createUserResult.isLoading, - user + user, }; }; -const UserForm: NextPage<{existingId: string}> = ({ existingId }) => { +const UserForm: NextPage<{ existingId: string }> = ({ existingId }) => { const { dirty, errors, @@ -123,7 +125,7 @@ const UserForm: NextPage<{existingId: string}> = ({ existingId }) => { user, } = useUserForm(existingId); - console.log(existingId) + console.log(existingId); return (
@@ -144,19 +146,19 @@ const UserForm: NextPage<{existingId: string}> = ({ existingId }) => { Username - - {errors.username} + + {errors.username} = ({ existingId }) => { Name - - {errors.name} + + {errors.name} - + = ({ existingId }) => { Select privileges to assign to this user */} - {/* {userPrivilegesArray.map((policy, idx) => ( + {/* {userPrivilegesArray.map((policy, idx) => ( <> {policy.privilege}
{policy.description}
))} */} - {/*
+ {/*
*/} - - - - - + +
diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index adf9ca9ae..0000b7a91 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -1,12 +1,6 @@ import React, { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { - Table, - Thead, - Tbody, - Tr, - Th, -} from '@fidesui/react'; +import { Table, Thead, Tbody, Tr, Th } from '@fidesui/react'; import UserManagementRow from './UserManagementRow'; @@ -24,9 +18,10 @@ const useUsersTable = () => { // dispatch(setPage(filters.page + 1)); // }; + console.log(useGetAllUsersQuery(filters)); const { data, isLoading } = useGetAllUsersQuery(filters); - const { users } = data || { users: [] }; - + const { items: users, total } = data || { users: [], total: 0 }; + return { ...filters, isLoading, @@ -34,13 +29,13 @@ const useUsersTable = () => { // handleNextPage, // handlePreviousPage, }; -} +}; const UserManagementTable: React.FC = () => { - const { users, - // page, size, handleNextPage, handlePreviousPage - } = - useUsersTable(); + const { + users, + // page, size, handleNextPage, handlePreviousPage + } = useUsersTable(); // const startingItem = (page - 1) * size + 1; // const endingItem = Math.min(total, page * size); diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 37c732a79..28fb26ab6 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -16,7 +16,8 @@ export interface UsersParams { } export interface UsersResponse { - users: User[]; + items: User[]; + total: number; } export interface UserPrivileges { diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index f9d98ce7f..21ea5d764 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -52,7 +52,7 @@ export const userApi = createApi({ endpoints: (build) => ({ getAllUsers: build.query({ query: (filters) => ({ - url: `users`, + url: `user`, params: mapFiltersToSearchParams(filters), }), providesTags: ['User'], diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx index ce0d823a0..a7b735e6c 100644 --- a/clients/admin-ui/src/pages/user-management/index.tsx +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -2,15 +2,19 @@ import React from 'react'; import type { NextPage } from 'next'; import Head from 'next/head'; import { Box, Heading } from '@fidesui/react'; +import { getSession } from 'next-auth/react'; +import { wrapper } from '../../app/store'; +import { assignToken } from '../../features/user/user.slice'; import NavBar from '../../features/common/NavBar'; import UserManagementTable from '../../features/user-management/UserManagementTable'; import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; -const UserManagement: NextPage<{ session: { username: string } }> = ({ session }) => ( +const UserManagement: NextPage<{ session: { username: string } }> = ({ + session, +}) => (
- Fides Admin UI - User Management @@ -32,3 +36,20 @@ const UserManagement: NextPage<{ session: { username: string } }> = ({ session } ); export default UserManagement; + +export const getServerSideProps = wrapper.getServerSideProps( + (store) => async (context) => { + const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { + await store.dispatch(assignToken(session.accessToken)); + return { props: { session } }; + } + + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } +); From e06a03ccddd77a12026fb9ac242f0eb14c0aeff5 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 29 Apr 2022 00:12:13 -0500 Subject: [PATCH 18/51] Create and Delete set-up --- .../user-management/DeleteUserModal.tsx | 135 +++++++++--------- .../src/features/user-management/UserForm.tsx | 2 +- .../user-management/UserManagementRow.tsx | 69 ++++----- .../user-management/UserManagementTable.tsx | 10 +- fidesops.toml | 2 +- 5 files changed, 104 insertions(+), 114 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx index 1abce64ff..7581fef59 100644 --- a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx +++ b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx @@ -1,73 +1,74 @@ import { - Button, - FormControl, - FormLabel, - Input, - MenuItem, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - Text, - useDisclosure, - } from '@fidesui/react'; + Button, + FormControl, + FormLabel, + Input, + MenuItem, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, + useDisclosure, +} from '@fidesui/react'; - import { User } from '../user/types'; - import { useDeleteUserMutation } from '../user/user.slice'; +import { User } from '../user/types'; +import { useDeleteUserMutation } from '../user/user.slice'; function DeleteUserModal(user: User) { - const { isOpen, onOpen, onClose } = useDisclosure(); - const [deleteUser, deleteUserResult] = useDeleteUserMutation(); - // const deleteUser() { - // if(user.id) { - // deleteUser(user.id) - // } - // } - - return ( - <> - - Delete - - - - - Delete User - - - - User name - - - - Confirm User name - - - - - - - {/* Disable delete user button when either field is blank or the fields don't match ? */} - - - - - - ) - } + const { isOpen, onOpen, onClose } = useDisclosure(); + const [deleteUser, deleteUserResult] = useDeleteUserMutation(); + + const handleDeleteUser = () => { + if (user.id) { + deleteUser(user.id); + } else { + console.log('Cant delete'); + } + }; + + return ( + <> + + Delete + + + + + Delete User + + + + Username + + + + Confirm Username + + + + + + + {/* Disable delete user button when either field is blank or the fields don't match ? */} + + + + + + ); +} export default DeleteUserModal; diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index 1249fbfcd..cb814f50b 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -150,7 +150,7 @@ const UserForm: NextPage<{ existingId: string }> = ({ existingId }) => { id="username" name="username" focusBorderColor="primary.500" - placeholder="Enter new user name" + placeholder="Enter new username" onChange={handleChange} onBlur={handleBlur} value={values.username} diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index d2f846eb6..157413b29 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -34,55 +34,44 @@ const useUserManagementRow = () => { }; const UserManagementRow: React.FC = ({ user }) => { - const { - handleMenuOpen, - handleMenuClose, - menuOpen, - } = useUserManagementRow(); + const { handleMenuOpen, handleMenuClose, menuOpen } = useUserManagementRow(); // const handleEditUser = () => { + // Blocked until PATCH user available // Router.push(/user-management/profile/${user.id}) // } const showMenu = menuOpen; return ( - <> -
- - + + - - + + ); }; diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index 0000b7a91..f3a42cdfe 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -26,6 +26,7 @@ const useUsersTable = () => { ...filters, isLoading, users, + total, // handleNextPage, // handlePreviousPage, }; @@ -34,6 +35,7 @@ const useUsersTable = () => { const UserManagementTable: React.FC = () => { const { users, + total, // page, size, handleNextPage, handlePreviousPage } = useUsersTable(); // const startingItem = (page - 1) * size + 1; @@ -44,15 +46,13 @@ const UserManagementTable: React.FC = () => {
- {/* {user.name} */} - Name - - - - - - - - - - Edit - - {DeleteUserModal(user)} - - - - + <> +
+ {user.username} + + + + + + + + + + Edit + + {DeleteUserModal(user)} + + + +
- + - {/* Blocked until GET users is implemented */} - {/* {users.map((user) => ( + {users?.map((user) => ( - ))} */} - + ))}
NameUsername
diff --git a/fidesops.toml b/fidesops.toml index 3f06eafbb..30abb7760 100644 --- a/fidesops.toml +++ b/fidesops.toml @@ -24,5 +24,5 @@ OAUTH_ROOT_CLIENT_SECRET="fidesopsadminsecret" TASK_RETRY_COUNT=0 TASK_RETRY_DELAY=1 TASK_RETRY_BACKOFF=1 -REQUIRE_MANUAL_REQUEST_APPROVAL=false +REQUIRE_MANUAL_REQUEST_APPROVAL=true MASKING_STRICT=true From fc0f8cde35fdfd628766d0918fd92b88741b11fa Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Mon, 2 May 2022 21:19:45 -0500 Subject: [PATCH 19/51] Delete user validation --- .../user-management/DeleteUserModal.tsx | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx index 7581fef59..6c27b6bc2 100644 --- a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx +++ b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx @@ -1,3 +1,4 @@ +import React, { useState } from 'react'; import { Button, FormControl, @@ -19,17 +20,39 @@ import { User } from '../user/types'; import { useDeleteUserMutation } from '../user/user.slice'; function DeleteUserModal(user: User) { + const [usernameValue, setUsernameValue] = useState(''); + const [confirmValue, setConfirmValue] = useState(''); const { isOpen, onOpen, onClose } = useDisclosure(); const [deleteUser, deleteUserResult] = useDeleteUserMutation(); + const handleChange = (event: React.ChangeEvent) => { + if (event.target.name === 'username') { + setUsernameValue(event.target.value); + } else { + setConfirmValue(event.target.value); + } + }; + + const deletionValidation = + user.id && + confirmValue && + usernameValue && + user.username === usernameValue && + user.username === confirmValue + ? true + : false; + const handleDeleteUser = () => { - if (user.id) { + if (deletionValidation && user.id) { deleteUser(user.id); } else { console.log('Cant delete'); + // throw error/alert ? } }; + console.log(deletionValidation); + return ( <> Username - + Confirm Username - + - {/* Disable delete user button when either field is blank or the fields don't match ? */} + diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index 52a3abbc8..91f7ba4d3 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; import NextLink from 'next/link'; @@ -19,6 +19,7 @@ import { import config from './config/config.json'; import { selectUserToken, + selectManagedUser, useEditUserMutation, useUpdateUserPermissionsMutation, useCreateUserMutation, @@ -26,30 +27,24 @@ import { useGetUserByIdQuery, useGetUserPermissionsQuery, } from '../user/user.slice'; -import { userPrivilegesArray } from '../user/types'; - +import { userPrivilegesArray, User } from '../user/types'; import { useRouter } from 'next/router'; -const useUserForm = ( - existingUser?: { - data: { id: string; username: string; password: string; name: string }; - } | null -) => { +const useUserForm = () => { const token = useSelector(selectUserToken); const [createUser, createUserResult] = useCreateUserMutation(); const [createUserPermissions, createUserPermissionsResult] = useCreateUserPermissionsMutation(); // const [editUser, editUserResult] = useEditUserMutation(); const router = useRouter(); + const existingUser = useSelector(selectManagedUser); const formik = useFormik({ initialValues: { - username: existingUser?.data?.username || '', - name: existingUser?.data?.name || '', - password: existingUser?.data?.password ? '********' : '', - scopes: existingUser - ? useGetUserPermissionsQuery(existingUser.data.id) - : [], + username: existingUser ? existingUser?.username : '', + name: existingUser ? existingUser?.name : '', + password: existingUser ? '********' : '', + scopes: existingUser ? useGetUserPermissionsQuery(existingUser?.id) : [], }, onSubmit: async (values) => { const host = @@ -63,6 +58,8 @@ const useUserForm = ( password: values.password, }; + console.log(values); + const permissionsBody = () => { const permissionsForUser: string[] = []; // add values.checkbox to array @@ -75,7 +72,7 @@ const useUserForm = ( return existingUser ? { scopes: permissionsForUser, - id: existingUser.data.id, + id: existingUser.id, } : { scopes: permissionsForUser, @@ -119,27 +116,24 @@ const useUserForm = ( return { ...formik, - // isLoading: createUserResult.isLoading, existingUser, }; }; const UserForm: NextPage<{ - existingUser?: { - data: { id: string; username: string; password: string; name: string }; - } | null; -}> = ({ existingUser }) => { + existingUser?: User; +}> = () => { const { dirty, errors, + existingUser, handleBlur, handleChange, handleSubmit, isValid, - // isLoading, touched, values, - } = useUserForm(existingUser); + } = useUserForm(); return (
@@ -167,9 +161,7 @@ const UserForm: NextPage<{ placeholder="Enter new username" onChange={handleChange} onBlur={handleBlur} - value={ - existingUser ? existingUser?.data?.username : values.username - } + value={existingUser ? existingUser?.username : values.username} isInvalid={touched.username && Boolean(errors.username)} isReadOnly={existingUser ? true : false} isDisabled={existingUser ? true : false} @@ -191,7 +183,7 @@ const UserForm: NextPage<{ placeholder="Enter name of user" onChange={handleChange} onBlur={handleBlur} - value={existingUser ? existingUser?.data?.name : values.name} + value={existingUser ? existingUser?.name : values.name} isInvalid={touched.name && Boolean(errors.name)} /> {errors.name} @@ -210,9 +202,7 @@ const UserForm: NextPage<{ focusBorderColor="primary.500" placeholder="********" type="password" - value={ - existingUser ? existingUser?.data?.password : values.password - } + value={existingUser ? existingUser?.password : values.password} onChange={handleChange} onBlur={handleBlur} isInvalid={touched.password && Boolean(errors.password)} @@ -232,7 +222,7 @@ const UserForm: NextPage<{ key={`${policy.privilege}-${idx}`} onChange={handleChange} value={policy.privilege} - id="checkbox" + id={`checkbox-${policy.privilege}-${idx}`} name="checkbox" > {policy.privilege} diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index 09e8bd515..4d83bf728 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -11,11 +11,13 @@ import { MenuItem, Portal, } from '@fidesui/react'; +import { useDispatch } from 'react-redux'; import { MoreIcon } from '../common/Icon'; import DeleteUserModal from './DeleteUserModal'; import { User } from '../user/types'; import { useRouter } from 'next/router'; +import { useGetUserByIdQuery, setManagedUser } from '../user/user.slice'; interface UserManagementRowProps { user: User; @@ -36,9 +38,13 @@ const useUserManagementRow = () => { const UserManagementRow: React.FC = ({ user }) => { const { handleMenuOpen, handleMenuClose, menuOpen } = useUserManagementRow(); const router = useRouter(); + const dispatch = useDispatch(); + + const { data } = useGetUserByIdQuery(user.id); + console.log(data); const handleEditUser = () => { - // Blocked until PATCH user available + dispatch(setManagedUser(data)); router.push(`/user-management/profile/${user.id}`); }; diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index e97571d01..fdc1e5536 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -11,6 +11,7 @@ export interface State { size: number; user: User; token: string | null; + managedUser: User; } const initialState: State = { @@ -19,6 +20,7 @@ const initialState: State = { size: 25, user: {}, token: null, + managedUser: null, }; // Helpers @@ -56,11 +58,11 @@ export const userApi = createApi({ providesTags: () => ['User'], }), getUserById: build.query({ - query: (id) => ({ url: `user/${id}/permission` }), + query: (id) => ({ url: `user/${id}` }), providesTags: ['User'], }), getUserPermissions: build.query({ - query: (id) => ({ url: `user/${id}` }), + query: (id) => ({ url: `user/${id}/permission` }), providesTags: ['User'], }), createUser: build.mutation>({ @@ -174,6 +176,10 @@ export const userSlice = createSlice({ page: initialState.page, size: action.payload, }), + setManagedUser: (state, action: PayloadAction) => ({ + ...state, + managedUser: action.payload, + }), }, extraReducers: { [HYDRATE]: (state, action) => ({ @@ -183,7 +189,8 @@ export const userSlice = createSlice({ }, }); -export const { assignToken, setUser, setPage } = userSlice.actions; +export const { assignToken, setManagedUser, setUser, setPage } = + userSlice.actions; export const selectUserToken = (state: AppState) => state.user.token; @@ -193,4 +200,6 @@ export const selectUserFilters = (state: AppState): UsersListParams => ({ user: state.user.user, }); +export const selectManagedUser = (state: AppState) => state.user.managedUser; + export const { reducer } = userSlice; diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index 830cf2df3..fe90e9738 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -13,11 +13,6 @@ import UserForm from '../../../features/user-management/UserForm'; import { useGetUserByIdQuery } from '../../../features/user/user.slice'; const Profile: NextPage = () => { - const router = useRouter(); - const { id } = router.query; - - const queriedUser = id ? useGetUserByIdQuery(id) : null; - return (
@@ -37,7 +32,7 @@ const Profile: NextPage = () => { - +
From b2ac302ee0db4d0aaf8a461bed0c8f933fa30f1c Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Thu, 5 May 2022 20:48:45 -0500 Subject: [PATCH 25/51] Permissions --- .../src/features/user-management/UserManagementRow.tsx | 8 ++------ clients/admin-ui/src/features/user/types.ts | 10 +++------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index 4d83bf728..4e3442518 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -17,7 +17,7 @@ import { MoreIcon } from '../common/Icon'; import DeleteUserModal from './DeleteUserModal'; import { User } from '../user/types'; import { useRouter } from 'next/router'; -import { useGetUserByIdQuery, setManagedUser } from '../user/user.slice'; +import { setManagedUser } from '../user/user.slice'; interface UserManagementRowProps { user: User; @@ -40,11 +40,8 @@ const UserManagementRow: React.FC = ({ user }) => { const router = useRouter(); const dispatch = useDispatch(); - const { data } = useGetUserByIdQuery(user.id); - console.log(data); - const handleEditUser = () => { - dispatch(setManagedUser(data)); + dispatch(setManagedUser(user)); router.push(`/user-management/profile/${user.id}`); }; @@ -66,7 +63,6 @@ const UserManagementRow: React.FC = ({ user }) => { Edit diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 23c345154..b761a6205 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -37,7 +37,7 @@ export const userPrivilegesArray: UserPrivileges[] = [ }, { privilege: 'Approve subject requests', - scopes: ['privacy-request:administrate:approve'], + scopes: ['privacy-request:review'], }, { privilege: 'View datastore connections', @@ -45,11 +45,7 @@ export const userPrivilegesArray: UserPrivileges[] = [ }, { privilege: 'Manage datastore connections', - scopes: [ - 'connection:read', - 'connection:create_or_update', - 'connection:delete', - ], + scopes: ['connection:create_or_update', 'connection:delete'], }, { privilege: 'View policies', @@ -65,6 +61,6 @@ export const userPrivilegesArray: UserPrivileges[] = [ }, { privilege: 'Create roles', - scopes: ['role:create'], + scopes: ['user-permission:create'], }, ]; From 4dcda8c06e3577ff8152456eb38745036e2d72eb Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Thu, 5 May 2022 21:52:56 -0500 Subject: [PATCH 26/51] Add additional user info to table --- .../src/features/user-management/UserForm.tsx | 63 ++++++++++++++----- .../user-management/UserManagementRow.tsx | 10 +++ .../user-management/UserManagementTable.tsx | 7 ++- clients/admin-ui/src/features/user/types.ts | 4 +- 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index 91f7ba4d3..b48a36214 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -42,7 +42,8 @@ const useUserForm = () => { const formik = useFormik({ initialValues: { username: existingUser ? existingUser?.username : '', - name: existingUser ? existingUser?.name : '', + first_name: existingUser ? existingUser?.first_name : '', + last_name: existingUser ? existingUser?.last_name : '', password: existingUser ? '********' : '', scopes: existingUser ? useGetUserPermissionsQuery(existingUser?.id) : [], }, @@ -54,12 +55,11 @@ const useUserForm = () => { const userBody = { username: values.username, - name: values.name, + first_name: values.first_name, + last_name: values.last_name, password: values.password, }; - console.log(values); - const permissionsBody = () => { const permissionsForUser: string[] = []; // add values.checkbox to array @@ -94,7 +94,8 @@ const useUserForm = () => { validate: (values) => { const errors: { username?: string; - name?: string; + first_name?: string; + last_name?: string; password?: string; } = {}; @@ -102,8 +103,12 @@ const useUserForm = () => { errors.username = 'Username is required'; } - if (!values.name) { - errors.name = 'Name is required'; + if (!values.first_name) { + errors.first_name = 'First name is required'; + } + + if (!values.last_name) { + errors.last_name = 'Last name is required'; } if (!values.password) { @@ -170,23 +175,47 @@ const UserForm: NextPage<{ + + First Name + + + {errors.first_name} + + + - - Name + + Last Name - {errors.name} + {errors.last_name} = ({ user }) => { }; const showMenu = menuOpen; + console.log(user); return ( <> @@ -53,6 +54,15 @@ const UserManagementRow: React.FC = ({ user }) => { {user.username} + + {user.first_name} + + + {user.last_name} + + + {new Date(user.created_at).toUTCString()} + diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index 23efd00ff..ac23bc847 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -49,9 +49,10 @@ const UserManagementTable: React.FC = () => { <> - - - + + + + {users?.map((user) => ( diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index b761a6205..118edf706 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -1,8 +1,10 @@ export interface User { id?: string; - name?: string; + first_name?: string; + last_name?: string; username?: string; password?: string; + created_at?: string; } export interface UserResponse { From 90b4f36360fc537bd8c283f44ce4d594df3baac1 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 6 May 2022 04:59:36 -0500 Subject: [PATCH 27/51] Checkboxes --- .../src/features/user-management/UserForm.tsx | 82 ++++++++++--------- .../user-management/UserManagementRow.tsx | 2 +- clients/admin-ui/src/features/user/types.ts | 30 ++++--- .../admin-ui/src/features/user/user.slice.ts | 4 +- 4 files changed, 65 insertions(+), 53 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index b48a36214..851d937b4 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -38,6 +38,8 @@ const useUserForm = () => { // const [editUser, editUserResult] = useEditUserMutation(); const router = useRouter(); const existingUser = useSelector(selectManagedUser); + const existingScopes = useGetUserPermissionsQuery(existingUser?.id); + // console.log('EXISTING', existingScopes.data.scopes); const formik = useFormik({ initialValues: { @@ -45,7 +47,11 @@ const useUserForm = () => { first_name: existingUser ? existingUser?.first_name : '', last_name: existingUser ? existingUser?.last_name : '', password: existingUser ? '********' : '', - scopes: existingUser ? useGetUserPermissionsQuery(existingUser?.id) : [], + // scopes: existingUser + // ? existingScopes?.data?.scopes.filter( + // (scope) => scope === values.scopes + // ) + // : [], }, onSubmit: async (values) => { const host = @@ -61,35 +67,24 @@ const useUserForm = () => { }; const permissionsBody = () => { - const permissionsForUser: string[] = []; - // add values.checkbox to array - // map through userPrivilegesArray - // map through checkboxes - if the value matches - // the privilege.privilege, then spread the privilege.scopes - // into the permissionsForUser array declared above - // use Set to check for duplicates - - return existingUser - ? { - scopes: permissionsForUser, - id: existingUser.id, - } - : { - scopes: permissionsForUser, - }; + const allScopes = [...values.scopes]; + return allScopes; }; if (!existingUser) { - createUser(userBody); - createUserPermissions(permissionsBody()); - router.replace('/user-management'); + createUser(userBody) + .then((result) => { + result = { ...result, scopes: permissionsBody() }; + console.log('result', result); + createUserPermissions(result); + }) + .then((result) => router.replace('/user-management')); } else { console.log('on edit page'); + console.log('permissionsBody', permissionsBody()); // editUser({existingId, ...body}) + // router.push('/user-management'); } - - // redirect after creating/editing - // router.push('/user-management'); }, validate: (values) => { const errors: { @@ -161,12 +156,15 @@ const UserForm: NextPage<{ {errors.first_name} @@ -205,14 +206,17 @@ const UserForm: NextPage<{ {errors.last_name} @@ -227,11 +231,12 @@ const UserForm: NextPage<{ {policy.privilege} diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index 5487438a0..a03dd5bed 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -61,7 +61,7 @@ const UserManagementRow: React.FC = ({ user }) => { {user.last_name} + {/* hide this from users that don't have the edit and/or delete actions */} - {/* hide this from users that don't have the edit and/or delete actions */}
Username
UsernameFirst NameLast NameCreated At
- {new Date(user.created_at).toUTCString()} + {user.created_at ? new Date(user.created_at).toUTCString() : null} diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 118edf706..96b48965e 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -24,45 +24,51 @@ export interface UsersResponse { export interface UserPrivileges { privilege: string; - scopes: string[]; + scope?: string; } export interface UserPermissions { - scopes: string[]; - id?: string; + data?: { + id?: string; + }; + scope?: string; } export const userPrivilegesArray: UserPrivileges[] = [ { privilege: 'View subject requests', - scopes: ['privacy-request:read'], + scope: 'privacy-request:read', }, { privilege: 'Approve subject requests', - scopes: ['privacy-request:review'], + scope: 'privacy-request:review', }, { privilege: 'View datastore connections', - scopes: ['connection:read'], + scope: 'connection:read', + }, + { + privilege: 'Create or Update datastore connections', + scope: 'connection:create_or_update', }, { - privilege: 'Manage datastore connections', - scopes: ['connection:create_or_update', 'connection:delete'], + privilege: 'Delete datastore connections', + scope: 'connection:delete', }, { privilege: 'View policies', - scopes: ['policy:read'], + scope: 'policy:read', }, { privilege: 'Create policies', - scopes: ['policy:create_or_update'], + scope: 'policy:create_or_update', }, { privilege: 'Create users', - scopes: ['user:create'], + scope: 'user:create', }, { privilege: 'Create roles', - scopes: ['user-permission:create'], + scope: 'user-permission:create', }, ]; diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index fdc1e5536..d124e9d81 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -77,9 +77,9 @@ export const userApi = createApi({ Partial >({ query: (user) => ({ - url: `user/${user.id}/permission`, + url: `user/${user?.data?.id}/permission`, method: 'POST', - body: user.scopes, + body: { scopes: user.scopes }, }), }), editUser: build.mutation & Pick>({ From 74cec89df0d63679a7a7c98ffaf4ef4acc36f2ad Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 6 May 2022 05:16:22 -0500 Subject: [PATCH 28/51] Some small form fixes --- .../src/features/user-management/UserForm.tsx | 48 +++++++++++-------- .../admin-ui/src/features/user/user.slice.ts | 4 +- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index 851d937b4..46505f0ac 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -222,27 +222,33 @@ const UserForm: NextPage<{ {errors.last_name} - - - Password - - - {errors.password} - + {existingUser ? ( +
Change Password
+ ) : ( + <> + + + Password + + + {errors.password} + + + )} Preferences diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index d124e9d81..6a0931561 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -11,7 +11,7 @@ export interface State { size: number; user: User; token: string | null; - managedUser: User; + managedUser: User | null; } const initialState: State = { @@ -79,7 +79,7 @@ export const userApi = createApi({ query: (user) => ({ url: `user/${user?.data?.id}/permission`, method: 'POST', - body: { scopes: user.scopes }, + body: { scopes: user.scope }, }), }), editUser: build.mutation & Pick>({ From 8ce4ed8a2c766969ef60453948d591982d7f2911 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 6 May 2022 12:38:29 -0500 Subject: [PATCH 29/51] Refreshes and checkboxes on load --- .../src/features/user-management/UserForm.tsx | 39 ++++++++++++------ .../user-management/UserManagementRow.tsx | 2 +- .../user-management/UserManagementTable.tsx | 10 +++-- .../admin-ui/src/features/user/user.slice.ts | 9 +++- .../pages/user-management/profile/[id].tsx | 41 ++++++++++++++++++- 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/UserForm.tsx index 46505f0ac..1b19893a8 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/UserForm.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; import NextLink from 'next/link'; @@ -37,9 +37,10 @@ const useUserForm = () => { useCreateUserPermissionsMutation(); // const [editUser, editUserResult] = useEditUserMutation(); const router = useRouter(); - const existingUser = useSelector(selectManagedUser); - const existingScopes = useGetUserPermissionsQuery(existingUser?.id); - // console.log('EXISTING', existingScopes.data.scopes); + const { id } = router.query; + const { data: existingUser } = useGetUserByIdQuery(id); + const { data: existingScopes, isLoading: scopesLoading } = + useGetUserPermissionsQuery(id); const formik = useFormik({ initialValues: { @@ -47,11 +48,7 @@ const useUserForm = () => { first_name: existingUser ? existingUser?.first_name : '', last_name: existingUser ? existingUser?.last_name : '', password: existingUser ? '********' : '', - // scopes: existingUser - // ? existingScopes?.data?.scopes.filter( - // (scope) => scope === values.scopes - // ) - // : [], + scopes: {}, }, onSubmit: async (values) => { const host = @@ -67,7 +64,8 @@ const useUserForm = () => { }; const permissionsBody = () => { - const allScopes = [...values.scopes]; + // const allScopes = [...values.scopes]; + const allScopes = []; return allScopes; }; @@ -114,6 +112,24 @@ const useUserForm = () => { }, }); + useEffect(() => { + // TODO: write in some error handling + if (existingScopes) { + formik.setFieldValue( + 'scopes', + existingScopes.scopes.reduce( + (scopes, scope) => ({ + ...scopes, + [scope]: true, + }), + {} as { + [key: string]: boolean; + } + ) + ); + } + }, [scopesLoading]); + return { ...formik, existingUser, @@ -261,10 +277,9 @@ const UserForm: NextPage<{ {policy.privilege} diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index a03dd5bed..582295909 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -46,7 +46,7 @@ const UserManagementRow: React.FC = ({ user }) => { }; const showMenu = menuOpen; - console.log(user); + // console.log(user); return ( <> diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index ac23bc847..a1775e0cc 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -49,10 +49,12 @@ const UserManagementTable: React.FC = () => { <> - - - - + + + + + + {users?.map((user) => ( diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index 6a0931561..58aae5b58 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -58,7 +58,10 @@ export const userApi = createApi({ providesTags: () => ['User'], }), getUserById: build.query({ - query: (id) => ({ url: `user/${id}` }), + query: (id) => { + console.log('Getting user by id', id); + return { url: `user/${id}` }; + }, providesTags: ['User'], }), getUserPermissions: build.query({ @@ -71,6 +74,7 @@ export const userApi = createApi({ method: 'POST', body: user, }), + invalidatesTags: ['User'], }), createUserPermissions: build.mutation< UserPermissions, @@ -81,6 +85,7 @@ export const userApi = createApi({ method: 'POST', body: { scopes: user.scope }, }), + invalidatesTags: ['User'], }), editUser: build.mutation & Pick>({ query: ({ id, ...patch }) => ({ @@ -138,7 +143,7 @@ export const userApi = createApi({ method: 'DELETE', }), // Invalidates all queries that subscribe to this User `id` only - invalidatesTags: (result, error, id) => [{ type: 'User', id }], + invalidatesTags: ['User'], }), }), }); diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index fe90e9738..bb1d70711 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; import type { NextPage } from 'next'; import { Box, @@ -10,7 +11,15 @@ import { import { useRouter } from 'next/router'; import NavBar from '../../../features/common/NavBar'; import UserForm from '../../../features/user-management/UserForm'; -import { useGetUserByIdQuery } from '../../../features/user/user.slice'; +import { + useGetUserByIdQuery, + userApi, + assignToken, + setManagedUser, +} from '../../../features/user/user.slice'; + +import { wrapper } from '../../../app/store'; +import { getSession } from 'next-auth/react'; const Profile: NextPage = () => { return ( @@ -40,3 +49,31 @@ const Profile: NextPage = () => { }; export default Profile; + +export const getServerSideProps = wrapper.getServerSideProps( + (store) => async (context) => { + const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { + await store.dispatch(assignToken(session.accessToken)); + console.log('Found ID on this page', context.query.id); + + if (context.query.id) { + store.dispatch( + userApi.endpoints.getUserById.initiate(context.query.id) + ); + store.dispatch( + userApi.endpoints.getUserPermissions.initiate(context.query.id) + ); + await Promise.all(userApi.util.getRunningOperationPromises()); + } + return { props: { session, query: context.query } }; + } + + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } +); From 7260bc9c3b0fa2ef1e314a0c1ea55b224a1c2ad6 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Tue, 10 May 2022 05:22:00 -0500 Subject: [PATCH 30/51] Default values --- .../admin-ui/src/features/common/Header.tsx | 5 +- .../src/features/user-management/UserForm.tsx | 94 ++++++++++++------- clients/admin-ui/src/features/user/types.ts | 2 +- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/clients/admin-ui/src/features/common/Header.tsx b/clients/admin-ui/src/features/common/Header.tsx index b61e13088..ce0a6b0e6 100644 --- a/clients/admin-ui/src/features/common/Header.tsx +++ b/clients/admin-ui/src/features/common/Header.tsx @@ -45,9 +45,10 @@ const Header: React.FC = ({ username }) => ( {username} - + {/* This text should only show if actually an admin */} + {/* Administrator - + */} { password?: string; } = {}; - if (!values.username) { + if (!values.username && !existingUser?.username) { errors.username = 'Username is required'; } - if (!values.first_name) { + if (!values.first_name && !existingUser?.first_name) { errors.first_name = 'First name is required'; } - if (!values.last_name) { + if (!values.last_name && !existingUser?.last_name) { errors.last_name = 'Last name is required'; } - if (!values.password) { + if (!values.password && !existingUser) { errors.password = 'Password is required'; } @@ -176,12 +176,16 @@ const UserForm: NextPage<{ name="username" focusBorderColor="primary.500" placeholder={ - existingUser ? existingUser?.last_name : 'Enter new username' + existingUser ? existingUser?.username : 'Enter new username' } onChange={handleChange} onBlur={handleBlur} value={values.username} - isInvalid={touched.username && Boolean(errors.username)} + isInvalid={ + !existingUser?.username && + touched.username && + Boolean(errors.username) + } isReadOnly={existingUser ? true : false} isDisabled={existingUser ? true : false} /> @@ -202,13 +206,20 @@ const UserForm: NextPage<{ focusBorderColor="primary.500" placeholder={ existingUser - ? existingUser?.last_name + ? existingUser?.first_name : 'Enter first name of user' } onChange={handleChange} onBlur={handleBlur} value={values.first_name} - isInvalid={touched.first_name && Boolean(errors.first_name)} + isInvalid={ + !existingUser?.first_name && + touched.first_name && + Boolean(errors.first_name) + } + // Only admins can edit names - need to add a check for admin role here + // isReadOnly={existingUser ? true : false} + // isDisabled={existingUser ? true : false} /> {errors.first_name} @@ -233,38 +244,53 @@ const UserForm: NextPage<{ onChange={handleChange} onBlur={handleBlur} value={values.last_name} - isInvalid={touched.last_name && Boolean(errors.last_name)} + isInvalid={ + !existingUser?.last_name && + touched.last_name && + Boolean(errors.last_name) + } + // Only admins can edit names - need to add a check for admin role here + // isReadOnly={existingUser ? true : false} + // isDisabled={existingUser ? true : false} /> {errors.last_name} - {existingUser ? ( + {/* existing use and it's that user's specific profile */} + {/* {existingUser ? (
Change Password
- ) : ( - <> - + + + Password + + - - Password - - - {errors.password} - - - )} + maxWidth={'40%'} + name="password" + focusBorderColor="primary.500" + placeholder={'********'} + type="password" + value={values.password} + onChange={handleChange} + onBlur={handleBlur} + isInvalid={ + !existingUser?.password && + touched.password && + Boolean(errors.password) + } + // Only the associated user can edit names - need to add a check for the user id here + // isReadOnly={existingUser ? true : false} + // isDisabled={existingUser ? true : false} + /> + {errors.password} + + + {/* )} */} Preferences diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 96b48965e..e4c35d037 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -24,7 +24,7 @@ export interface UsersResponse { export interface UserPrivileges { privilege: string; - scope?: string; + scope: string; } export interface UserPermissions { From 7efd4a2ba704b50000ec8f92306086be9376f4ba Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Tue, 10 May 2022 15:12:25 -0500 Subject: [PATCH 31/51] Split out edit and new forms for easier flow and initial value in formik handlin --- .../{UserForm.tsx => EditUserForm.tsx} | 67 +---- .../features/user-management/NewUserForm.tsx | 247 ++++++++++++++++++ clients/admin-ui/src/features/user/types.ts | 2 +- .../admin-ui/src/features/user/user.slice.ts | 28 +- .../src/pages/user-management/new.tsx | 4 +- .../pages/user-management/profile/[id].tsx | 4 +- 6 files changed, 279 insertions(+), 73 deletions(-) rename clients/admin-ui/src/features/user-management/{UserForm.tsx => EditUserForm.tsx} (80%) create mode 100644 clients/admin-ui/src/features/user-management/NewUserForm.tsx diff --git a/clients/admin-ui/src/features/user-management/UserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx similarity index 80% rename from clients/admin-ui/src/features/user-management/UserForm.tsx rename to clients/admin-ui/src/features/user-management/EditUserForm.tsx index 1d7c7da0c..739544ea7 100644 --- a/clients/admin-ui/src/features/user-management/UserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -22,8 +22,6 @@ import { selectManagedUser, useEditUserMutation, useUpdateUserPermissionsMutation, - useCreateUserMutation, - useCreateUserPermissionsMutation, useGetUserByIdQuery, useGetUserPermissionsQuery, } from '../user/user.slice'; @@ -32,9 +30,6 @@ import { useRouter } from 'next/router'; const useUserForm = () => { const token = useSelector(selectUserToken); - const [createUser, createUserResult] = useCreateUserMutation(); - const [createUserPermissions, createUserPermissionsResult] = - useCreateUserPermissionsMutation(); // const [editUser, editUserResult] = useEditUserMutation(); const router = useRouter(); const { id } = router.query; @@ -42,13 +37,15 @@ const useUserForm = () => { const { data: existingScopes, isLoading: scopesLoading } = useGetUserPermissionsQuery(id); + console.log(existingScopes); + const formik = useFormik({ initialValues: { - username: existingUser ? existingUser?.username : '', - first_name: existingUser ? existingUser?.first_name : '', - last_name: existingUser ? existingUser?.last_name : '', - password: existingUser ? '********' : '', - scopes: {}, + username: existingUser?.username, + first_name: existingUser?.first_name, + last_name: existingUser?.last_name, + password: '********', + scopes: existingScopes?.scopes | [], }, onSubmit: async (values) => { const host = @@ -63,26 +60,7 @@ const useUserForm = () => { password: values.password, }; - const permissionsBody = () => { - // const allScopes = [...values.scopes]; - const allScopes = []; - return allScopes; - }; - - if (!existingUser) { - createUser(userBody) - .then((result) => { - result = { ...result, scopes: permissionsBody() }; - console.log('result', result); - createUserPermissions(result); - }) - .then((result) => router.replace('/user-management')); - } else { - console.log('on edit page'); - console.log('permissionsBody', permissionsBody()); - // editUser({existingId, ...body}) - // router.push('/user-management'); - } + // edit user / user permissions }, validate: (values) => { const errors: { @@ -92,10 +70,6 @@ const useUserForm = () => { password?: string; } = {}; - if (!values.username && !existingUser?.username) { - errors.username = 'Username is required'; - } - if (!values.first_name && !existingUser?.first_name) { errors.first_name = 'First name is required'; } @@ -175,19 +149,12 @@ const UserForm: NextPage<{ maxWidth={'40%'} name="username" focusBorderColor="primary.500" - placeholder={ - existingUser ? existingUser?.username : 'Enter new username' - } + placeholder={existingUser?.username} onChange={handleChange} onBlur={handleBlur} value={values.username} - isInvalid={ - !existingUser?.username && - touched.username && - Boolean(errors.username) - } - isReadOnly={existingUser ? true : false} - isDisabled={existingUser ? true : false} + isReadOnly={true} + isDisabled={true} /> {errors.username} @@ -204,11 +171,7 @@ const UserForm: NextPage<{ maxWidth={'40%'} name="first_name" focusBorderColor="primary.500" - placeholder={ - existingUser - ? existingUser?.first_name - : 'Enter first name of user' - } + placeholder={existingUser?.first_name} onChange={handleChange} onBlur={handleBlur} value={values.first_name} @@ -236,11 +199,7 @@ const UserForm: NextPage<{ maxWidth={'40%'} name="last_name" focusBorderColor="primary.500" - placeholder={ - existingUser - ? existingUser?.last_name - : 'Enter last name of user' - } + placeholder={existingUser?.last_name} onChange={handleChange} onBlur={handleBlur} value={values.last_name} diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx new file mode 100644 index 000000000..e498bb21a --- /dev/null +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -0,0 +1,247 @@ +import React, { useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import type { NextPage } from 'next'; +import NextLink from 'next/link'; +import { useFormik } from 'formik'; +import { + Button, + chakra, + CheckboxGroup, + Checkbox, + FormControl, + FormErrorMessage, + FormLabel, + Heading, + Input, + Stack, + Text, +} from '@fidesui/react'; +import config from './config/config.json'; +import { + selectUserToken, + selectManagedUser, + useCreateUserMutation, + useUpdateUserPermissionsMutation, +} from '../user/user.slice'; +import { userPrivilegesArray, User } from '../user/types'; +import { useRouter } from 'next/router'; + +const useUserForm = () => { + const token = useSelector(selectUserToken); + const [createUser, createUserResult] = useCreateUserMutation(); + const [updateUserPermissions, updateUserPermissionsResult] = + useUpdateUserPermissionsMutation(); + const router = useRouter(); + + const formik = useFormik({ + initialValues: { + username: '', + first_name: '', + last_name: '', + password: '', + scopes: [], + }, + onSubmit: async (values) => { + const host = + process.env.NODE_ENV === 'development' + ? config.fidesops_host_development + : config.fidesops_host_production; + + const userBody = { + username: values.username, + first_name: values.first_name, + last_name: values.last_name, + password: values.password, + }; + + await createUser(userBody) + .then((result) => { + console.log('values.scopes', values.scopes); + console.log('ID?', result); + result = { id: result.data.id, scopes: values.scopes }; + console.log('result', result); + return result; + }) + .then((result) => { + console.log('PERMISSIONS TO PASS', result); + return updateUserPermissions(result); + }) + .then((result) => { + router.replace('/user-management'); + }); + }, + validate: (values) => { + const errors: { + username?: string; + first_name?: string; + last_name?: string; + password?: string; + } = {}; + + if (!values.username) { + errors.username = 'Username is required'; + } + + if (!values.password) { + errors.password = 'Password is required'; + } + + return errors; + }, + }); + + return { + ...formik, + }; +}; + +const UserForm: NextPage<{ + existingUser?: User; +}> = () => { + const { + dirty, + errors, + handleBlur, + handleChange, + handleSubmit, + isValid, + touched, + values, + } = useUserForm(); + + return ( +
+
+ + Profile + + + + + + Username + + + {errors.username} + + + + + First Name + + + + + + + Last Name + + + + + <> + + + Password + + + {errors.password} + + + + + Preferences + + Select privileges to assign to this user + + + {userPrivilegesArray.map((policy, idx) => ( + <> + + {policy.privilege} + + + ))} + + + + + + + + + +
+
+ ); +}; + +export default UserForm; diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index e4c35d037..8dfcba039 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -31,7 +31,7 @@ export interface UserPermissions { data?: { id?: string; }; - scope?: string; + scopes?: string; } export const userPrivilegesArray: UserPrivileges[] = [ diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index 58aae5b58..f70591a01 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -117,25 +117,25 @@ export const userApi = createApi({ UserPermissions, Partial & Pick >({ - query: ({ id, ...put }) => ({ + query: ({ id, scopes }) => ({ url: `user/${id}/permission`, method: 'PUT', - body: put, + body: { id, scopes }, }), invalidatesTags: ['User'], // For optimistic updates - async onQueryStarted({ id, ...put }, { dispatch, queryFulfilled }) { - const putResult = dispatch( - userApi.util.updateQueryData('getUserPermissions', id, (draft) => { - Object.assign(draft, put); - }) - ); - try { - await queryFulfilled; - } catch { - putResult.undo(); - } - }, + // async onQueryStarted({ id, scopes }, { dispatch, queryFulfilled }) { + // const putResult = dispatch( + // userApi.util.updateQueryData('getUserPermissions', id, (draft) => { + // Object.assign(draft, scopes); + // }) + // ); + // try { + // await queryFulfilled; + // } catch { + // putResult.undo(); + // } + // }, }), deleteUser: build.mutation<{ success: boolean; id: string }, string>({ query: (id) => ({ diff --git a/clients/admin-ui/src/pages/user-management/new.tsx b/clients/admin-ui/src/pages/user-management/new.tsx index 692e2b92d..25a80fa00 100644 --- a/clients/admin-ui/src/pages/user-management/new.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -12,7 +12,7 @@ import NavBar from '../../features/common/NavBar'; // import UserManagementTable from '../features/user-management/UserManagementTable'; // import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; -import UserForm from '../../features/user-management/UserForm'; +import NewUserForm from '../../features/user-management/NewUserForm'; import { assignToken } from '../../features/user/user.slice'; import { getSession } from 'next-auth/react'; @@ -39,7 +39,7 @@ const CreateNewUser: NextPage<{ session: { username: string } }> = ({
- + diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index bb1d70711..45319ba7b 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -10,7 +10,7 @@ import { } from '@fidesui/react'; import { useRouter } from 'next/router'; import NavBar from '../../../features/common/NavBar'; -import UserForm from '../../../features/user-management/UserForm'; +import EditUserForm from '../../../features/user-management/EditUserForm'; import { useGetUserByIdQuery, userApi, @@ -41,7 +41,7 @@ const Profile: NextPage = () => { - + From 048335830ee5c704d25022f3373358a5c00e8cfa Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Wed, 11 May 2022 05:42:16 -0500 Subject: [PATCH 32/51] Type checking --- clients/admin-ui/src/app/store.ts | 1 - .../features/user-management/EditUserForm.tsx | 65 ++++++------------- .../features/user-management/NewUserForm.tsx | 50 ++++++++------ .../user-management/UserManagementRow.tsx | 2 - clients/admin-ui/src/features/user/types.ts | 34 ++++++---- .../admin-ui/src/features/user/user.slice.ts | 37 ++++++----- .../pages/user-management/profile/[id].tsx | 7 +- 7 files changed, 92 insertions(+), 104 deletions(-) diff --git a/clients/admin-ui/src/app/store.ts b/clients/admin-ui/src/app/store.ts index 8f895378c..57b366aaa 100644 --- a/clients/admin-ui/src/app/store.ts +++ b/clients/admin-ui/src/app/store.ts @@ -15,7 +15,6 @@ const makeStore = () => { subjectRequests: privacyRequestsReducer, [userApi.reducerPath]: userApi.reducer, user: userReducer, - managedUser: userReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat( diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 739544ea7..8304d3970 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; @@ -19,7 +20,6 @@ import { import config from './config/config.json'; import { selectUserToken, - selectManagedUser, useEditUserMutation, useUpdateUserPermissionsMutation, useGetUserByIdQuery, @@ -33,9 +33,9 @@ const useUserForm = () => { // const [editUser, editUserResult] = useEditUserMutation(); const router = useRouter(); const { id } = router.query; - const { data: existingUser } = useGetUserByIdQuery(id); + const { data: existingUser } = useGetUserByIdQuery(id as string); const { data: existingScopes, isLoading: scopesLoading } = - useGetUserPermissionsQuery(id); + useGetUserPermissionsQuery(id as string); console.log(existingScopes); @@ -70,14 +70,6 @@ const useUserForm = () => { password?: string; } = {}; - if (!values.first_name && !existingUser?.first_name) { - errors.first_name = 'First name is required'; - } - - if (!values.last_name && !existingUser?.last_name) { - errors.last_name = 'Last name is required'; - } - if (!values.password && !existingUser) { errors.password = 'Password is required'; } @@ -92,7 +84,7 @@ const useUserForm = () => { formik.setFieldValue( 'scopes', existingScopes.scopes.reduce( - (scopes, scope) => ({ + (scopes: string[], scope: string) => ({ ...scopes, [scope]: true, }), @@ -137,10 +129,7 @@ const UserForm: NextPage<{ width="100%" > - + Username @@ -156,13 +145,9 @@ const UserForm: NextPage<{ isReadOnly={true} isDisabled={true} /> - {errors.username} - + First Name @@ -175,22 +160,13 @@ const UserForm: NextPage<{ onChange={handleChange} onBlur={handleBlur} value={values.first_name} - isInvalid={ - !existingUser?.first_name && - touched.first_name && - Boolean(errors.first_name) - } // Only admins can edit names - need to add a check for admin role here - // isReadOnly={existingUser ? true : false} - // isDisabled={existingUser ? true : false} + // isReadOnly={existingUser && !adminUser ? true : false} + // isDisabled={existingUser ? && !adminUser ? true : false} /> - {errors.first_name} - + Last Name @@ -203,20 +179,14 @@ const UserForm: NextPage<{ onChange={handleChange} onBlur={handleBlur} value={values.last_name} - isInvalid={ - !existingUser?.last_name && - touched.last_name && - Boolean(errors.last_name) - } // Only admins can edit names - need to add a check for admin role here - // isReadOnly={existingUser ? true : false} - // isDisabled={existingUser ? true : false} + // isReadOnly={existingUser && !adminUser ? true : false} + // isDisabled={existingUser && !adminUser ? true : false} /> - {errors.last_name} - {/* existing use and it's that user's specific profile */} - {/* {existingUser ? ( + {/* existing user and it's that user's specific profile */} + {/* {existingUser && !adminUser ? (
Change Password
) : ( */} <> @@ -243,8 +213,8 @@ const UserForm: NextPage<{ Boolean(errors.password) } // Only the associated user can edit names - need to add a check for the user id here - // isReadOnly={existingUser ? true : false} - // isDisabled={existingUser ? true : false} + // isReadOnly={existingUser && adminUser ? true : false} + // isDisabled={existingUser && adminUser ? true : false} /> {errors.password}
@@ -252,7 +222,7 @@ const UserForm: NextPage<{ {/* )} */} - Preferences + Privileges Select privileges to assign to this user @@ -266,6 +236,9 @@ const UserForm: NextPage<{ name="scopes" isChecked={values.scopes[policy.scope]} > + {/* Only admins can edit privileges - need to add a check for admin role here */} + {/* isReadOnly={existingUser && !adminUser ? true : false} */} + {/* isDisabled={existingUser && !adminUser ? true : false} */} {policy.privilege} diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx index e498bb21a..52c79d07c 100644 --- a/clients/admin-ui/src/features/user-management/NewUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -19,11 +19,10 @@ import { import config from './config/config.json'; import { selectUserToken, - selectManagedUser, useCreateUserMutation, useUpdateUserPermissionsMutation, } from '../user/user.slice'; -import { userPrivilegesArray, User } from '../user/types'; +import { userPrivilegesArray, User, UserResponse } from '../user/types'; import { useRouter } from 'next/router'; const useUserForm = () => { @@ -56,17 +55,21 @@ const useUserForm = () => { await createUser(userBody) .then((result) => { - console.log('values.scopes', values.scopes); - console.log('ID?', result); - result = { id: result.data.id, scopes: values.scopes }; - console.log('result', result); - return result; + const userWithPrivileges = { + id: 'data' in result ? result.data.id : null, + scopes: values.scopes, + }; + + return userWithPrivileges; }) .then((result) => { console.log('PERMISSIONS TO PASS', result); - return updateUserPermissions(result); + const permissionsToAddToUser = updateUserPermissions(result); + + console.log(permissionsToAddToUser); + return permissionsToAddToUser; }) - .then((result) => { + .then(() => { router.replace('/user-management'); }); }, @@ -205,18 +208,23 @@ const UserForm: NextPage<{ {userPrivilegesArray.map((policy, idx) => ( - <> - - {policy.privilege} - - + + {policy.privilege} + ))} diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index 582295909..105d0893e 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -17,7 +17,6 @@ import { MoreIcon } from '../common/Icon'; import DeleteUserModal from './DeleteUserModal'; import { User } from '../user/types'; import { useRouter } from 'next/router'; -import { setManagedUser } from '../user/user.slice'; interface UserManagementRowProps { user: User; @@ -41,7 +40,6 @@ const UserManagementRow: React.FC = ({ user }) => { const dispatch = useDispatch(); const handleEditUser = () => { - dispatch(setManagedUser(user)); router.push(`/user-management/profile/${user.id}`); }; diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index 8dfcba039..e4d55a549 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -1,25 +1,38 @@ export interface User { - id?: string; + id: string; first_name?: string; last_name?: string; - username?: string; - password?: string; - created_at?: string; + username: string; + password: string; + created_at: string; } export interface UserResponse { + data: {}; id: string; } +export interface UsersResponse { + items: User[]; + total: number; +} + export interface UsersListParams { page: number; size: number; user: User; } -export interface UsersResponse { - items: User[]; - total: number; +export interface UserPermissionsUpdate { + id: string | null; + scopes: never[]; +} + +export interface UserPermissionsResponse { + data: { + id: string; + }; + scope: string[]; } export interface UserPrivileges { @@ -27,13 +40,6 @@ export interface UserPrivileges { scope: string; } -export interface UserPermissions { - data?: { - id?: string; - }; - scopes?: string; -} - export const userPrivilegesArray: UserPrivileges[] = [ { privilege: 'View subject requests', diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index f70591a01..cc7456ebc 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -3,7 +3,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { HYDRATE } from 'next-redux-wrapper'; import type { AppState } from '../../app/store'; -import { User, UsersListParams, UserPermissions, UsersResponse } from './types'; +import { + User, + UsersListParams, + UserPermissionsUpdate, + UserPermissionsResponse, + UserResponse, + UsersResponse, +} from './types'; export interface State { id: string; @@ -11,16 +18,19 @@ export interface State { size: number; user: User; token: string | null; - managedUser: User | null; } const initialState: State = { id: '', page: 1, size: 25, - user: {}, + user: { + id: '', + password: '', + username: '', + created_at: '', + }, token: null, - managedUser: null, }; // Helpers @@ -68,7 +78,7 @@ export const userApi = createApi({ query: (id) => ({ url: `user/${id}/permission` }), providesTags: ['User'], }), - createUser: build.mutation>({ + createUser: build.mutation>({ query: (user) => ({ url: 'user', method: 'POST', @@ -77,8 +87,8 @@ export const userApi = createApi({ invalidatesTags: ['User'], }), createUserPermissions: build.mutation< - UserPermissions, - Partial + UserPermissionsResponse, + Partial >({ query: (user) => ({ url: `user/${user?.data?.id}/permission`, @@ -114,8 +124,8 @@ export const userApi = createApi({ }, }), updateUserPermissions: build.mutation< - UserPermissions, - Partial & Pick + UserPermissionsUpdate, + Partial & Pick >({ query: ({ id, scopes }) => ({ url: `user/${id}/permission`, @@ -181,10 +191,6 @@ export const userSlice = createSlice({ page: initialState.page, size: action.payload, }), - setManagedUser: (state, action: PayloadAction) => ({ - ...state, - managedUser: action.payload, - }), }, extraReducers: { [HYDRATE]: (state, action) => ({ @@ -194,8 +200,7 @@ export const userSlice = createSlice({ }, }); -export const { assignToken, setManagedUser, setUser, setPage } = - userSlice.actions; +export const { assignToken, setUser, setPage } = userSlice.actions; export const selectUserToken = (state: AppState) => state.user.token; @@ -205,6 +210,4 @@ export const selectUserFilters = (state: AppState): UsersListParams => ({ user: state.user.user, }); -export const selectManagedUser = (state: AppState) => state.user.managedUser; - export const { reducer } = userSlice; diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index 45319ba7b..9b7b3a16e 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -15,7 +15,6 @@ import { useGetUserByIdQuery, userApi, assignToken, - setManagedUser, } from '../../../features/user/user.slice'; import { wrapper } from '../../../app/store'; @@ -59,10 +58,12 @@ export const getServerSideProps = wrapper.getServerSideProps( if (context.query.id) { store.dispatch( - userApi.endpoints.getUserById.initiate(context.query.id) + userApi.endpoints.getUserById.initiate(context.query.id as string) ); store.dispatch( - userApi.endpoints.getUserPermissions.initiate(context.query.id) + userApi.endpoints.getUserPermissions.initiate( + context.query.id as string + ) ); await Promise.all(userApi.util.getRunningOperationPromises()); } From b0665a506ff1fe823dcc811706b24e26262a7b69 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Wed, 11 May 2022 05:48:37 -0500 Subject: [PATCH 33/51] Some file cleanup --- .../src/features/user-management/DeleteUserModal.tsx | 1 - .../src/features/user-management/EditUserForm.tsx | 2 -- .../src/features/user-management/NewUserForm.tsx | 3 +-- .../features/user-management/UserManagementRow.tsx | 2 -- clients/admin-ui/src/features/user/types.ts | 8 ++++---- clients/admin-ui/src/features/user/user.slice.ts | 2 +- clients/admin-ui/src/pages/user-management/index.tsx | 3 +-- clients/admin-ui/src/pages/user-management/new.tsx | 3 --- .../src/pages/user-management/profile/[id].tsx | 11 ++--------- 9 files changed, 9 insertions(+), 26 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx index 4cd00fa44..a49606ed0 100644 --- a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx +++ b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import { Button, FormControl, - FormLabel, Input, MenuItem, Modal, diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 8304d3970..828fd944f 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -37,8 +37,6 @@ const useUserForm = () => { const { data: existingScopes, isLoading: scopesLoading } = useGetUserPermissionsQuery(id as string); - console.log(existingScopes); - const formik = useFormik({ initialValues: { username: existingUser?.username, diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx index 52c79d07c..00f45520e 100644 --- a/clients/admin-ui/src/features/user-management/NewUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; import NextLink from 'next/link'; @@ -66,7 +66,6 @@ const useUserForm = () => { console.log('PERMISSIONS TO PASS', result); const permissionsToAddToUser = updateUserPermissions(result); - console.log(permissionsToAddToUser); return permissionsToAddToUser; }) .then(() => { diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index 105d0893e..fcbba1fc6 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -37,14 +37,12 @@ const useUserManagementRow = () => { const UserManagementRow: React.FC = ({ user }) => { const { handleMenuOpen, handleMenuClose, menuOpen } = useUserManagementRow(); const router = useRouter(); - const dispatch = useDispatch(); const handleEditUser = () => { router.push(`/user-management/profile/${user.id}`); }; const showMenu = menuOpen; - // console.log(user); return ( <> diff --git a/clients/admin-ui/src/features/user/types.ts b/clients/admin-ui/src/features/user/types.ts index e4d55a549..95656992f 100644 --- a/clients/admin-ui/src/features/user/types.ts +++ b/clients/admin-ui/src/features/user/types.ts @@ -1,10 +1,10 @@ export interface User { - id: string; + id?: string; first_name?: string; last_name?: string; - username: string; - password: string; - created_at: string; + username?: string; + password?: string; + created_at?: string; } export interface UserResponse { diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index cc7456ebc..d93cc460e 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -69,7 +69,6 @@ export const userApi = createApi({ }), getUserById: build.query({ query: (id) => { - console.log('Getting user by id', id); return { url: `user/${id}` }; }, providesTags: ['User'], @@ -107,6 +106,7 @@ export const userApi = createApi({ // For optimistic updates async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) { const patchResult = dispatch( + // @ts-ignore userApi.util.updateQueryData('getUserById', id, (draft) => { Object.assign(draft, patch); }) diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx index 9f8f2cab0..48763517f 100644 --- a/clients/admin-ui/src/pages/user-management/index.tsx +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import type { NextPage } from 'next'; import Head from 'next/head'; import { Box, Heading } from '@fidesui/react'; import { getSession } from 'next-auth/react'; -import { useRouter } from 'next/router'; import { wrapper } from '../../app/store'; import { assignToken } from '../../features/user/user.slice'; diff --git a/clients/admin-ui/src/pages/user-management/new.tsx b/clients/admin-ui/src/pages/user-management/new.tsx index 25a80fa00..5e9228ea9 100644 --- a/clients/admin-ui/src/pages/user-management/new.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -9,9 +9,6 @@ import { } from '@fidesui/react'; import NavBar from '../../features/common/NavBar'; - -// import UserManagementTable from '../features/user-management/UserManagementTable'; -// import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; import NewUserForm from '../../features/user-management/NewUserForm'; import { assignToken } from '../../features/user/user.slice'; diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index 9b7b3a16e..e9fde4986 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -1,5 +1,4 @@ -import React, { useEffect } from 'react'; -import { useDispatch } from 'react-redux'; +import React from 'react'; import type { NextPage } from 'next'; import { Box, @@ -8,14 +7,9 @@ import { BreadcrumbLink, Heading, } from '@fidesui/react'; -import { useRouter } from 'next/router'; import NavBar from '../../../features/common/NavBar'; import EditUserForm from '../../../features/user-management/EditUserForm'; -import { - useGetUserByIdQuery, - userApi, - assignToken, -} from '../../../features/user/user.slice'; +import { userApi, assignToken } from '../../../features/user/user.slice'; import { wrapper } from '../../../app/store'; import { getSession } from 'next-auth/react'; @@ -54,7 +48,6 @@ export const getServerSideProps = wrapper.getServerSideProps( const session = await getSession(context); if (session && typeof session.accessToken !== 'undefined') { await store.dispatch(assignToken(session.accessToken)); - console.log('Found ID on this page', context.query.id); if (context.query.id) { store.dispatch( From 7fd6a8a7d3985bdc3ec7197f7b35222901057e6b Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Wed, 11 May 2022 05:56:52 -0500 Subject: [PATCH 34/51] Pagination; --- .../user-management/UserManagementTable.tsx | 76 ++++++++++++++----- 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index a1775e0cc..6e33bcfab 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -1,10 +1,23 @@ -import React, { useState } from 'react'; +import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { Table, Thead, Tbody, Tr, Th } from '@fidesui/react'; +import { + Button, + Flex, + Table, + Tbody, + Text, + Thead, + Tr, + Th, +} from '@fidesui/react'; import UserManagementRow from './UserManagementRow'; -import { selectUserFilters, useGetAllUsersQuery } from '../user/user.slice'; +import { + selectUserFilters, + setPage, + useGetAllUsersQuery, +} from '../user/user.slice'; import { User } from '../user/types'; interface UsersTableProps { @@ -15,13 +28,13 @@ const useUsersTable = () => { const dispatch = useDispatch(); const filters = useSelector(selectUserFilters); - // const handlePreviousPage = () => { - // dispatch(setPage(filters.page - 1)); - // }; + const handlePreviousPage = () => { + dispatch(setPage(filters.page - 1)); + }; - // const handleNextPage = () => { - // dispatch(setPage(filters.page + 1)); - // }; + const handleNextPage = () => { + dispatch(setPage(filters.page + 1)); + }; const { data, isLoading } = useGetAllUsersQuery(filters); const { items: users, total } = data || { users: [], total: 0 }; @@ -31,19 +44,16 @@ const useUsersTable = () => { isLoading, users, total, - // handleNextPage, - // handlePreviousPage, + handleNextPage, + handlePreviousPage, }; }; const UserManagementTable: React.FC = () => { - const { - users, - total, - // page, size, handleNextPage, handlePreviousPage - } = useUsersTable(); - // const startingItem = (page - 1) * size + 1; - // const endingItem = Math.min(total, page * size); + const { users, total, page, size, handleNextPage, handlePreviousPage } = + useUsersTable(); + const startingItem = (page - 1) * size + 1; + const endingItem = Math.min(total, page * size); return ( <> @@ -62,6 +72,36 @@ const UserManagementTable: React.FC = () => { ))}
UsernameFirst NameLast NameCreated At
UsernameFirst NameLast NameCreated At
+ + + {total > 0 ? ( + <> + Showing {Number.isNaN(startingItem) ? 0 : startingItem} to{' '} + {Number.isNaN(endingItem) ? 0 : endingItem} of{' '} + {Number.isNaN(total) ? 0 : total} results + + ) : ( + '0 results' + )} + +
+ + +
+
); }; From c14dcce965a9dcea59afb8ec5a77d91ac8059e8a Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Wed, 11 May 2022 06:44:38 -0500 Subject: [PATCH 35/51] Some style tweaks --- .../features/user-management/EditUserForm.tsx | 12 ++++++---- .../features/user-management/NewUserForm.tsx | 13 ++++++---- .../src/pages/user-management/index.tsx | 2 +- .../src/pages/user-management/new.tsx | 24 ++++++++++--------- .../pages/user-management/profile/[id].tsx | 24 ++++++++++--------- 5 files changed, 43 insertions(+), 32 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 828fd944f..862ebeb74 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -9,6 +9,7 @@ import { chakra, CheckboxGroup, Checkbox, + Divider, FormControl, FormErrorMessage, FormLabel, @@ -118,15 +119,16 @@ const UserForm: NextPage<{ return (
- + Profile + - + Username @@ -218,11 +220,13 @@ const UserForm: NextPage<{ {/* )} */} + Privileges - Select privileges to assign to this user + Edit privileges assigned to this user + {userPrivilegesArray.map((policy, idx) => ( @@ -246,7 +250,7 @@ const UserForm: NextPage<{ - diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx index 00f45520e..f45e459d0 100644 --- a/clients/admin-ui/src/features/user-management/NewUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -8,6 +8,7 @@ import { chakra, CheckboxGroup, Checkbox, + Divider, FormControl, FormErrorMessage, FormLabel, @@ -114,15 +115,16 @@ const UserForm: NextPage<{ return (
- + Profile + - + {errors.password} - + - Preferences + Privileges Select privileges to assign to this user + {userPrivilegesArray.map((policy, idx) => ( @@ -230,7 +233,7 @@ const UserForm: NextPage<{ - diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx index 48763517f..2cbb21103 100644 --- a/clients/admin-ui/src/pages/user-management/index.tsx +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -26,7 +26,7 @@ const UserManagement: NextPage<{ session: { username: string } }> = ({
- + User Management diff --git a/clients/admin-ui/src/pages/user-management/new.tsx b/clients/admin-ui/src/pages/user-management/new.tsx index 5e9228ea9..db6777d8b 100644 --- a/clients/admin-ui/src/pages/user-management/new.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -22,19 +22,21 @@ const CreateNewUser: NextPage<{ session: { username: string } }> = ({
- + User Management - - - - User Management - - + + + + + User Management + + - - Add New User - - + + Add New User + + + diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index e9fde4986..9159ba470 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -20,19 +20,21 @@ const Profile: NextPage = () => {
- + User Management - - - - User Management - - + + + + + User Management + + - - Edit User - - + + Edit User + + + From ec15cfd51b941937d813a8375c06dc6598bbeecf Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Wed, 11 May 2022 06:49:15 -0500 Subject: [PATCH 36/51] Change checkbox color --- clients/admin-ui/src/features/user-management/EditUserForm.tsx | 3 ++- clients/admin-ui/src/features/user-management/NewUserForm.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 862ebeb74..616f61076 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -227,11 +227,12 @@ const UserForm: NextPage<{ Edit privileges assigned to this user - + {userPrivilegesArray.map((policy, idx) => ( <> Select privileges to assign to this user - + {userPrivilegesArray.map((policy, idx) => ( Date: Thu, 12 May 2022 06:15:27 -0500 Subject: [PATCH 37/51] Checkboxes --- .../features/user-management/EditUserForm.tsx | 109 ++++++++++-------- .../features/user-management/NewUserForm.tsx | 54 ++++----- .../admin-ui/src/features/user/user.slice.ts | 2 +- 3 files changed, 91 insertions(+), 74 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 616f61076..84a7aae2f 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -7,7 +7,6 @@ import { useFormik } from 'formik'; import { Button, chakra, - CheckboxGroup, Checkbox, Divider, FormControl, @@ -31,9 +30,11 @@ import { useRouter } from 'next/router'; const useUserForm = () => { const token = useSelector(selectUserToken); - // const [editUser, editUserResult] = useEditUserMutation(); const router = useRouter(); const { id } = router.query; + const [updateUserPermissions, updateUserPermissionsResult] = + useUpdateUserPermissionsMutation(); + const [editUser, editUserResult] = useEditUserMutation(id as string); const { data: existingUser } = useGetUserByIdQuery(id as string); const { data: existingScopes, isLoading: scopesLoading } = useGetUserPermissionsQuery(id as string); @@ -44,7 +45,8 @@ const useUserForm = () => { first_name: existingUser?.first_name, last_name: existingUser?.last_name, password: '********', - scopes: existingScopes?.scopes | [], + scopes: existingScopes?.scopes, + id: existingUser?.id, }, onSubmit: async (values) => { const host = @@ -53,13 +55,34 @@ const useUserForm = () => { : config.fidesops_host_production; const userBody = { - username: values.username, - first_name: values.first_name, - last_name: values.last_name, - password: values.password, + username: values.username ? values.username : existingUser?.username, + first_name: values.first_name + ? values.first_name + : existingUser?.first_name, + last_name: values.last_name + ? values.last_name + : existingUser?.last_name, + password: values.password ? values.password : existingUser?.password, + id: existingUser?.id, }; - // edit user / user permissions + console.log(values.scopes); + + await editUser(userBody) + .then((result) => { + const userWithPrivileges = { + id: 'data' in result ? result.data.id : null, + scopes: [...Object.keys(values.scopes), 'privacy-request:read'], + }; + return userWithPrivileges; + }) + .then((result) => { + updateUserPermissions(result); + return result; + }) + .then(() => { + router.replace('/user-management'); + }); }, validate: (values) => { const errors: { @@ -77,26 +100,16 @@ const useUserForm = () => { }, }); - useEffect(() => { - // TODO: write in some error handling - if (existingScopes) { - formik.setFieldValue( - 'scopes', - existingScopes.scopes.reduce( - (scopes: string[], scope: string) => ({ - ...scopes, - [scope]: true, - }), - {} as { - [key: string]: boolean; - } - ) - ); - } - }, [scopesLoading]); + // useEffect(() => { + // // TODO: write in some error handling + // if (existingScopes) { + // formik.setFieldValue('scopes', existingScopes.scopes); + // } + // }, [scopesLoading]); return { ...formik, + existingScopes, existingUser, }; }; @@ -107,6 +120,7 @@ const UserForm: NextPage<{ const { dirty, errors, + existingScopes, existingUser, handleBlur, handleChange, @@ -116,6 +130,9 @@ const UserForm: NextPage<{ values, } = useUserForm(); + console.log(values.scopes); + console.log(existingScopes?.scopes); + return (
@@ -227,27 +244,27 @@ const UserForm: NextPage<{ Edit privileges assigned to this user - - - {userPrivilegesArray.map((policy, idx) => ( - <> - - {/* Only admins can edit privileges - need to add a check for admin role here */} - {/* isReadOnly={existingUser && !adminUser ? true : false} */} - {/* isDisabled={existingUser && !adminUser ? true : false} */} - {policy.privilege} - - - ))} - - + + + {userPrivilegesArray.map((policy, idx) => ( + + {policy.privilege} + + ))} + diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx index 314131cfb..4d8ee1057 100644 --- a/clients/admin-ui/src/features/user-management/NewUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -6,7 +6,6 @@ import { useFormik } from 'formik'; import { Button, chakra, - CheckboxGroup, Checkbox, Divider, FormControl, @@ -58,14 +57,15 @@ const useUserForm = () => { .then((result) => { const userWithPrivileges = { id: 'data' in result ? result.data.id : null, - scopes: values.scopes, + scopes: [...values.scopes, 'privacy-request:read'], }; return userWithPrivileges; }) .then((result) => { - console.log('PERMISSIONS TO PASS', result); - const permissionsToAddToUser = updateUserPermissions(result); + const permissionsToAddToUser = updateUserPermissions( + result as { id: string } + ); return permissionsToAddToUser; }) @@ -207,29 +207,29 @@ const UserForm: NextPage<{ Select privileges to assign to this user - - - {userPrivilegesArray.map((policy, idx) => ( - - {policy.privilege} - - ))} - - + + + {userPrivilegesArray.map((policy, idx) => ( + + {policy.privilege} + + ))} + diff --git a/clients/admin-ui/src/features/user/user.slice.ts b/clients/admin-ui/src/features/user/user.slice.ts index d93cc460e..8d235a985 100644 --- a/clients/admin-ui/src/features/user/user.slice.ts +++ b/clients/admin-ui/src/features/user/user.slice.ts @@ -99,7 +99,7 @@ export const userApi = createApi({ editUser: build.mutation & Pick>({ query: ({ id, ...patch }) => ({ url: `user/${id}`, - method: 'PATCH', + method: 'PUT', body: patch, }), invalidatesTags: ['User'], From 38de12582d05ae9cf5cf2e0aa8475bd2d9dc5e02 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Thu, 12 May 2022 11:16:45 -0500 Subject: [PATCH 38/51] Cleanup --- .../user-management/DeleteUserModal.tsx | 3 - .../features/user-management/EditUserForm.tsx | 63 +++++++++++-------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx index a49606ed0..857c2bcd6 100644 --- a/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx +++ b/clients/admin-ui/src/features/user-management/DeleteUserModal.tsx @@ -46,9 +46,6 @@ function DeleteUserModal(user: User) { if (deletionValidation && user.id) { deleteUser(user.id); onClose(); - } else { - console.log('Cant delete'); - // throw error/alert ? } }; diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 84a7aae2f..8f8a5bf8f 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React, { useEffect } from 'react'; +import React, { ChangeEvent, useEffect } from 'react'; import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; import NextLink from 'next/link'; @@ -48,6 +48,7 @@ const useUserForm = () => { scopes: existingScopes?.scopes, id: existingUser?.id, }, + enableReinitialize: true, onSubmit: async (values) => { const host = process.env.NODE_ENV === 'development' @@ -66,13 +67,11 @@ const useUserForm = () => { id: existingUser?.id, }; - console.log(values.scopes); - await editUser(userBody) .then((result) => { const userWithPrivileges = { id: 'data' in result ? result.data.id : null, - scopes: [...Object.keys(values.scopes), 'privacy-request:read'], + scopes: [...values.scopes, 'privacy-request:read'], }; return userWithPrivileges; }) @@ -128,11 +127,9 @@ const UserForm: NextPage<{ isValid, touched, values, + setFieldValue, } = useUserForm(); - console.log(values.scopes); - console.log(existingScopes?.scopes); - return (
@@ -246,24 +243,40 @@ const UserForm: NextPage<{ - {userPrivilegesArray.map((policy, idx) => ( - - {policy.privilege} - - ))} + {userPrivilegesArray.map((policy, idx) => { + const isChecked = values.scopes + ? values.scopes.indexOf(policy.scope) >= 0 + : false; + return ( + { + if (!isChecked) { + setFieldValue(`scopes`, [ + ...values.scopes, + policy.scope, + ]); + } else { + setFieldValue( + 'scopes', + values.scopes.filter( + (scope) => scope !== policy.scope + ) + ); + } + }} + id={`scopes-${policy.privilege}-${idx}`} + name="scopes" + value={policy.scope} + isDisabled={policy.scope === 'privacy-request:read'} + isReadOnly={policy.scope === 'privacy-request:read'} + > + {policy.privilege} + + ); + })} From fc3663ddd7f16e2088cbefe5304412f0bf653484 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Thu, 12 May 2022 11:26:15 -0500 Subject: [PATCH 39/51] Remove unused effect --- .../admin-ui/src/features/user-management/EditUserForm.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 8f8a5bf8f..b3ab6acf9 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -99,13 +99,6 @@ const useUserForm = () => { }, }); - // useEffect(() => { - // // TODO: write in some error handling - // if (existingScopes) { - // formik.setFieldValue('scopes', existingScopes.scopes); - // } - // }, [scopesLoading]); - return { ...formik, existingScopes, From f0a9d8fa864fc9faaf73d14797392ef5b5cd90f4 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 13 May 2022 11:11:55 -0500 Subject: [PATCH 40/51] Update session with user info and fix client console warning for controlled input --- .../features/user-management/EditUserForm.tsx | 10 +++++----- .../admin-ui/src/pages/api/auth/[...nextauth].ts | 13 +++++-------- clients/admin-ui/src/pages/index.tsx | 7 +++++-- clients/admin-ui/src/types/next-auth.d.ts | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index b3ab6acf9..1e14543e9 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -41,12 +41,12 @@ const useUserForm = () => { const formik = useFormik({ initialValues: { - username: existingUser?.username, - first_name: existingUser?.first_name, - last_name: existingUser?.last_name, + username: existingUser?.username ?? '', + first_name: existingUser?.first_name ?? '', + last_name: existingUser?.last_name ?? '', password: '********', - scopes: existingScopes?.scopes, - id: existingUser?.id, + scopes: existingScopes?.scopes ?? '', + id: existingUser?.id ?? '', }, enableReinitialize: true, onSubmit: async (values) => { diff --git a/clients/admin-ui/src/pages/api/auth/[...nextauth].ts b/clients/admin-ui/src/pages/api/auth/[...nextauth].ts index b14d8a61d..b33b06c89 100644 --- a/clients/admin-ui/src/pages/api/auth/[...nextauth].ts +++ b/clients/admin-ui/src/pages/api/auth/[...nextauth].ts @@ -37,10 +37,7 @@ export default NextAuth({ // If no error and we have user data, return it if (res.ok && user) { - return { - ...user, - username: credentials!.email, - }; + return user; } // Return null if user data could not be retrieved @@ -50,10 +47,10 @@ export default NextAuth({ ], callbacks: { async jwt({ token, user }) { - if (user?.access_token) { + if (user?.token_data?.access_token) { Object.assign(token, { - token: user.access_token, - username: user.username, + token: user.token_data.access_token, + user: user.user_data, }); } @@ -62,7 +59,7 @@ export default NextAuth({ async session({ session, token }) { Object.assign(session, { accessToken: token.token, - username: token.username, + user: token.user, }); return session; }, diff --git a/clients/admin-ui/src/pages/index.tsx b/clients/admin-ui/src/pages/index.tsx index 0a2770b21..d75d85deb 100644 --- a/clients/admin-ui/src/pages/index.tsx +++ b/clients/admin-ui/src/pages/index.tsx @@ -4,14 +4,15 @@ import Head from 'next/head'; import { Heading, Box } from '@fidesui/react'; import { getSession } from 'next-auth/react'; import { wrapper } from '../app/store'; -import { assignToken } from '../features/user/user.slice'; +import { assignToken, setUser } from '../features/user/user.slice'; +import { User } from '../features/user/types'; import NavBar from '../features/common/NavBar'; import RequestTable from '../features/privacy-requests/RequestTable'; import RequestFilters from '../features/privacy-requests/RequestFilters'; -const Home: NextPage<{ session: { username: string } }> = ({ session }) => { +const Home: NextPage<{ session: { user: User } }> = ({ session }) => { return (
@@ -38,8 +39,10 @@ const Home: NextPage<{ session: { username: string } }> = ({ session }) => { export const getServerSideProps = wrapper.getServerSideProps( (store) => async (context) => { const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { await store.dispatch(assignToken(session.accessToken)); + await store.dispatch(setUser(session.user)); return { props: { session } }; } diff --git a/clients/admin-ui/src/types/next-auth.d.ts b/clients/admin-ui/src/types/next-auth.d.ts index adcec1394..ae7ce77a7 100644 --- a/clients/admin-ui/src/types/next-auth.d.ts +++ b/clients/admin-ui/src/types/next-auth.d.ts @@ -1,6 +1,7 @@ // required import to teach TypeScript to pick up the types // eslint-disable-next-line @typescript-eslint/no-unused-vars import NextAuth from 'next-auth'; +import { User as ClientUser } from '../features/user/types'; declare module 'next-auth' { /** @@ -8,5 +9,20 @@ declare module 'next-auth' { */ interface Session { accessToken: string; + user: ClientUser; + } + + interface User { + token_data: { + access_token: string; + }; + + user_data: { + id: string; + username: string; + created_at: string; + first_name: string; + last_name: string; + }; } } From c9dae35d5fefbe8d2451ba672840e123499a5f95 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 13 May 2022 11:34:13 -0500 Subject: [PATCH 41/51] Update session in pages --- clients/admin-ui/src/pages/user-management/index.tsx | 12 ++++++------ clients/admin-ui/src/pages/user-management/new.tsx | 10 +++++----- .../src/pages/user-management/profile/[id].tsx | 12 +++++++++--- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx index 2cbb21103..8b7dcd416 100644 --- a/clients/admin-ui/src/pages/user-management/index.tsx +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -3,17 +3,15 @@ import type { NextPage } from 'next'; import Head from 'next/head'; import { Box, Heading } from '@fidesui/react'; import { getSession } from 'next-auth/react'; -import { wrapper } from '../../app/store'; -import { assignToken } from '../../features/user/user.slice'; +import { wrapper } from '../../app/store'; +import { assignToken, setUser } from '../../features/user/user.slice'; +import { User } from '../../features/user/types'; import NavBar from '../../features/common/NavBar'; - import UserManagementTable from '../../features/user-management/UserManagementTable'; import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; -const UserManagement: NextPage<{ session: { username: string } }> = ({ - session, -}) => { +const UserManagement: NextPage<{ session: { user: User } }> = ({ session }) => { return (
@@ -42,8 +40,10 @@ export default UserManagement; export const getServerSideProps = wrapper.getServerSideProps( (store) => async (context) => { const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { await store.dispatch(assignToken(session.accessToken)); + await store.dispatch(setUser(session.user)); return { props: { session } }; } diff --git a/clients/admin-ui/src/pages/user-management/new.tsx b/clients/admin-ui/src/pages/user-management/new.tsx index db6777d8b..9d9b75dd2 100644 --- a/clients/admin-ui/src/pages/user-management/new.tsx +++ b/clients/admin-ui/src/pages/user-management/new.tsx @@ -10,14 +10,12 @@ import { import NavBar from '../../features/common/NavBar'; import NewUserForm from '../../features/user-management/NewUserForm'; - -import { assignToken } from '../../features/user/user.slice'; +import { assignToken, setUser } from '../../features/user/user.slice'; +import { User } from '../../features/user/types'; import { getSession } from 'next-auth/react'; import { wrapper } from '../../app/store'; -const CreateNewUser: NextPage<{ session: { username: string } }> = ({ - session, -}) => ( +const CreateNewUser: NextPage<{ session: { user: User } }> = ({ session }) => (
@@ -49,8 +47,10 @@ export default CreateNewUser; export const getServerSideProps = wrapper.getServerSideProps( (store) => async (context) => { const session = await getSession(context); + if (session && typeof session.accessToken !== 'undefined') { await store.dispatch(assignToken(session.accessToken)); + await store.dispatch(setUser(session.user)); return { props: { session } }; } diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index 9159ba470..73a8e593d 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -7,14 +7,19 @@ import { BreadcrumbLink, Heading, } from '@fidesui/react'; + import NavBar from '../../../features/common/NavBar'; import EditUserForm from '../../../features/user-management/EditUserForm'; -import { userApi, assignToken } from '../../../features/user/user.slice'; - +import { + assignToken, + setUser, + userApi, +} from '../../../features/user/user.slice'; +import { User } from '../../../features/user/types'; import { wrapper } from '../../../app/store'; import { getSession } from 'next-auth/react'; -const Profile: NextPage = () => { +const Profile: NextPage<{ session: { user: User } }> = ({ session }) => { return (
@@ -50,6 +55,7 @@ export const getServerSideProps = wrapper.getServerSideProps( const session = await getSession(context); if (session && typeof session.accessToken !== 'undefined') { await store.dispatch(assignToken(session.accessToken)); + await store.dispatch(setUser(session.user)); if (context.query.id) { store.dispatch( From 29a2f76ac4b77f865d69fc849b28e256260e9374 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 13 May 2022 11:56:39 -0500 Subject: [PATCH 42/51] Some form cleanup --- .../admin-ui/src/features/user-management/EditUserForm.tsx | 1 - .../admin-ui/src/features/user-management/NewUserForm.tsx | 6 ++---- .../src/features/user-management/UserManagementTable.tsx | 1 - clients/admin-ui/src/pages/user-management/index.tsx | 6 ++++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 1e14543e9..abbe631ce 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -112,7 +112,6 @@ const UserForm: NextPage<{ const { dirty, errors, - existingScopes, existingUser, handleBlur, handleChange, diff --git a/clients/admin-ui/src/features/user-management/NewUserForm.tsx b/clients/admin-ui/src/features/user-management/NewUserForm.tsx index 4d8ee1057..5eadfab39 100644 --- a/clients/admin-ui/src/features/user-management/NewUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/NewUserForm.tsx @@ -22,7 +22,7 @@ import { useCreateUserMutation, useUpdateUserPermissionsMutation, } from '../user/user.slice'; -import { userPrivilegesArray, User, UserResponse } from '../user/types'; +import { userPrivilegesArray } from '../user/types'; import { useRouter } from 'next/router'; const useUserForm = () => { @@ -98,9 +98,7 @@ const useUserForm = () => { }; }; -const UserForm: NextPage<{ - existingUser?: User; -}> = () => { +const UserForm: NextPage = () => { const { dirty, errors, diff --git a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx index 6e33bcfab..4d0ef2751 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTable.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTable.tsx @@ -12,7 +12,6 @@ import { } from '@fidesui/react'; import UserManagementRow from './UserManagementRow'; - import { selectUserFilters, setPage, diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx index 8b7dcd416..9538c9f87 100644 --- a/clients/admin-ui/src/pages/user-management/index.tsx +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -11,7 +11,9 @@ import NavBar from '../../features/common/NavBar'; import UserManagementTable from '../../features/user-management/UserManagementTable'; import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; -const UserManagement: NextPage<{ session: { user: User } }> = ({ session }) => { +const UserManagement: NextPage<{ session: { username: string } }> = ({ + session, +}) => { return (
@@ -43,7 +45,7 @@ export const getServerSideProps = wrapper.getServerSideProps( if (session && typeof session.accessToken !== 'undefined') { await store.dispatch(assignToken(session.accessToken)); - await store.dispatch(setUser(session.user)); + // await store.dispatch(setUser(session.user)); return { props: { session } }; } From 828ca39e6efb51ead75a552abcf5426d00a330af Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 13 May 2022 11:58:41 -0500 Subject: [PATCH 43/51] Clean index page --- clients/admin-ui/src/pages/user-management/index.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clients/admin-ui/src/pages/user-management/index.tsx b/clients/admin-ui/src/pages/user-management/index.tsx index 9538c9f87..801afff24 100644 --- a/clients/admin-ui/src/pages/user-management/index.tsx +++ b/clients/admin-ui/src/pages/user-management/index.tsx @@ -11,9 +11,7 @@ import NavBar from '../../features/common/NavBar'; import UserManagementTable from '../../features/user-management/UserManagementTable'; import UserManagementTableActions from '../../features/user-management/UserManagementTableActions'; -const UserManagement: NextPage<{ session: { username: string } }> = ({ - session, -}) => { +const UserManagement: NextPage<{ session: { user: User } }> = ({ session }) => { return (
@@ -45,7 +43,6 @@ export const getServerSideProps = wrapper.getServerSideProps( if (session && typeof session.accessToken !== 'undefined') { await store.dispatch(assignToken(session.accessToken)); - // await store.dispatch(setUser(session.user)); return { props: { session } }; } From dd434ae2aed6ac01d290dfd3630c095ece514737 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Fri, 13 May 2022 12:26:16 -0500 Subject: [PATCH 44/51] Edit user permissions checks --- .../features/user-management/EditUserForm.tsx | 87 ++++++++++--------- .../pages/user-management/profile/[id].tsx | 3 +- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index abbe631ce..d4ad73fcf 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -103,16 +103,18 @@ const useUserForm = () => { ...formik, existingScopes, existingUser, + id, }; }; -const UserForm: NextPage<{ +const EditUserForm: NextPage<{ existingUser?: User; -}> = () => { +}> = (user) => { const { dirty, errors, existingUser, + id, handleBlur, handleChange, handleSubmit, @@ -122,6 +124,11 @@ const UserForm: NextPage<{ setFieldValue, } = useUserForm(); + const { data: loggedInUser, isLoading: loggedInUserLoading } = + useGetUserPermissionsQuery(user.user.id as string); + + const hasAdminPermission = loggedInUser?.scopes?.includes('user:update'); + return (
@@ -166,9 +173,8 @@ const UserForm: NextPage<{ onChange={handleChange} onBlur={handleBlur} value={values.first_name} - // Only admins can edit names - need to add a check for admin role here - // isReadOnly={existingUser && !adminUser ? true : false} - // isDisabled={existingUser ? && !adminUser ? true : false} + isReadOnly={!hasAdminPermission} + isDisabled={!hasAdminPermission} /> @@ -185,47 +191,42 @@ const UserForm: NextPage<{ onChange={handleChange} onBlur={handleBlur} value={values.last_name} - // Only admins can edit names - need to add a check for admin role here - // isReadOnly={existingUser && !adminUser ? true : false} - // isDisabled={existingUser && !adminUser ? true : false} + isReadOnly={!hasAdminPermission} + isDisabled={!hasAdminPermission} /> - {/* existing user and it's that user's specific profile */} - {/* {existingUser && !adminUser ? ( -
Change Password
- ) : ( */} - <> - - - Password - - + - {errors.password} - - - {/* )} */} + isInvalid={touched.password && Boolean(errors.password)} + > + + Password + + + {errors.password} + + + )} + @@ -294,4 +295,4 @@ const UserForm: NextPage<{ ); }; -export default UserForm; +export default EditUserForm; diff --git a/clients/admin-ui/src/pages/user-management/profile/[id].tsx b/clients/admin-ui/src/pages/user-management/profile/[id].tsx index 73a8e593d..8dd1d8d77 100644 --- a/clients/admin-ui/src/pages/user-management/profile/[id].tsx +++ b/clients/admin-ui/src/pages/user-management/profile/[id].tsx @@ -41,7 +41,8 @@ const Profile: NextPage<{ session: { user: User } }> = ({ session }) => { - + {/* @ts-ignore */} +
From 902cfa22bb0b923b36bbe61381ae28e96edca317 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Mon, 16 May 2022 08:20:08 -0500 Subject: [PATCH 45/51] Feedback --- .../src/features/user-management/UserManagementRow.tsx | 1 + .../user-management/UserManagementTableActions.tsx | 1 + clients/admin-ui/src/features/user/types.ts | 8 ++++++++ clients/admin-ui/src/pages/api/auth/[...nextauth].ts | 2 +- clients/admin-ui/src/pages/login.tsx | 4 ++-- fidesops.toml | 2 +- 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx index fcbba1fc6..7089822be 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementRow.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementRow.tsx @@ -59,6 +59,7 @@ const UserManagementRow: React.FC = ({ user }) => {
{user.created_at ? new Date(user.created_at).toUTCString() : null} diff --git a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx index daf8f5a2d..4307673c3 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx @@ -46,6 +46,7 @@ const UserManagementTableActions: React.FC = () => { onChange={handleSearchChange} /> + {/* hide this from users that can't create new users */} {user.created_at ? new Date(user.created_at).toUTCString() : null} diff --git a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx index 4307673c3..daf8f5a2d 100644 --- a/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx +++ b/clients/admin-ui/src/features/user-management/UserManagementTableActions.tsx @@ -46,7 +46,6 @@ const UserManagementTableActions: React.FC = () => { onChange={handleSearchChange} /> - {/* hide this from users that can't create new users */} + + + + + + ); +} + +export default UpdatePasswordModal; From eec59343c71fd4435c3989c572c1c0218e387e31 Mon Sep 17 00:00:00 2001 From: Laura Smith Date: Tue, 17 May 2022 12:06:19 -0500 Subject: [PATCH 50/51] Change password modal --- .../features/user-management/EditUserForm.tsx | 95 ++++++++----------- .../user-management/UpdatePasswordModal.tsx | 62 ++++++------ .../admin-ui/src/features/user/user.slice.ts | 2 +- 3 files changed, 75 insertions(+), 84 deletions(-) diff --git a/clients/admin-ui/src/features/user-management/EditUserForm.tsx b/clients/admin-ui/src/features/user-management/EditUserForm.tsx index 0e7d2dd6d..58fd12807 100644 --- a/clients/admin-ui/src/features/user-management/EditUserForm.tsx +++ b/clients/admin-ui/src/features/user-management/EditUserForm.tsx @@ -1,8 +1,9 @@ // @ts-nocheck -import React, { ChangeEvent, useEffect } from 'react'; +import React from 'react'; import { useSelector } from 'react-redux'; import type { NextPage } from 'next'; import NextLink from 'next/link'; +import { useRouter } from 'next/router'; import { useFormik } from 'formik'; import { Button, @@ -16,6 +17,7 @@ import { Input, Stack, Text, + useToast, } from '@fidesui/react'; import config from './config/config.json'; import { @@ -27,7 +29,7 @@ import { useGetUserPermissionsQuery, } from '../user/user.slice'; import { userPrivilegesArray, User } from '../user/types'; -import { useRouter } from 'next/router'; +import UpdatePasswordModal from './UpdatePasswordModal'; const useUserForm = () => { const token = useSelector(selectUserToken); @@ -41,6 +43,7 @@ const useUserForm = () => { const { data: existingUser } = useGetUserByIdQuery(id as string); const { data: existingScopes, isLoading: scopesLoading } = useGetUserPermissionsQuery(id as string); + const toast = useToast(); const formik = useFormik({ initialValues: { @@ -70,32 +73,39 @@ const useUserForm = () => { id: existingUser?.id, }; - await editUser(userBody) - .then((result) => { - const userWithPrivileges = { - id: 'data' in result ? result.data.id : null, - scopes: [...new Set(values.scopes, 'privacy-request:read')], - }; - return userWithPrivileges; - }) - .then((result) => { - updateUserPermissions(result); - return result; - }) - .then((result) => { - console.log('result', result); - const updatePasswordbody = { - id: result.id, - old_password: existingUser?.password, - new_password: values.password, - }; - console.log(updatePasswordbody); - updateUserPassword(updatePasswordbody); - return result; - }) - .then(() => { - router.replace('/user-management'); + const { error: editUserError, data } = await editUser(userBody); + + if (editUserError) { + toast({ + status: 'error', + description: editUserError.data.detail.length + ? `${editUserError.data.detail[0].msg}` + : 'An unexpected error occurred. Please try again.', + }); + return; + } + + if (editUserError && editUserError.status == 422) { + toast({ + status: 'error', + description: editUserError.data.detail.length + ? `${editUserError.data.detail[0].msg}` + : 'An unexpected error occurred. Please try again.', }); + } + + const userWithPrivileges = { + id: data ? data.id : null, + scopes: [new Set(...values.scopes, 'privacy-request:read')], + }; + + const { error: updatePermissionsError } = await updateUserPermissions( + userWithPrivileges as { id: string } + ); + + if (!updatePermissionsError) { + router.push('/user-management'); + } }, validate: (values) => { const errors: { @@ -162,6 +172,7 @@ const EditUserForm: NextPage<{ Username + @@ -70,7 +76,8 @@ function UpdatePasswordModal(user: User) { name="oldPassword" onChange={handleChange} placeholder="Old Password" - value={usernameValue} + type="password" + value={oldPasswordValue} /> @@ -79,7 +86,8 @@ function UpdatePasswordModal(user: User) { name="newPassword" onChange={handleChange} placeholder="New Password" - value={confirmValue} + type="password" + value={newPasswordValue} /> @@ -97,7 +105,7 @@ function UpdatePasswordModal(user: User) { Cancel