From 10333967f7896a937c0a7cf90bfb383db6b6a294 Mon Sep 17 00:00:00 2001 From: Issam Baccouch Date: Mon, 11 Dec 2023 17:23:17 +0100 Subject: [PATCH 001/183] feat: wip implement config prototyping --- .../widgets/ConfusionMatrixWidget.tsx | 2 +- .../src/components/shell/NavigationMenu.tsx | 42 +++++---- .../JeMPI_UI/src/pages/settings/Settings.tsx | 91 +++++++++++++++++++ JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx | 5 + 4 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx diff --git a/JeMPI_Apps/JeMPI_UI/src/components/dashboard/widgets/ConfusionMatrixWidget.tsx b/JeMPI_Apps/JeMPI_UI/src/components/dashboard/widgets/ConfusionMatrixWidget.tsx index 0d25e4131..a869ac9d0 100644 --- a/JeMPI_Apps/JeMPI_UI/src/components/dashboard/widgets/ConfusionMatrixWidget.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/components/dashboard/widgets/ConfusionMatrixWidget.tsx @@ -83,7 +83,7 @@ const ConfusionMatrix = () => { - Precesion + Precision { return null } - return config.useSso ? ( + return ( { 'aria-labelledby': 'basic-button' }} > - - - - {`${currentUser?.givenName} ${currentUser?.familyName}`} - - - {currentUser?.email} - - - - - - - LOGOUT + navigate('/settings')}> + + Settings + + {config.useSso && ( + <> + + + + {`${currentUser?.givenName} ${currentUser?.familyName}`} + + + {currentUser?.email} + + + + + + + LOGOUT + + + + )} - ) : ( - <> ) } diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx new file mode 100644 index 000000000..ec6900213 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -0,0 +1,91 @@ +import { Grid, Tab, Tabs, Typography } from '@mui/material' +import { Box } from '@mui/system' +import React, { SyntheticEvent, useState } from 'react' + +interface TabPanelProps { + children?: React.ReactNode + index: number + value: number +} + +function CustomTabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props + + return ( + + ) +} + +function a11yProps(index: number) { + return { + id: `simple-tab-${index}`, + 'aria-controls': `simple-tabpanel-${index}` + } +} +const Settings = () => { + const [value, setValue] = useState(0) + + const handleChange = (event: SyntheticEvent, newValue: number) => { + setValue(newValue) + } + + return ( + + + + + + + + + + + + + + + + + Common + + + Unique to Golden record + + + Unique to Interaction + + + Golden Records Lists + + + Deterministic + + + Blocking + + + Probabilistic + + + + + ) +} + +export default Settings diff --git a/JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx b/JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx index 45b9b2da2..c43c39ab2 100644 --- a/JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx @@ -11,6 +11,7 @@ import SimpleSearch from 'components/search/SimpleSearch' import SearchResult from 'components/searchResult/SearchResult' import Login from 'components/user/Login' import Dashboard from 'components/dashboard/Dashboard' +import Settings from 'pages/settings/Settings' const baseRouter = createBrowserRouter([ { path: 'login', element: }, @@ -26,6 +27,10 @@ const baseRouter = createBrowserRouter([ path: 'browse-records', element: }, + { + path: 'settings', + element: + }, { path: 'record-details/:uid', loader: async ({ params }) => params.uid, From 3bdb59ca6589e80ccb65a17c23de45a50d894d3b Mon Sep 17 00:00:00 2001 From: Issam Baccouch Date: Tue, 12 Dec 2023 19:38:26 +0100 Subject: [PATCH 002/183] chore: wip implement config common tab --- .../JeMPI_UI/src/pages/settings/Settings.tsx | 38 ++- .../src/pages/settings/common/Common.tsx | 269 ++++++++++++++++++ .../pages/settings/uniqueToGR/UniqueToGR.tsx | 226 +++++++++++++++ 3 files changed, 518 insertions(+), 15 deletions(-) create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/common/Common.tsx create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index ec6900213..d92a309b1 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -1,6 +1,8 @@ -import { Grid, Tab, Tabs, Typography } from '@mui/material' +import { Grid, Tab, Tabs, Typography, useMediaQuery } from '@mui/material' import { Box } from '@mui/system' import React, { SyntheticEvent, useState } from 'react' +import CommonSettings from './common/Common' +import UniqueToGR from './uniqueToGR/UniqueToGR' interface TabPanelProps { children?: React.ReactNode @@ -15,14 +17,12 @@ function CustomTabPanel(props: TabPanelProps) { ) @@ -30,27 +30,27 @@ function CustomTabPanel(props: TabPanelProps) { function a11yProps(index: number) { return { - id: `simple-tab-${index}`, - 'aria-controls': `simple-tabpanel-${index}` + id: `settings-tab-${index}`, + 'aria-controls': `settings-tabpanel-${index}` } } const Settings = () => { const [value, setValue] = useState(0) - const handleChange = (event: SyntheticEvent, newValue: number) => { setValue(newValue) } return ( - - - + + + @@ -62,10 +62,18 @@ const Settings = () => { - Common + <> + + Setup common properties + + + - Unique to Golden record + + Unique to Golden record + + Unique to Interaction diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/common/Common.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/common/Common.tsx new file mode 100644 index 000000000..20ff55d0f --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/common/Common.tsx @@ -0,0 +1,269 @@ +import React from 'react' +import Box from '@mui/material/Box' +import Button from '@mui/material/Button' +import AddIcon from '@mui/icons-material/Add' +import EditIcon from '@mui/icons-material/Edit' +import DeleteIcon from '@mui/icons-material/DeleteOutlined' +import SaveIcon from '@mui/icons-material/Save' +import CancelIcon from '@mui/icons-material/Close' +import { + GridRowsProp, + GridRowModesModel, + GridRowModes, + DataGrid, + GridColDef, + GridToolbarContainer, + GridActionsCellItem, + GridEventListener, + GridRowId, + GridRowModel, + GridRowEditStopReasons, + GridValueSetterParams +} from '@mui/x-data-grid' +import { useSnackbar } from 'notistack' + +const randomTraderName = () => { + return Math.random().toString(36).substring(2, 7) +} + +const randomId = () => { + return Math.random().toString(36).substring(2, 9) +} + +const initialRows: GridRowsProp = [ + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact, trigram', + m: 0.8, + u: 0.3 + }, + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact, trigram', + m: 0.8, + u: 0.3 + }, + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact, trigram', + m: 0.8, + u: 0.3 + } +] + +interface EditToolbarProps { + setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void + setRowModesModel: ( + newModel: (oldModel: GridRowModesModel) => GridRowModesModel + ) => void +} + +function EditToolbar(props: EditToolbarProps) { + const { setRows, setRowModesModel } = props + + const handleClick = () => { + const id = randomId() + setRows(oldRows => [...oldRows, { id, name: '', age: '', isNew: true }]) + setRowModesModel(oldModel => ({ + ...oldModel, + [id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' } + })) + } + + return +} + +const CommonSettings = () => { + const [rows, setRows] = React.useState(initialRows) + const { enqueueSnackbar } = useSnackbar() + const [rowModesModel, setRowModesModel] = React.useState( + {} + ) + + const handleRowEditStop: GridEventListener<'rowEditStop'> = ( + params, + event + ) => { + if (params.reason === GridRowEditStopReasons.rowFocusOut) { + event.defaultMuiPrevented = true + } + } + + const handleEditClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } }) + } + + const handleSaveClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }) + } + + const handleDeleteClick = (id: GridRowId) => () => { + setRows(rows.filter(row => row.id !== id)) + } + + const handleCancelClick = (id: GridRowId) => () => { + setRowModesModel({ + ...rowModesModel, + [id]: { mode: GridRowModes.View, ignoreModifications: true } + }) + + const editedRow = rows.find(row => row.id === id) + if (editedRow!.isNew) { + setRows(rows.filter(row => row.id !== id)) + } + } + + const processRowUpdate = (newRow: GridRowModel) => { + const updatedRow = { ...newRow, isNew: false } + setRows(rows.map(row => (row.id === newRow.id ? updatedRow : row))) + return updatedRow + } + + const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { + setRowModesModel(newRowModesModel) + } + + const columns: GridColDef[] = [ + { + field: 'name', + headerName: 'Name', + width: 180, + editable: true, + align: 'center', + headerAlign: 'center' + }, + { + field: 'type', + headerName: 'Type', + type: 'string', + width: 180, + align: 'center', + headerAlign: 'center', + editable: false + }, + { + field: 'index', + headerName: 'Index', + type: 'string', + width: 180, + align: 'center', + headerAlign: 'center', + + editable: true + }, + { + field: 'm', + headerName: 'Default M', + + width: 180, + editable: true, + type: 'number', + align: 'center', + headerAlign: 'center', + valueSetter: (params: GridValueSetterParams) => { + if (params.value == null || params.value < 0 || params.value > 1) { + enqueueSnackbar('Value must be between 0 and 1', { variant: 'error' }) + return false + } + return true + } + }, + { + field: 'u', + headerName: 'Default U', + width: 180, + editable: true, + type: 'number', + align: 'center', + headerAlign: 'center', + valueSetter: (params: GridValueSetterParams) => { + if (params.value == null || params.value < 0 || params.value > 1) { + enqueueSnackbar('Value must be between 0 and 1', { variant: 'error' }) + return false + } + return true + } + }, + { + field: 'actions', + type: 'actions', + headerName: 'Actions', + align: 'center', + headerAlign: 'center', + + width: 180, + cellClassName: 'actions', + getActions: ({ id }) => { + const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit + if (isInEditMode) { + return [ + } + label="Save" + sx={{ + color: 'white' + }} + onClick={handleSaveClick(id)} + />, + } + label="Cancel" + className="textPrimary" + onClick={handleCancelClick(id)} + color="inherit" + /> + ] + } + + return [ + } + label="Edit" + className="textPrimary" + onClick={handleEditClick(id)} + color="inherit" + /> + ] + } + } + ] + + return ( + + + + ) +} + +export default CommonSettings diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx new file mode 100644 index 000000000..ea4fbcab1 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx @@ -0,0 +1,226 @@ +import React from 'react' +import Box from '@mui/material/Box' +import Button from '@mui/material/Button' +import AddIcon from '@mui/icons-material/Add' +import EditIcon from '@mui/icons-material/Edit' +import DeleteIcon from '@mui/icons-material/DeleteOutlined' +import SaveIcon from '@mui/icons-material/Save' +import CancelIcon from '@mui/icons-material/Close' +import { + GridRowsProp, + GridRowModesModel, + GridRowModes, + DataGrid, + GridColDef, + GridToolbarContainer, + GridActionsCellItem, + GridEventListener, + GridRowId, + GridRowModel, + GridRowEditStopReasons, + GridValueSetterParams +} from '@mui/x-data-grid' +import { useSnackbar } from 'notistack' + +const randomTraderName = () => { + return Math.random().toString(36).substring(2, 7) +} + +const randomId = () => { + return Math.random().toString(36).substring(2, 9) +} + +const initialRows: GridRowsProp = [ + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact, trigram', + m: 0.8, + u: 0.3 + }, + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact, trigram', + m: 0.8, + u: 0.3 + }, + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact, trigram', + m: 0.8, + u: 0.3 + } +] + +interface EditToolbarProps { + setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void + setRowModesModel: ( + newModel: (oldModel: GridRowModesModel) => GridRowModesModel + ) => void +} + +function EditToolbar(props: EditToolbarProps) { + const { setRows, setRowModesModel } = props + + const handleClick = () => { + const id = randomId() + setRows(oldRows => [...oldRows, { id, name: '', age: '', isNew: true }]) + setRowModesModel(oldModel => ({ + ...oldModel, + [id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' } + })) + } + + return +} + +const UniqueToGR = () => { + const [rows, setRows] = React.useState(initialRows) + const { enqueueSnackbar } = useSnackbar() + const [rowModesModel, setRowModesModel] = React.useState( + {} + ) + + const handleRowEditStop: GridEventListener<'rowEditStop'> = ( + params, + event + ) => { + if (params.reason === GridRowEditStopReasons.rowFocusOut) { + event.defaultMuiPrevented = true + } + } + + const handleEditClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } }) + } + + const handleSaveClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }) + } + + const handleDeleteClick = (id: GridRowId) => () => { + setRows(rows.filter(row => row.id !== id)) + } + + const handleCancelClick = (id: GridRowId) => () => { + setRowModesModel({ + ...rowModesModel, + [id]: { mode: GridRowModes.View, ignoreModifications: true } + }) + + const editedRow = rows.find(row => row.id === id) + if (editedRow!.isNew) { + setRows(rows.filter(row => row.id !== id)) + } + } + + const processRowUpdate = (newRow: GridRowModel) => { + const updatedRow = { ...newRow, isNew: false } + setRows(rows.map(row => (row.id === newRow.id ? updatedRow : row))) + return updatedRow + } + + const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { + setRowModesModel(newRowModesModel) + } + + const columns: GridColDef[] = [ + { + field: 'name', + headerName: 'Name', + width: 250, + editable: true, + align: 'center', + headerAlign: 'center' + }, + { + field: 'type', + headerName: 'Type', + type: 'string', + width: 250, + align: 'center', + headerAlign: 'center', + editable: false + }, + { + field: 'actions', + type: 'actions', + headerName: 'Actions', + align: 'center', + headerAlign: 'center', + + width: 250, + cellClassName: 'actions', + getActions: ({ id }) => { + const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit + if (isInEditMode) { + return [ + } + label="Save" + sx={{ + color: 'white' + }} + onClick={handleSaveClick(id)} + />, + } + label="Cancel" + className="textPrimary" + onClick={handleCancelClick(id)} + color="inherit" + /> + ] + } + + return [ + } + label="Edit" + className="textPrimary" + onClick={handleEditClick(id)} + color="inherit" + /> + ] + } + } + ] + + return ( + + + + ) +} + +export default UniqueToGR From aa973e1dedab119e85d7bc6ddf5d87a79cb1a557 Mon Sep 17 00:00:00 2001 From: Issam Baccouch Date: Thu, 14 Dec 2023 20:02:46 +0100 Subject: [PATCH 003/183] chore: implement config unique interaction/gr tab --- .../JeMPI_UI/src/pages/settings/Settings.tsx | 6 +- .../pages/settings/uniqueToGR/UniqueToGR.tsx | 7 +- .../UniqueToInteraction.tsx | 231 ++++++++++++++++++ 3 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToInteraction/UniqueToInteraction.tsx diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index d92a309b1..765153605 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -3,6 +3,7 @@ import { Box } from '@mui/system' import React, { SyntheticEvent, useState } from 'react' import CommonSettings from './common/Common' import UniqueToGR from './uniqueToGR/UniqueToGR' +import UniqueToInteraction from './uniqueToInteraction/UniqueToInteraction' interface TabPanelProps { children?: React.ReactNode @@ -76,7 +77,10 @@ const Settings = () => { - Unique to Interaction + + Unique to Interaction + + Golden Records Lists diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx index ea4fbcab1..ad0ab4e83 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx @@ -133,7 +133,7 @@ const UniqueToGR = () => { { field: 'name', headerName: 'Name', - width: 250, + width: 300, editable: true, align: 'center', headerAlign: 'center' @@ -142,7 +142,7 @@ const UniqueToGR = () => { field: 'type', headerName: 'Type', type: 'string', - width: 250, + width: 300, align: 'center', headerAlign: 'center', editable: false @@ -153,8 +153,7 @@ const UniqueToGR = () => { headerName: 'Actions', align: 'center', headerAlign: 'center', - - width: 250, + width: 300, cellClassName: 'actions', getActions: ({ id }) => { const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToInteraction/UniqueToInteraction.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToInteraction/UniqueToInteraction.tsx new file mode 100644 index 000000000..ffa419049 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToInteraction/UniqueToInteraction.tsx @@ -0,0 +1,231 @@ +import React, { useState } from 'react' +import Box from '@mui/material/Box' +import Button from '@mui/material/Button' +import AddIcon from '@mui/icons-material/Add' +import EditIcon from '@mui/icons-material/Edit' +import DeleteIcon from '@mui/icons-material/DeleteOutlined' +import SaveIcon from '@mui/icons-material/Save' +import CancelIcon from '@mui/icons-material/Close' +import { + GridRowsProp, + GridRowModesModel, + GridRowModes, + DataGrid, + GridColDef, + GridToolbarContainer, + GridActionsCellItem, + GridEventListener, + GridRowId, + GridRowModel, + GridRowEditStopReasons, + GridValueSetterParams +} from '@mui/x-data-grid' +import { useSnackbar } from 'notistack' + +const randomTraderName = () => { + return Math.random().toString(36).substring(2, 7) +} + +const randomId = () => { + return Math.random().toString(36).substring(2, 9) +} + +const initialRows: GridRowsProp = [ + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact', + m: 0.8, + u: 0.3 + }, + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact, trigram', + m: 0.8, + u: 0.3 + }, + { + id: randomId(), + name: randomTraderName(), + type: 'string', + index: 'exact', + m: 0.8, + u: 0.3 + } +] + +interface EditToolbarProps { + setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void + setRowModesModel: ( + newModel: (oldModel: GridRowModesModel) => GridRowModesModel + ) => void +} + +function EditToolbar(props: EditToolbarProps) { + const { setRows, setRowModesModel } = props + + const handleClick = () => { + const id = randomId() + setRows(oldRows => [...oldRows, { id, name: '', age: '', isNew: true }]) + setRowModesModel(oldModel => ({ + ...oldModel, + [id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' } + })) + } + + return +} +const UniqueToInteraction = () => { + const [rows, setRows] = useState(initialRows) + const { enqueueSnackbar } = useSnackbar() + const [rowModesModel, setRowModesModel] = useState({}) + + const handleRowEditStop: GridEventListener<'rowEditStop'> = ( + params, + event + ) => { + if (params.reason === GridRowEditStopReasons.rowFocusOut) { + event.defaultMuiPrevented = true + } + } + + const handleEditClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } }) + } + + const handleSaveClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }) + } + + const handleDeleteClick = (id: GridRowId) => () => { + setRows(rows.filter(row => row.id !== id)) + } + + const handleCancelClick = (id: GridRowId) => () => { + setRowModesModel({ + ...rowModesModel, + [id]: { mode: GridRowModes.View, ignoreModifications: true } + }) + + const editedRow = rows.find(row => row.id === id) + if (editedRow!.isNew) { + setRows(rows.filter(row => row.id !== id)) + } + } + + const processRowUpdate = (newRow: GridRowModel) => { + const updatedRow = { ...newRow, isNew: false } + setRows(rows.map(row => (row.id === newRow.id ? updatedRow : row))) + return updatedRow + } + + const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { + setRowModesModel(newRowModesModel) + } + + const columns: GridColDef[] = [ + { + field: 'name', + headerName: 'Name', + width: 300, + editable: true, + align: 'center', + headerAlign: 'center' + }, + { + field: 'type', + headerName: 'Type', + type: 'string', + width: 300, + align: 'center', + headerAlign: 'center', + editable: false + }, + { + field: 'index', + headerName: 'Index', + type: 'string', + width: 300, + align: 'center', + headerAlign: 'center', + editable: false + }, + { + field: 'actions', + type: 'actions', + headerName: 'Actions', + align: 'center', + headerAlign: 'center', + width: 300, + cellClassName: 'actions', + getActions: ({ id }) => { + const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit + if (isInEditMode) { + return [ + } + label="Save" + sx={{ + color: 'white' + }} + onClick={handleSaveClick(id)} + />, + } + label="Cancel" + className="textPrimary" + onClick={handleCancelClick(id)} + color="inherit" + /> + ] + } + + return [ + } + label="Edit" + className="textPrimary" + onClick={handleEditClick(id)} + color="inherit" + /> + ] + } + } + ] + + return ( + + + + ) +} + +export default UniqueToInteraction From 4b54b4e30c14300f21b98e72cfd7f478563bb395 Mon Sep 17 00:00:00 2001 From: Issam Baccouch Date: Thu, 21 Dec 2023 14:48:38 +0100 Subject: [PATCH 004/183] chore: add deterministic tab --- .../JeMPI_UI/src/pages/settings/Settings.tsx | 2 + .../settings/deterministic/deterministic.tsx | 102 ++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index 765153605..859f74bef 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -4,6 +4,7 @@ import React, { SyntheticEvent, useState } from 'react' import CommonSettings from './common/Common' import UniqueToGR from './uniqueToGR/UniqueToGR' import UniqueToInteraction from './uniqueToInteraction/UniqueToInteraction' +import Deterministic from './deterministic/deterministic' interface TabPanelProps { children?: React.ReactNode @@ -87,6 +88,7 @@ const Settings = () => { Deterministic + Blocking diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx new file mode 100644 index 000000000..fbebcf2c3 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx @@ -0,0 +1,102 @@ +import { AddCircle, AddCircleOutline, AddOutlined } from '@mui/icons-material' +import { + Box, + Button, + Card, + CardActions, + CardContent, + Divider, + FormControl, + IconButton, + InputLabel, + MenuItem, + Select, + Typography +} from '@mui/material' +import React from 'react' + +const Deterministic = () => { + const [viewType, setViewType] = React.useState(0) + return ( + <> + + + + + + + {viewType === 0 ? ( + + + + Select Comparator Function + + + + + + Select Field + + + + + + Select Operator + + + + + ) : null} + + + + + + + + + ) +} + +export default Deterministic From 22522b5f93685f63b34bd43f566431f5fb0f2242 Mon Sep 17 00:00:00 2001 From: Issam Baccouch Date: Fri, 22 Dec 2023 13:52:03 +0100 Subject: [PATCH 005/183] chore: add blocking tab --- .../JeMPI_UI/src/pages/settings/Settings.tsx | 2 + .../src/pages/settings/blocking/Blocking.tsx | 119 ++++++++++++++++++ .../settings/deterministic/deterministic.tsx | 5 +- .../pages/settings/uniqueToGR/UniqueToGR.tsx | 5 - .../UniqueToInteraction.tsx | 8 +- 5 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/Blocking.tsx diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index 859f74bef..589c606a7 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -5,6 +5,7 @@ import CommonSettings from './common/Common' import UniqueToGR from './uniqueToGR/UniqueToGR' import UniqueToInteraction from './uniqueToInteraction/UniqueToInteraction' import Deterministic from './deterministic/deterministic' +import Blocking from './blocking/Blocking' interface TabPanelProps { children?: React.ReactNode @@ -92,6 +93,7 @@ const Settings = () => { Blocking + Probabilistic diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/Blocking.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/Blocking.tsx new file mode 100644 index 000000000..336917032 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/Blocking.tsx @@ -0,0 +1,119 @@ +import { AddOutlined } from '@mui/icons-material' +import { + Card, + CardContent, + Button, + FormControl, + InputLabel, + Select, + CardActions, + IconButton, + Typography +} from '@mui/material' +import { Box } from '@mui/system' +import React from 'react' + +const Blocking = () => { + const [viewType, setViewType] = React.useState(0) + return ( + <> + + + + + + + {viewType === 0 ? ( + + + + Select Comparator Function + + + + + + Select Field + + + + + + Select Operator + + + + + ) : ( + + Match (phone number, 3) + Or + Match (National ID, 3) Or + + Int (Match (given name , 3)) + Int(Match (family name, 3)) + + Int(Match (city, 3)) ≥ 3 + + + )} + + + + + + + + + ) +} + +export default Blocking diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx index fbebcf2c3..9eb0bdc99 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx @@ -1,17 +1,14 @@ -import { AddCircle, AddCircleOutline, AddOutlined } from '@mui/icons-material' +import { AddOutlined } from '@mui/icons-material' import { Box, Button, Card, CardActions, CardContent, - Divider, FormControl, IconButton, InputLabel, - MenuItem, Select, - Typography } from '@mui/material' import React from 'react' diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx index ad0ab4e83..956fd493a 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToGR/UniqueToGR.tsx @@ -1,9 +1,6 @@ import React from 'react' import Box from '@mui/material/Box' -import Button from '@mui/material/Button' -import AddIcon from '@mui/icons-material/Add' import EditIcon from '@mui/icons-material/Edit' -import DeleteIcon from '@mui/icons-material/DeleteOutlined' import SaveIcon from '@mui/icons-material/Save' import CancelIcon from '@mui/icons-material/Close' import { @@ -18,7 +15,6 @@ import { GridRowId, GridRowModel, GridRowEditStopReasons, - GridValueSetterParams } from '@mui/x-data-grid' import { useSnackbar } from 'notistack' @@ -81,7 +77,6 @@ function EditToolbar(props: EditToolbarProps) { const UniqueToGR = () => { const [rows, setRows] = React.useState(initialRows) - const { enqueueSnackbar } = useSnackbar() const [rowModesModel, setRowModesModel] = React.useState( {} ) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToInteraction/UniqueToInteraction.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToInteraction/UniqueToInteraction.tsx index ffa419049..988d8356f 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToInteraction/UniqueToInteraction.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/uniqueToInteraction/UniqueToInteraction.tsx @@ -1,9 +1,6 @@ -import React, { useState } from 'react' +import { useState } from 'react' import Box from '@mui/material/Box' -import Button from '@mui/material/Button' -import AddIcon from '@mui/icons-material/Add' import EditIcon from '@mui/icons-material/Edit' -import DeleteIcon from '@mui/icons-material/DeleteOutlined' import SaveIcon from '@mui/icons-material/Save' import CancelIcon from '@mui/icons-material/Close' import { @@ -18,9 +15,7 @@ import { GridRowId, GridRowModel, GridRowEditStopReasons, - GridValueSetterParams } from '@mui/x-data-grid' -import { useSnackbar } from 'notistack' const randomTraderName = () => { return Math.random().toString(36).substring(2, 7) @@ -80,7 +75,6 @@ function EditToolbar(props: EditToolbarProps) { } const UniqueToInteraction = () => { const [rows, setRows] = useState(initialRows) - const { enqueueSnackbar } = useSnackbar() const [rowModesModel, setRowModesModel] = useState({}) const handleRowEditStop: GridEventListener<'rowEditStop'> = ( From 16cc8d6eade4335a11bc82ec104834b71a4246f5 Mon Sep 17 00:00:00 2001 From: Issam Baccouch Date: Fri, 22 Dec 2023 13:55:37 +0100 Subject: [PATCH 006/183] chore: update deterministic tab --- .../settings/deterministic/deterministic.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx index 9eb0bdc99..95d18db42 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/deterministic.tsx @@ -9,6 +9,7 @@ import { IconButton, InputLabel, Select, + Typography } from '@mui/material' import React from 'react' @@ -84,7 +85,24 @@ const Deterministic = () => { > - ) : null} + ) : ( + + eq (National ID) + Or + + eq (given name) and eq(family name, 3) and eq (phone number) + + + )} From 7e7f6fbdfd013daa4aad04f03d88d906b12d3105 Mon Sep 17 00:00:00 2001 From: Issam Baccouch Date: Sun, 24 Dec 2023 00:05:20 +0100 Subject: [PATCH 007/183] feat: add golden record list --- .../JeMPI_UI/src/pages/settings/Settings.tsx | 6 +- .../goldenRecordLists/GoldenRecordLists.tsx | 216 ++++++++++++++++++ 2 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/goldenRecordLists/GoldenRecordLists.tsx diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index 589c606a7..72a69433a 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -6,6 +6,7 @@ import UniqueToGR from './uniqueToGR/UniqueToGR' import UniqueToInteraction from './uniqueToInteraction/UniqueToInteraction' import Deterministic from './deterministic/deterministic' import Blocking from './blocking/Blocking' +import GoldenRecordLists from './goldenRecordLists/GoldenRecordLists' interface TabPanelProps { children?: React.ReactNode @@ -85,7 +86,10 @@ const Settings = () => { - Golden Records Lists + + Setup properties for Golden Records Lists{' '} + + Deterministic diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/goldenRecordLists/GoldenRecordLists.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/goldenRecordLists/GoldenRecordLists.tsx new file mode 100644 index 000000000..b8192c018 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/goldenRecordLists/GoldenRecordLists.tsx @@ -0,0 +1,216 @@ +import React from 'react' +import Box from '@mui/material/Box' +import EditIcon from '@mui/icons-material/Edit' +import SaveIcon from '@mui/icons-material/Save' +import CancelIcon from '@mui/icons-material/Close' +import { + GridRowsProp, + GridRowModesModel, + GridRowModes, + DataGrid, + GridColDef, + GridToolbarContainer, + GridActionsCellItem, + GridEventListener, + GridRowId, + GridRowModel, + GridRowEditStopReasons +} from '@mui/x-data-grid' +import { useSnackbar } from 'notistack' + +const randomTraderName = () => { + return Math.random().toString(36).substring(2, 7) +} + +const randomId = () => { + return Math.random().toString(36).substring(2, 9) +} + +const initialRows: GridRowsProp = [ + { + id: randomId(), + listName: 'Source ID' , + propertyName: 'Facility ID', + type: 'string' + }, + { + id: randomId(), + listName: '', + propertyName: 'Patient ID', + type: 'string' + }, + { + id: randomId(), + listName: 'Biometric ID', + propertyName: 'Subject ID', + type: 'string' + } +] + +interface EditToolbarProps { + setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void + setRowModesModel: ( + newModel: (oldModel: GridRowModesModel) => GridRowModesModel + ) => void +} + +function EditToolbar(props: EditToolbarProps) { + const { setRows, setRowModesModel } = props + + const handleClick = () => { + const id = randomId() + setRows(oldRows => [...oldRows, { id, name: '', age: '', isNew: true }]) + setRowModesModel(oldModel => ({ + ...oldModel, + [id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' } + })) + } + + return +} + +const GoldenRecordLists = () => { + const [rows, setRows] = React.useState(initialRows) + const [rowModesModel, setRowModesModel] = React.useState( + {} + ) + const handleRowEditStop: GridEventListener<'rowEditStop'> = ( + params, + event + ) => { + if (params.reason === GridRowEditStopReasons.rowFocusOut) { + event.defaultMuiPrevented = true + } + } + const handleEditClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } }) + } + const handleSaveClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }) + } + const handleDeleteClick = (id: GridRowId) => () => { + setRows(rows.filter(row => row.id !== id)) + } + const handleCancelClick = (id: GridRowId) => () => { + setRowModesModel({ + ...rowModesModel, + [id]: { mode: GridRowModes.View, ignoreModifications: true } + }) + + const editedRow = rows.find(row => row.id === id) + if (editedRow!.isNew) { + setRows(rows.filter(row => row.id !== id)) + } + } + const processRowUpdate = (newRow: GridRowModel) => { + const updatedRow = { ...newRow, isNew: false } + setRows(rows.map(row => (row.id === newRow.id ? updatedRow : row))) + return updatedRow + } + const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { + setRowModesModel(newRowModesModel) + } + const columns: GridColDef[] = [ + { + field: 'listName', + headerName: 'List Name', + width: 300, + editable: true, + align: 'center', + headerAlign: 'center' + }, + { + field: 'propertyName', + headerName: 'Property Name', + type: 'string', + width: 300, + align: 'center', + headerAlign: 'center', + editable: false + }, + { + field: 'type', + headerName: 'Type', + type: 'string', + width: 300, + align: 'center', + headerAlign: 'center', + editable: false + }, + { + field: 'actions', + type: 'actions', + headerName: 'Actions', + align: 'center', + headerAlign: 'center', + width: 300, + cellClassName: 'actions', + getActions: ({ id }) => { + const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit + if (isInEditMode) { + return [ + } + label="Save" + sx={{ + color: 'white' + }} + onClick={handleSaveClick(id)} + />, + } + label="Cancel" + className="textPrimary" + onClick={handleCancelClick(id)} + color="inherit" + /> + ] + } + + return [ + } + label="Edit" + className="textPrimary" + onClick={handleEditClick(id)} + color="inherit" + /> + ] + } + } + ] + + + return ( + + + + ) +} + +export default GoldenRecordLists From 804cff431bb842b3b0af66092137ab140e2af732 Mon Sep 17 00:00:00 2001 From: Issam Baccouch Date: Fri, 29 Dec 2023 21:21:41 +0100 Subject: [PATCH 008/183] fix: navigation menu not showing up --- JeMPI_Apps/JeMPI_UI/src/components/shell/NavigationMenu.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/components/shell/NavigationMenu.tsx b/JeMPI_Apps/JeMPI_UI/src/components/shell/NavigationMenu.tsx index 6d4e08f03..d7958bf5a 100644 --- a/JeMPI_Apps/JeMPI_UI/src/components/shell/NavigationMenu.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/components/shell/NavigationMenu.tsx @@ -24,11 +24,6 @@ const NavigationMenu: React.FC = () => { close() logout(navigate) } - - if (!currentUser) { - return null - } - return ( Date: Fri, 29 Dec 2023 22:15:52 +0100 Subject: [PATCH 009/183] feat: add settings diagram --- .../JeMPI_UI/src/pages/settings/Settings.tsx | 43 ++++++++++++++++++- .../JeMPI_UI/src/pages/settings/Shapes.css | 43 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/Shapes.css diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index 72a69433a..478e0b305 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -7,6 +7,7 @@ import UniqueToInteraction from './uniqueToInteraction/UniqueToInteraction' import Deterministic from './deterministic/deterministic' import Blocking from './blocking/Blocking' import GoldenRecordLists from './goldenRecordLists/GoldenRecordLists' +import './Shapes.css' interface TabPanelProps { children?: React.ReactNode @@ -46,7 +47,47 @@ const Settings = () => { return ( - + +
+
+
+ Golden Record +
+
+
+ + Interaction +
+ (encounter) +
+
+
+
+
+
+ +
+
+
+
+
diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Shapes.css b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Shapes.css new file mode 100644 index 000000000..814227d45 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Shapes.css @@ -0,0 +1,43 @@ +.shapes-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 0px; +} + +.square { + width: 150px; + height: 50px; + border: 1px dashed #000 ; + border-radius: 5px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + padding: 0 5px; +} +.circle { + width: 100px; + height: 100px; + border: 2px dashed #000; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.connection-dashed { + height: 200px; + border: 1px dashed #000; +} +.connection-solid { + height: 150px; + border: 1px solid #000; + laft: 0; +} +.label { + position: absolute; + font-size: 12px; + color: #000; +} From 76f02d2639eb94d6b330baaab381fb107939f203 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Thu, 18 Apr 2024 13:57:57 +0200 Subject: [PATCH 010/183] added drag and drop functionality to widget nodes --- JeMPI_Apps/JeMPI_UI/package.json | 1 + .../JeMPI_UI/src/pages/settings/Settings.tsx | 14 ++--- .../JeMPI_UI/src/pages/settings/Shapes.css | 59 ++++++++++++++++++- .../GoldenRecordInteractiveNode.tsx | 47 +++++++++++++++ .../InteractionEncounterInteractiveNode .tsx | 37 ++++++++++++ 5 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/interactiveNode/GoldenRecordInteractiveNode.tsx create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/interactiveNode/InteractionEncounterInteractiveNode .tsx diff --git a/JeMPI_Apps/JeMPI_UI/package.json b/JeMPI_Apps/JeMPI_UI/package.json index 79c06b421..c9c55723c 100644 --- a/JeMPI_Apps/JeMPI_UI/package.json +++ b/JeMPI_Apps/JeMPI_UI/package.json @@ -45,6 +45,7 @@ "notistack": "^2.0.8", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-draggable": "^4.4.6", "react-dropzone": "^14.2.3", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index 478e0b305..879d073c3 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -6,6 +6,8 @@ import UniqueToGR from './uniqueToGR/UniqueToGR' import UniqueToInteraction from './uniqueToInteraction/UniqueToInteraction' import Deterministic from './deterministic/deterministic' import Blocking from './blocking/Blocking' +import GoldenRecordInteractiveNode from './interactiveNode/GoldenRecordInteractiveNode' +import InteractionEncounterInteractiveNode from '../settings/interactiveNode/InteractionEncounterInteractiveNode ' import GoldenRecordLists from './goldenRecordLists/GoldenRecordLists' import './Shapes.css' @@ -61,17 +63,9 @@ const Settings = () => { }} >
-
- Golden Record -
+
-
- - Interaction -
- (encounter) -
-
+
{ + + const [currentRotate, setCurrentRotate] = useState(0); + + const isDraggingRef = useRef(false); + + const onDrag = () => { + isDraggingRef.current = true; + }; + + const onStop = () => { + if (!isDraggingRef.current) { + setCurrentRotate(currentRotate + 90); + } + isDraggingRef.current = false; + }; + + return ( + +
+
+ Golden Record +
+

Common Properties

+
    +
  • Family Name: Smith
  • +
  • Name: John
  • +
  • City: New York
  • +
  • Age: 30
  • +
  • Phone Number: 123-456-7890
  • +
  • National ID: 123456789
  • +
+
+
+
+
+ ); + }; + +export default GoldenRecordInteractiveNode; diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/interactiveNode/InteractionEncounterInteractiveNode .tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/interactiveNode/InteractionEncounterInteractiveNode .tsx new file mode 100644 index 000000000..fbbddac9c --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/interactiveNode/InteractionEncounterInteractiveNode .tsx @@ -0,0 +1,37 @@ +import Draggable from "react-draggable"; +import React, { useState, useRef } from "react"; +import '../Shapes.css'; + +const InteractionEncounterInteractiveNode = () => { + + const [currentRotate, setCurrentRotate] = useState(0); + + const isDraggingRef = useRef(false); + + const onDrag = () => { + isDraggingRef.current = true; + }; + + const onStop = () => { + if (!isDraggingRef.current) { + setCurrentRotate(currentRotate + 90); + } + isDraggingRef.current = false; + }; + + return ( + +
+
+ Interaction +
+
+
+ ); + }; + +export default InteractionEncounterInteractiveNode ; + From 514e7e481fabd5db7dc168a384b5ffa377c09bf5 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Tue, 23 Apr 2024 14:54:55 +0200 Subject: [PATCH 011/183] added leader line dependency, refactored interactive nodes --- JeMPI_Apps/JeMPI_UI/package.json | 9 +- JeMPI_Apps/JeMPI_UI/public/index.html | 3 + JeMPI_Apps/JeMPI_UI/src/App.tsx | 1 - .../JeMPI_UI/src/pages/settings/Settings.tsx | 22 +-- .../JeMPI_UI/src/pages/settings/Shapes.css | 27 +--- .../GoldenRecordInteractiveNode.tsx | 47 ------ .../InteractionEncounterInteractiveNode .tsx | 37 ----- .../interactiveNode/InteractiveNode.tsx | 135 ++++++++++++++++++ JeMPI_Apps/JeMPI_UI/tsconfig.json | 1 + 9 files changed, 156 insertions(+), 126 deletions(-) delete mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/interactiveNode/GoldenRecordInteractiveNode.tsx delete mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/interactiveNode/InteractionEncounterInteractiveNode .tsx create mode 100644 JeMPI_Apps/JeMPI_UI/src/pages/settings/interactiveNode/InteractiveNode.tsx diff --git a/JeMPI_Apps/JeMPI_UI/package.json b/JeMPI_Apps/JeMPI_UI/package.json index c9c55723c..77986db99 100644 --- a/JeMPI_Apps/JeMPI_UI/package.json +++ b/JeMPI_Apps/JeMPI_UI/package.json @@ -11,6 +11,7 @@ "lint": "eslint src/**/*.{js,jsx,ts,tsx}", "lint:fix": "eslint ./src/ --ext ts,js,tsx,jsx --fix", "format": "prettier 'src/**/*.{js,jsx,ts,tsx,json,css}' --write", + "leader-line": "./node_modules/leader-line/leader-line.min.js", "type-check": "tsc", "mock:startJeMPIAPIServer": "npx ts-node --compilerOptions '{\"module\":\"commonjs\"}' ./tests/test.utils/mocks/enviroments/MockJeMPI_API/MockJeMPI_API.ts", "mock:startKeycloakServer": "npx ts-node --compilerOptions '{\"module\":\"commonjs\"}' ./tests/test.utils/mocks/enviroments/MockKeyCloak/MockKeyCloak.ts", @@ -42,11 +43,14 @@ "dayjs": "^1.11.8", "formik": "^2.2.9", "keycloak-js": "^20.0.2", + "leader-line": "^1.0.7", + "leader-line-types": "^1.0.5", "notistack": "^2.0.8", "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.6", "react-dropzone": "^14.2.3", + "react-leader-line": "^1.0.5", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", "typescript": "^4.4.2", @@ -75,7 +79,10 @@ "lint-staged": "^13.1.1", "prettier": "^2.8.4", "process": "^0.11.10", - "ts-jest": "^29.1.1" + "skeleton-loader": "^2.0.0", + "ts-jest": "^29.1.1", + "webpack": "^5.91.0", + "webpack-cli": "^5.1.4" }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ diff --git a/JeMPI_Apps/JeMPI_UI/public/index.html b/JeMPI_Apps/JeMPI_UI/public/index.html index 7d3564925..9fe42e59f 100644 --- a/JeMPI_Apps/JeMPI_UI/public/index.html +++ b/JeMPI_Apps/JeMPI_UI/public/index.html @@ -29,6 +29,9 @@
+ - - - - - - - - - - - - - - - - - diff --git a/JeMPI_Apps/JeMPI_API/src/main/java/org/jembi/jempi/api/API.java b/JeMPI_Apps/JeMPI_API/src/main/java/org/jembi/jempi/api/API.java index a5d4ccb58..867225c39 100644 --- a/JeMPI_Apps/JeMPI_API/src/main/java/org/jembi/jempi/api/API.java +++ b/JeMPI_Apps/JeMPI_API/src/main/java/org/jembi/jempi/api/API.java @@ -9,15 +9,12 @@ import org.apache.logging.log4j.Logger; import org.jembi.jempi.AppConfig; import org.jembi.jempi.libapi.BackEnd; -import org.jembi.jempi.libapi.JsonFieldsConfig; import java.util.UUID; public final class API { private static final Logger LOGGER = LogManager.getLogger(API.class); - private static final String CONFIG_RESOURCE_FILE_NAME = "config-api.json"; - private final JsonFieldsConfig jsonFieldsConfig = new JsonFieldsConfig(CONFIG_RESOURCE_FILE_NAME); private HttpServer httpServer; private API() { @@ -51,7 +48,7 @@ public Behavior create() { AppConfig.API_FIELDS_CONFIG_FILENAME), "BackEnd"); context.watch(backEnd); httpServer = HttpServer.create(); - httpServer.open("0.0.0.0", AppConfig.API_HTTP_PORT, context.getSystem(), backEnd, jsonFieldsConfig.jsonFields); + httpServer.open("0.0.0.0", AppConfig.API_HTTP_PORT, context.getSystem(), backEnd); return Behaviors.receive(Void.class).onSignal(Terminated.class, sig -> { httpServer.close(context.getSystem()); return Behaviors.stopped(); @@ -62,9 +59,6 @@ public Behavior create() { private void run() { LOGGER.info("interface:port {}:{}", "0.0.0.0", AppConfig.API_HTTP_PORT); try { - LOGGER.info("Loading fields configuration file "); - jsonFieldsConfig.load(CONFIG_RESOURCE_FILE_NAME); - LOGGER.info("Fields configuration file successfully loaded"); ActorSystem.create(this.create(), "API-App"); } catch (Exception e) { LOGGER.error("Unable to start the API", e); diff --git a/JeMPI_Apps/JeMPI_API/src/main/java/org/jembi/jempi/api/HttpServer.java b/JeMPI_Apps/JeMPI_API/src/main/java/org/jembi/jempi/api/HttpServer.java index f7ff538a1..7c43271ac 100644 --- a/JeMPI_Apps/JeMPI_API/src/main/java/org/jembi/jempi/api/HttpServer.java +++ b/JeMPI_Apps/JeMPI_API/src/main/java/org/jembi/jempi/api/HttpServer.java @@ -4,11 +4,7 @@ import akka.actor.typed.ActorSystem; import akka.http.javadsl.Http; import akka.http.javadsl.ServerBinding; -import akka.http.javadsl.model.HttpEntity; -import akka.http.javadsl.model.StatusCodes; import akka.http.javadsl.server.AllDirectives; -import akka.http.javadsl.server.ExceptionHandler; -import akka.http.javadsl.server.RejectionHandler; import akka.http.javadsl.server.Route; import ch.megard.akka.http.cors.javadsl.settings.CorsSettings; import org.apache.logging.log4j.LogManager; @@ -17,7 +13,6 @@ import org.jembi.jempi.AppConfig; import org.jembi.jempi.libapi.BackEnd; import org.jembi.jempi.libapi.Routes; -import org.jembi.jempi.shared.models.GlobalConstants; import java.util.concurrent.CompletionStage; @@ -43,10 +38,9 @@ public void open( final String httpServerHost, final int httpPort, final ActorSystem actorSystem, - final ActorRef backEnd, - final String jsonFields) { + final ActorRef backEnd) { http = Http.get(actorSystem); - binding = http.newServerAt(httpServerHost, httpPort).bind(this.createCorsRoutes(actorSystem, backEnd, jsonFields)); + binding = http.newServerAt(httpServerHost, httpPort).bind(this.createCorsRoutes(actorSystem, backEnd)); LOGGER.info("Server online at http://{}:{}", httpServerHost, httpPort); } @@ -57,37 +51,18 @@ public void close(final ActorSystem actorSystem) { public Route createCorsRoutes( final ActorSystem actorSystem, - final ActorRef backEnd, - final String jsonFields) { + final ActorRef backEnd) { final var settings = CorsSettings.create(AppConfig.CONFIG); - final RejectionHandler rejectionHandler = RejectionHandler.defaultHandler().mapRejectionResponse(response -> { - if (response.entity() instanceof HttpEntity.Strict) { - String message = ((HttpEntity.Strict) response.entity()).getData().utf8String(); - LOGGER.warn("Request was rejected. Reason: %s".formatted(message)); - } - - return response; - }); - - final ExceptionHandler exceptionHandler = ExceptionHandler.newBuilder().match(Exception.class, x -> { - LOGGER.error("An exception occurred while executing the Route", x); - return complete(StatusCodes.INTERNAL_SERVER_ERROR, "An exception occurred, see server logs for details"); - }).build(); - return cors(settings, () -> pathPrefix("JeMPI", () -> concat(Routes.createCoreAPIRoutes(actorSystem, backEnd, - jsonFields, AppConfig.LINKER_IP, AppConfig.LINKER_HTTP_PORT, AppConfig.CONTROLLER_IP, AppConfig.CONTROLLER_HTTP_PORT, - http), - path(GlobalConstants.SEGMENT_POST_FIELDS_CONFIG, - () -> complete(StatusCodes.OK, jsonFields))))).seal(rejectionHandler, - exceptionHandler); + http)))); } } diff --git a/JeMPI_Apps/JeMPI_API_KC/pom.xml b/JeMPI_Apps/JeMPI_API_KC/pom.xml index 4003e82fb..a7a75de91 100644 --- a/JeMPI_Apps/JeMPI_API_KC/pom.xml +++ b/JeMPI_Apps/JeMPI_API_KC/pom.xml @@ -163,29 +163,6 @@ test - - com.googlecode.json-simple - json-simple - - - - - - - - - - - - - - - - - - - - diff --git a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/APIKC.java b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/APIKC.java index 96ebe1a56..d4bbd85a2 100644 --- a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/APIKC.java +++ b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/APIKC.java @@ -8,15 +8,12 @@ import org.jembi.jempi.AppConfig; import org.jembi.jempi.api.httpServer.HttpServer; import org.jembi.jempi.libapi.BackEnd; -import org.jembi.jempi.libapi.JsonFieldsConfig; import java.util.UUID; public final class APIKC { private static final Logger LOGGER = LogManager.getLogger(APIKC.class); - private static final String CONFIG_RESOURCE_FILE_NAME = "config-api.json"; - private final JsonFieldsConfig jsonFieldsConfig = new JsonFieldsConfig(CONFIG_RESOURCE_FILE_NAME); private HttpServer httpServer; private APIKC() { @@ -54,8 +51,7 @@ public Behavior create() { final DispatcherSelector selector = DispatcherSelector.fromConfig("akka.actor.default-dispatcher"); final MessageDispatcher dispatcher = (MessageDispatcher) system.dispatchers().lookup(selector); httpServer = new HttpServer(dispatcher); - httpServer.open("0.0.0.0", AppConfig.API_KC_HTTP_PORT, context.getSystem(), backEnd, - jsonFieldsConfig.jsonFields); + httpServer.open("0.0.0.0", AppConfig.API_KC_HTTP_PORT, context.getSystem(), backEnd); return Behaviors.receive(Void.class).onSignal(Terminated.class, sig -> { LOGGER.info("API Server Terminated. Reason {}", sig); httpServer.close(context.getSystem()); @@ -67,9 +63,6 @@ public Behavior create() { private void run() { LOGGER.info("interface:port {}:{}", "0.0.0.0", AppConfig.API_KC_HTTP_PORT); try { - LOGGER.info("Loading fields configuration file "); - jsonFieldsConfig.load(CONFIG_RESOURCE_FILE_NAME); - LOGGER.info("Fields configuration file successfully loaded"); ActorSystem.create(this.create(), "API-App"); } catch (Exception e) { LOGGER.error("Unable to start the API", e); diff --git a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/HttpServer.java b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/HttpServer.java index 0a97eeeb3..1ca66ff3d 100644 --- a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/HttpServer.java +++ b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/HttpServer.java @@ -27,10 +27,8 @@ public final class HttpServer extends HttpSessionAwareDirectives { private static final Logger LOGGER = LogManager.getLogger(HttpServer.class); private CompletionStage binding = null; - private ActorSystem actorSystem; private ActorRef backEnd; - private String jsonFields; private Http akkaHttpServer = null; public HttpServer(final MessageDispatcher dispatcher) { @@ -46,12 +44,10 @@ public void open( final String httpServerHost, final int httpPort, final ActorSystem actorSystem, - final ActorRef backEnd, - final String jsonFields) { + final ActorRef backEnd) { this.actorSystem = actorSystem; this.backEnd = backEnd; - this.jsonFields = jsonFields; Configurator.setLevel(this.getClass(), AppConfig.GET_LOG_LEVEL); akkaHttpServer = Http.get(actorSystem); @@ -67,10 +63,6 @@ public Http getAkkaHttpServer() { return akkaHttpServer; } - public String getJsonFields() { - return jsonFields; - } - public ActorRef getBackEnd() { return backEnd; } diff --git a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/httpServerRoutes/RoutesEntries.java b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/httpServerRoutes/RoutesEntries.java index 929259c27..d2c1987cc 100644 --- a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/httpServerRoutes/RoutesEntries.java +++ b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/httpServerRoutes/RoutesEntries.java @@ -19,7 +19,6 @@ public Route getRouteEntries() { return concat(new UserRoutes(this.httpServer).getRouteEntries(), requireSession(Routes.createCoreAPIRoutes(this.httpServer.getActorSystem(), this.httpServer.getBackEnd(), - this.httpServer.getJsonFields(), AppConfig.LINKER_IP, AppConfig.LINKER_HTTP_PORT, AppConfig.CONTROLLER_IP, diff --git a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/httpServerRoutes/routes/UserRoutes.java b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/httpServerRoutes/routes/UserRoutes.java index 71532042e..829939f51 100644 --- a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/httpServerRoutes/routes/UserRoutes.java +++ b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/httpServer/httpServerRoutes/routes/UserRoutes.java @@ -81,11 +81,7 @@ private Route routeLogout() { @Override public Route getRouteEntries() { return concat(post(() -> path(GlobalConstants.SEGMENT_VALIDATE_OAUTH, () -> routeLoginWithKeycloakRequest(checkHeader))), - get(() -> concat(path(GlobalConstants.SEGMENT_POST_FIELDS_CONFIG, - () -> httpServer.setNewCsrfToken(checkHeader, - () -> complete(StatusCodes.OK, - httpServer.getJsonFields()))), - path(GlobalConstants.SEGMENT_CURRENT_USER, this::routeCurrentUser), + get(() -> concat(path(GlobalConstants.SEGMENT_CURRENT_USER, this::routeCurrentUser), path(GlobalConstants.SEGMENT_LOGOUT, this::routeLogout))) ); diff --git a/JeMPI_Apps/JeMPI_LibAPI/pom.xml b/JeMPI_Apps/JeMPI_LibAPI/pom.xml index e500f3385..95ed2b13b 100644 --- a/JeMPI_Apps/JeMPI_LibAPI/pom.xml +++ b/JeMPI_Apps/JeMPI_LibAPI/pom.xml @@ -145,35 +145,11 @@ test - - com.googlecode.json-simple - json-simple - commons-io commons-io - - diff --git a/JeMPI_Apps/JeMPI_LibAPI/src/main/java/org/jembi/jempi/libapi/JsonFieldsConfig.java b/JeMPI_Apps/JeMPI_LibAPI/src/main/java/org/jembi/jempi/libapi/JsonFieldsConfig.java deleted file mode 100644 index 17948272d..000000000 --- a/JeMPI_Apps/JeMPI_LibAPI/src/main/java/org/jembi/jempi/libapi/JsonFieldsConfig.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.jembi.jempi.libapi; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jembi.jempi.shared.utils.AppUtils; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; - -import java.io.*; -import java.nio.file.FileSystems; - -public final class JsonFieldsConfig { - - private static final Logger LOGGER = LogManager.getLogger(JsonFieldsConfig.class); - public String jsonFields; - private JSONArray fields; - - public JsonFieldsConfig(final String resourceFilename) { - try { - load(resourceFilename); - } catch (Exception e) { - LOGGER.debug(e); - } - } - - - private JSONArray buildFieldsResponsePayload( - final JSONArray systemFields, - final JSONArray customFields) { - JSONArray result = new JSONArray(); - // Process system fields - for (Object systemField : systemFields) { - JSONObject field = (JSONObject) systemField; - // Mark field as readonly - field.put("readOnly", true); - // Merge array values - result.add(field); - } - // Process custom fields - for (Object customField : customFields) { - // Convert field names from snake case to camel case - JSONObject field = (JSONObject) customField; - String fieldName = (String) field.get("fieldName"); - field.put("fieldName", AppUtils.snakeToCamelCase(fieldName)); - // Remove extra attributes - field.remove("indexGoldenRecord"); - field.remove("indexPatient"); - field.remove("m"); - field.remove("u"); - // Mark field as editable - field.put("readOnly", false); - // Merge array values - result.add(field); - } - return result; - } - - private InputStream getFileStreamFromResource(final String resourceFilename) { - ClassLoader classLoader = getClass().getClassLoader(); - return classLoader.getResourceAsStream(resourceFilename); - } - - public void load(final String filename) { - final var separator = FileSystems.getDefault().getSeparator(); - final var filePath = "%sapp%sconf_system%s%s".formatted(separator, separator, separator, filename); - final var file = new File(filePath); - try (Reader reader = new InputStreamReader(new FileInputStream(file))) { - JSONParser jsonParser = new JSONParser(); - Object obj = jsonParser.parse(reader); - JSONObject config = (JSONObject) obj; - // System fields are fields that exists regardless of the implementation - JSONArray systemFields = (JSONArray) config.get("systemFields"); - // Custom fields depend on the needs of the implementation - JSONArray customFields = (JSONArray) config.get("fields"); - jsonFields = buildFieldsResponsePayload(systemFields, customFields).toJSONString(); - } catch (IOException | ParseException e) { - throw new RuntimeException(e); - } - -// -// JSONParser jsonParser = new JSONParser(); -// try (Reader reader = new InputStreamReader(getFileStreamFromResource(resourceFilename))) { -// // Read JSON file -// Object obj = jsonParser.parse(reader); -// JSONObject config = (JSONObject) obj; -// // System fields are fields that exists regardless of the implementation -// JSONArray systemFields = (JSONArray) config.get("systemFields"); -// // Custom fields depend on the needs of the implementation -// JSONArray customFields = (JSONArray) config.get("fields"); -// jsonFields = buildFieldsResponsePayload(systemFields, customFields).toJSONString(); -// } catch (ParseException | IOException e) { -// LOGGER.error(e.getLocalizedMessage(), e); -// fields = new JSONArray(); -// } - } -} diff --git a/JeMPI_Apps/JeMPI_LibAPI/src/main/java/org/jembi/jempi/libapi/Routes.java b/JeMPI_Apps/JeMPI_LibAPI/src/main/java/org/jembi/jempi/libapi/Routes.java index 644a6e98c..a79dc65e8 100644 --- a/JeMPI_Apps/JeMPI_LibAPI/src/main/java/org/jembi/jempi/libapi/Routes.java +++ b/JeMPI_Apps/JeMPI_LibAPI/src/main/java/org/jembi/jempi/libapi/Routes.java @@ -578,7 +578,6 @@ private static Route postConfiguration( public static Route createCoreAPIRoutes( final ActorSystem actorSystem, final ActorRef backEnd, - final String jsonFields, final String linkerIP, final Integer linkerPort, final String controllerIP, @@ -652,8 +651,6 @@ public static Route createCoreAPIRoutes( () -> Routes.postExpandedInteractionsUsingCSV(actorSystem, backEnd)), path(GlobalConstants.SEGMENT_POST_GOLDEN_RECORD_AUDIT_TRAIL, () -> Routes.getGoldenRecordAuditTrail(actorSystem, backEnd)), - path(GlobalConstants.SEGMENT_POST_FIELDS_CONFIG, - () -> complete(StatusCodes.OK, jsonFields)), path(GlobalConstants.SEGMENT_POST_UPLOAD_CSV_FILE, () -> Routes.postUploadCsvFile(actorSystem, backEnd)), path(GlobalConstants.SEGMENT_POST_CONFIGURATION, diff --git a/JeMPI_Apps/JeMPI_LibShared/src/main/java/org/jembi/jempi/shared/models/GlobalConstants.java b/JeMPI_Apps/JeMPI_LibShared/src/main/java/org/jembi/jempi/shared/models/GlobalConstants.java index 96e4e0060..6844cffc3 100644 --- a/JeMPI_Apps/JeMPI_LibShared/src/main/java/org/jembi/jempi/shared/models/GlobalConstants.java +++ b/JeMPI_Apps/JeMPI_LibShared/src/main/java/org/jembi/jempi/shared/models/GlobalConstants.java @@ -11,7 +11,6 @@ public final class GlobalConstants { public static final String TOPIC_MU_LINKER = "JeMPI-mu-linker"; public static final String TOPIC_AUDIT_TRAIL = "JeMPI-audit-trail"; public static final String TOPIC_NOTIFICATIONS = "JeMPI-notifications"; - public static final String PSQL_TABLE_AUDIT_TRAIL = "audit_trail"; /* @@ -34,8 +33,6 @@ public final class GlobalConstants { public static final String SEGMENT_POST_EXPANDED_INTERACTIONS_FOR_INTERACTION_IDS = "expandedInteractionsCsv"; public static final String SEGMENT_POST_GOLDEN_RECORD_AUDIT_TRAIL = "goldenRecordAuditTrail"; public static final String SEGMENT_POST_INTERACTION_AUDIT_TRAIL = "interactionAuditTrail"; - public static final String SEGMENT_POST_FIELDS_CONFIG = "config"; - public static final String SEGMENT_POST_LINKED_RECORDS = "linkedRecords"; public static final String SEGMENT_POST_NOTIFICATIONS = "notifications"; public static final String SEGMENT_POST_GOLDEN_RECORD = "updateGoldenRecordFieldsForId"; public static final String SEGMENT_POST_RELINK = "relink"; @@ -59,8 +56,6 @@ public final class GlobalConstants { public static final String SEGMENT_PROXY_POST_DASHBOARD_DATA = "dashboardData"; public static final String SEGMENT_PROXY_ON_NOTIFICATION_RESOLUTION = "onNotificationResolution"; public static final String SEGMENT_PROXY_POST_CR_LINK = "crLink"; - // public static final String SEGMENT_PROXY_POST_LINK_INTERACTION_TO_GID = - // "linkInteractionToGid"; public static final String SEGMENT_VALIDATE_OAUTH = "authenticate"; public static final String SEGMENT_LOGOUT = "logout"; public static final String SEGMENT_CURRENT_USER = "currentUser"; diff --git a/JeMPI_Apps/pom.xml b/JeMPI_Apps/pom.xml index 5c70ae04a..51ce38927 100644 --- a/JeMPI_Apps/pom.xml +++ b/JeMPI_Apps/pom.xml @@ -294,19 +294,6 @@ 42.6.2 - - - - - - - - com.googlecode.json-simple - json-simple - 1.1.1 - - - From 24090840ee47ef7d637651974661a09f36a579f1 Mon Sep 17 00:00:00 2001 From: MatthewErispe Date: Fri, 24 May 2024 12:26:28 +0200 Subject: [PATCH 089/183] Update JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts b/JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts index 26d079aef..efbb195ae 100644 --- a/JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts +++ b/JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts @@ -146,7 +146,7 @@ export function processIndex(index: string) { } export const transformFieldName = (input: Params | string): string => { - const fieldName = typeof input === 'string' ? input : input?.row?.fieldName || ''; + const fieldName = typeof input === 'string' ? input : input?.row?.fieldName || 'Unknown Field'; return fieldName .replace(/_/g, ' ') .replace(/\b\w/g, (char: string) => char.toUpperCase()) From e7c2d9ed2817ad7b73e8ff73388ff7975e6cc6e6 Mon Sep 17 00:00:00 2001 From: MatthewErispe Date: Fri, 24 May 2024 12:29:24 +0200 Subject: [PATCH 090/183] Update JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../pages/settings/deterministic/Deterministic.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx index f6566c619..e0d981040 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx @@ -47,15 +47,13 @@ const Deterministic = ({ demographicData = [], linkingRules }: DeterministicProp const savedOperator = localStorage.getItem('selectedOperator'); const savedRules = localStorage.getItem('rules'); - if (savedComparator) setSelectedComparator(Number(savedComparator)); - if (savedFields) setSelectedFields(JSON.parse(savedFields)); - if (savedOperator) setSelectedOperator(savedOperator as Operator); - if (savedRules) { - const parsedRules = JSON.parse(savedRules); + if (savedComparator || savedFields || savedOperator || savedRules) { + const parsedRules = savedRules ? JSON.parse(savedRules) : []; setRules(parsedRules); + setSelectedComparator(savedComparator ? Number(savedComparator) : selectedComparator); + setSelectedFields(savedFields ? JSON.parse(savedFields) : selectedFields); + setSelectedOperator(savedOperator ? savedOperator as Operator : ''); setIsOperatorDisabled(parsedRules.length === 0); - } else { - setIsOperatorDisabled(true); } console.log('Loaded rules from storage:', savedRules ? JSON.parse(savedRules) : []); From 7fc1efe5d11993c7df1741eae9bcb353af92cf42 Mon Sep 17 00:00:00 2001 From: Matthew Erispe Date: Fri, 24 May 2024 12:57:48 +0200 Subject: [PATCH 091/183] code clean up --- .../settings/deterministic/Deterministic.tsx | 180 ++++++++++-------- JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts | 12 +- 2 files changed, 102 insertions(+), 90 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx index e0d981040..97e85cc47 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx @@ -10,13 +10,13 @@ import { Select, SelectChangeEvent, Typography -} from '@mui/material'; -import { useEffect, useState } from 'react'; -import { DemographicField, Configuration } from 'types/Configuration'; -import { transformFieldName } from 'utils/helpers'; +} from '@mui/material' +import { useEffect, useState } from 'react' +import { DemographicField, Configuration } from 'types/Configuration' +import { transformFieldName } from 'utils/helpers' interface DeterministicProps { - demographicData: DemographicField[]; + demographicData: DemographicField[] linkingRules: Configuration['rules']['link'] } @@ -25,80 +25,90 @@ const options = [ { value: 1, label: 'Low Fuzziness' }, { value: 2, label: 'Medium Fuzziness' }, { value: 3, label: 'High Fuzziness' } -]; +] enum Operator { AND = 'And', OR = 'Or' } -const Deterministic = ({ demographicData = [], linkingRules }: DeterministicProps) => { - const [viewType, setViewType] = useState(0); - const [selectedComparator, setSelectedComparator] = useState(0); - const [selectedFields, setSelectedFields] = useState([]); - const [selectedOperator, setSelectedOperator] = useState(''); - const [rules, setRules] = useState([]); - const [isOperatorDisabled, setIsOperatorDisabled] = useState(true); +const Deterministic = ({ + demographicData = [], + linkingRules +}: DeterministicProps) => { + const [viewType, setViewType] = useState(0) + const [selectedComparator, setSelectedComparator] = useState(0) + const [selectedFields, setSelectedFields] = useState([]) + const [selectedOperator, setSelectedOperator] = useState('') + const [rules, setRules] = useState([]) + const [isOperatorDisabled, setIsOperatorDisabled] = useState(true) const deterministicRules = linkingRules.deterministic || {} useEffect(() => { - const savedComparator = localStorage.getItem('selectedComparator'); - const savedFields = localStorage.getItem('selectedFields'); - const savedOperator = localStorage.getItem('selectedOperator'); - const savedRules = localStorage.getItem('rules'); + const savedComparator = localStorage.getItem('selectedComparator') + const savedFields = localStorage.getItem('selectedFields') + const savedOperator = localStorage.getItem('selectedOperator') + const savedRules = localStorage.getItem('rules') if (savedComparator || savedFields || savedOperator || savedRules) { - const parsedRules = savedRules ? JSON.parse(savedRules) : []; - setRules(parsedRules); - setSelectedComparator(savedComparator ? Number(savedComparator) : selectedComparator); - setSelectedFields(savedFields ? JSON.parse(savedFields) : selectedFields); - setSelectedOperator(savedOperator ? savedOperator as Operator : ''); - setIsOperatorDisabled(parsedRules.length === 0); + const parsedRules = savedRules ? JSON.parse(savedRules) : [] + setRules(parsedRules) + setSelectedComparator( + savedComparator ? Number(savedComparator) : selectedComparator + ) + setSelectedFields(savedFields ? JSON.parse(savedFields) : selectedFields) + setSelectedOperator(savedOperator ? (savedOperator as Operator) : '') + setIsOperatorDisabled(parsedRules.length === 0) } - - console.log('Loaded rules from storage:', savedRules ? JSON.parse(savedRules) : []); - }, []); + }, [selectedComparator, selectedFields, selectedOperator, rules]) useEffect(() => { - localStorage.setItem('selectedComparator', selectedComparator.toString()); - }, [selectedComparator]); + localStorage.setItem('selectedComparator', selectedComparator.toString()) + }, [selectedComparator]) useEffect(() => { - localStorage.setItem('selectedFields', JSON.stringify(selectedFields)); - }, [selectedFields]); + localStorage.setItem('selectedFields', JSON.stringify(selectedFields)) + }, [selectedFields]) useEffect(() => { - localStorage.setItem('selectedOperator', selectedOperator); - }, [selectedOperator]); + localStorage.setItem('selectedOperator', selectedOperator) + }, [selectedOperator]) useEffect(() => { - localStorage.setItem('rules', JSON.stringify(rules)); - setIsOperatorDisabled(rules.length === 0); - console.log('Current rules:', rules); - }, [rules]); - - const handleComparatorChange = (event: SelectChangeEvent) => { - setSelectedComparator(event.target.value as number); - }; - - const handleFieldChange = (event: SelectChangeEvent) => { - setSelectedFields(event.target.value as string[]); - }; - - const handleOperatorChange = (event: SelectChangeEvent) => { - setSelectedOperator(event.target.value as Operator); - }; + localStorage.setItem('rules', JSON.stringify(rules)) + setIsOperatorDisabled(rules.length === 0) + }, [rules]) + + const handleComparatorChange = ( + event: SelectChangeEvent + ) => { + setSelectedComparator(event.target.value as number) + } + + const handleFieldChange = ( + event: SelectChangeEvent + ) => { + setSelectedFields(event.target.value as string[]) + } + + const handleOperatorChange = ( + event: SelectChangeEvent + ) => { + setSelectedOperator(event.target.value as Operator) + } const handleAddRule = () => { - const formattedFields = selectedFields.map(field => `eq(${field})`); + const formattedFields = selectedFields.map(field => `eq(${field})`) const newRule = { vars: selectedFields, - text: formattedFields.join(selectedOperator === Operator.AND ? ' and ' : ' or ') - }; - setRules([...rules, newRule]); - setSelectedFields([]); - setSelectedOperator(''); - }; + text: formattedFields.join( + selectedOperator === Operator.AND ? ' and ' : ' or ' + ) + } + setRules([...rules, newRule]) + setSelectedFields([]) + setSelectedOperator('') + } return ( @@ -164,15 +174,18 @@ const Deterministic = ({ demographicData = [], linkingRules }: DeterministicProp onChange={handleFieldChange} multiple > - {Array.isArray(demographicData) && demographicData.map((field, index) => ( - - {transformFieldName(field.fieldName)} - - ))} + {Array.isArray(demographicData) && + demographicData.map((field, index) => ( + + {transformFieldName(field.fieldName)} + + ))} - Select Operator + + Select Operator + - {options.map(option => ( - - {option.label} - - ))} - - - - Select Field - - - - - Select Operator - - - - - ) : ( - - {Object.keys(deterministicRules).map((key, index) => ( - - {`Rule ${index + 1}: ${deterministicRules[key].text}`} - - ))} + + + + + + + - )} + + + + + + + + + + - - - ) } diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx new file mode 100644 index 000000000..7b3a74f4a --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -0,0 +1,233 @@ +import { + Box, + Button, + FormControl, + InputLabel, + MenuItem, + Select, + SelectChangeEvent, + Typography + } from '@mui/material' + import { useState, useEffect } from 'react' + import { DemographicField, Configuration } from 'types/Configuration' + import { transformFieldName } from 'utils/helpers' +import SourceView from './SourceView' + + interface DeterministicContentProps { + demographicData: DemographicField[] + linkingRules: Configuration['rules']['link'] + } + + const options = [ + { value: 0, label: 'Exact' }, + { value: 1, label: 'Low Fuzziness' }, + { value: 2, label: 'Medium Fuzziness' }, + { value: 3, label: 'High Fuzziness' } + ] + + enum Operator { + AND = 'And', + OR = 'Or' + } + + const DeterministicContent = ({ + demographicData = [], + linkingRules + }: DeterministicContentProps) => { + const [viewType, setViewType] = useState(0) + const [selectedComparator, setSelectedComparator] = useState(0) + const [selectedFields, setSelectedFields] = useState([]) + const [selectedOperator, setSelectedOperator] = useState('') + const [rules, setRules] = useState([]) + const [isOperatorDisabled, setIsOperatorDisabled] = useState(true) + + const deterministicRules = linkingRules.deterministic || {} + + useEffect(() => { + const savedComparator = localStorage.getItem('selectedComparator') + const savedFields = localStorage.getItem('selectedFields') + const savedOperator = localStorage.getItem('selectedOperator') + const savedRules = localStorage.getItem('rules') + + if (savedComparator || savedFields || savedOperator || savedRules) { + const parsedRules = savedRules ? JSON.parse(savedRules) : [] + setRules(parsedRules) + setSelectedComparator( + savedComparator ? Number(savedComparator) : selectedComparator + ) + setSelectedFields(savedFields ? JSON.parse(savedFields) : selectedFields) + setSelectedOperator(savedOperator ? (savedOperator as Operator) : '') + setIsOperatorDisabled(parsedRules.length === 0) + } + }, []) + + useEffect(() => { + localStorage.setItem('selectedComparator', selectedComparator.toString()) + }, [selectedComparator]) + + useEffect(() => { + localStorage.setItem('selectedFields', JSON.stringify(selectedFields)) + }, [selectedFields]) + + useEffect(() => { + localStorage.setItem('selectedOperator', selectedOperator) + }, [selectedOperator]) + + useEffect(() => { + localStorage.setItem('rules', JSON.stringify(rules)) + setIsOperatorDisabled(rules.length === 0) + }, [rules]) + + const handleComparatorChange = ( + event: SelectChangeEvent + ) => { + setSelectedComparator(event.target.value as number) + } + + const handleFieldChange = ( + event: SelectChangeEvent + ) => { + setSelectedFields(event.target.value as string[]) + } + + const handleOperatorChange = ( + event: SelectChangeEvent + ) => { + setSelectedOperator(event.target.value as Operator) + } + + const handleAddRule = () => { + const formattedFields = selectedFields.map(field => `eq(${field})`) + const newRule = { + vars: selectedFields, + text: formattedFields.join( + selectedOperator === Operator.AND ? ' and ' : ' or ' + ) + } + setRules([...rules, newRule]) + setSelectedFields([]) + setSelectedOperator('') + } + + return ( + + + + + + + {viewType != 0 ? ( + + + + Select Comparator Function + + + + + Select Field + + + + + Select Operator + + + + + ) : ( + + {/* {Object.keys(deterministicRules).map((key, index) => ( + + {`Rule ${index + 1}: ${deterministicRules[key].text}`} + + ))} */} + + + )} + + + ) + } + + export default DeterministicContent + \ No newline at end of file diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx new file mode 100644 index 000000000..af34e22cd --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx @@ -0,0 +1,144 @@ +import React, { useState, useEffect } from 'react'; +import Box from '@mui/material/Box'; +import { DataGrid, GridColDef, GridEventListener, GridRowEditStopReasons, GridRowId, GridRowModel, GridRowModes, GridRowModesModel, GridActionsCellItem } from '@mui/x-data-grid'; +import EditIcon from '@mui/icons-material/Edit'; +import SaveIcon from '@mui/icons-material/Save'; +import CancelIcon from '@mui/icons-material/Close'; + +interface RowData { + id: string; + ruleNumber: number; + ruleText: string; +} + +interface SourceViewProps { + data: RowData[]; +} + +const SourceView: React.FC = ({ data }) => { + const [rows, setRows] = useState([]); + const [rowModesModel, setRowModesModel] = useState({}); + + useEffect(() => { + setRows(data); + }, [data]); + + const handleEditClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } }); + }; + + const handleSaveClick = (id: GridRowId) => () => { + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }); + }; + + const handleCancelClick = (id: GridRowId) => () => { + setRowModesModel({ + ...rowModesModel, + [id]: { mode: GridRowModes.View, ignoreModifications: true } + }); + }; + + const processRowUpdate = (newRow: GridRowModel) => { + const { id, ...updatedRow } = newRow; + setRows(rows.map(row => (row.id === id ? updatedRow as RowData : row))); + return updatedRow as RowData; + }; + + const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { + setRowModesModel(newRowModesModel); + }; + + const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { + if (params.reason === GridRowEditStopReasons.rowFocusOut) { + event.defaultMuiPrevented = true; + } + }; + + const columns: GridColDef[] = [ + { + field: 'ruleNumber', + headerName: 'Rule Number', + width: 150, + align: 'center', + headerAlign: 'center', + editable: false, + }, + { + field: 'ruleText', + headerName: 'Rule', + width: 500, + align: 'left', + headerAlign: 'left', + editable: true, + }, + { + field: 'actions', + type: 'actions', + headerName: 'Actions', + width: 150, + align: 'center', + headerAlign: 'center', + cellClassName: 'actions', + getActions: ({ id }) => { + const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; + if (isInEditMode) { + return [ + } + id="save-button" + label="Save" + sx={{ color: 'white' }} + onClick={handleSaveClick(id)} + />, + } + id="cancel-button" + label="Cancel" + className="textPrimary" + onClick={handleCancelClick(id)} + color="inherit" + /> + ]; + } + + return [ + } + id="edit-button" + label="Edit" + className="textPrimary" + onClick={handleEditClick(id)} + color="inherit" + /> + ]; + } + } + ]; + + return ( + + + + ); +}; + +export default SourceView; From 9b4c02cfcf4ea75c08a3e7ea4bbfb3676904cdc1 Mon Sep 17 00:00:00 2001 From: MatthewErispe Date: Tue, 28 May 2024 13:44:54 +0200 Subject: [PATCH 097/183] Update JeMPI_Apps/JeMPI_UI/tests/services/configuration.test.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- JeMPI_Apps/JeMPI_UI/tests/services/configuration.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/JeMPI_Apps/JeMPI_UI/tests/services/configuration.test.ts b/JeMPI_Apps/JeMPI_UI/tests/services/configuration.test.ts index d625eabea..4963a8eae 100644 --- a/JeMPI_Apps/JeMPI_UI/tests/services/configuration.test.ts +++ b/JeMPI_Apps/JeMPI_UI/tests/services/configuration.test.ts @@ -24,6 +24,7 @@ test('fetchConfiguration should return configuration data from the server', asyn console.log('result') // Then expect(mockAxios.post).toHaveBeenCalledWith(ROUTES.GET_CONFIGURATION) + mockAxios.get.mockResolvedValue(mockResponse) expect(result).toEqual(configuration) }) From 50f0d4b3cc3f4d4f708b76b0efd767e59cec4eee Mon Sep 17 00:00:00 2001 From: Matthew Erispe Date: Tue, 28 May 2024 14:54:45 +0200 Subject: [PATCH 098/183] update configuration mock data --- JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts | 4 ++- JeMPI_Apps/JeMPI_UI/src/services/mockData.ts | 25 +++++++++++++------ .../test/settings/GoldenRecordLists.test.tsx | 2 +- .../src/test/settings/UniqueToGR.test.tsx | 2 +- .../settings/UniqueToInteraction.test.tsx | 2 +- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts b/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts index 709f02945..8c26150d7 100644 --- a/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts +++ b/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts @@ -89,7 +89,9 @@ export class ApiClient { } async fetchConfiguration() { - const { data } = await this.client.get(ROUTES.GET_CONFIGURATION) + const { data } = await this.client.get( + ROUTES.GET_CONFIGURATION + ) return data } diff --git a/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts b/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts index 41fc55eb6..ba98adce0 100644 --- a/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts +++ b/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts @@ -332,9 +332,8 @@ const currentUser: User = { provider: 'keycloak' } - const configuration = { - uniqueInteractionFields: [ + auxInteractionFields: [ { fieldName: 'aux_date_created', fieldType: 'DateTime' @@ -342,15 +341,19 @@ const configuration = { { fieldName: 'aux_id', fieldType: 'String', - csvCol: 0 + source: { + csvCol: 0 + } }, { fieldName: 'aux_clinical_data', fieldType: 'String', - csvCol: 10 + source: { + csvCol: 10 + } } ], - uniqueGoldenRecordFields: [ + auxGoldenRecordFields: [ { fieldName: 'aux_date_created', fieldType: 'DateTime' @@ -363,7 +366,9 @@ const configuration = { { fieldName: 'aux_id', fieldType: 'String', - source: 'aux_id' + source: { + interactionField: 'aux_id' + } } ], additionalNodes: [ @@ -373,12 +378,16 @@ const configuration = { { fieldName: 'facility', fieldType: 'String', - csvCol: 8 + source: { + csvCol: 8 + } }, { fieldName: 'patient', fieldType: 'String', - csvCol: 9 + source: { + csvCol: 9 + } } ] } diff --git a/JeMPI_Apps/JeMPI_UI/src/test/settings/GoldenRecordLists.test.tsx b/JeMPI_Apps/JeMPI_UI/src/test/settings/GoldenRecordLists.test.tsx index 9ac531044..eb6b4e998 100644 --- a/JeMPI_Apps/JeMPI_UI/src/test/settings/GoldenRecordLists.test.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/test/settings/GoldenRecordLists.test.tsx @@ -7,7 +7,7 @@ import GoldenRecordLists from 'pages/settings/goldenRecordLists/GoldenRecordList describe('GoldenRecordLists', () => { - const goldenRecordListsWithIds = mockData.configuration.uniqueGoldenRecordFields.map((row, index) => ({ + const goldenRecordListsWithIds = mockData.configuration.auxGoldenRecordFields.map((row, index) => ({ ...row, id: `row_${index}`, })); diff --git a/JeMPI_Apps/JeMPI_UI/src/test/settings/UniqueToGR.test.tsx b/JeMPI_Apps/JeMPI_UI/src/test/settings/UniqueToGR.test.tsx index 22aa64dd7..d97e5ebbe 100644 --- a/JeMPI_Apps/JeMPI_UI/src/test/settings/UniqueToGR.test.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/test/settings/UniqueToGR.test.tsx @@ -7,7 +7,7 @@ import UniqueToGR from 'pages/settings/uniqueToGR/UniqueToGR'; describe('UniqueToGR', () => { - const uniqueGoldenRecordFieldsWithIds = mockData.configuration.uniqueGoldenRecordFields.map((row, index) => ({ + const uniqueGoldenRecordFieldsWithIds = mockData.configuration.auxGoldenRecordFields.map((row, index) => ({ ...row, id: `row_${index}`, })); diff --git a/JeMPI_Apps/JeMPI_UI/src/test/settings/UniqueToInteraction.test.tsx b/JeMPI_Apps/JeMPI_UI/src/test/settings/UniqueToInteraction.test.tsx index 40e5db4ce..a747debb6 100644 --- a/JeMPI_Apps/JeMPI_UI/src/test/settings/UniqueToInteraction.test.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/test/settings/UniqueToInteraction.test.tsx @@ -6,7 +6,7 @@ import UniqueToInteraction from 'pages/settings/uniqueToInteraction/UniqueToInte describe('UniqueToInteraction', () => { - const uniqueGoldenRecordFieldsWithIds = mockData.configuration.uniqueGoldenRecordFields.map((row, index) => ({ + const uniqueGoldenRecordFieldsWithIds = mockData.configuration.auxGoldenRecordFields.map((row, index) => ({ ...row, id: `row_${index}`, })); From 03b8084d5a31a23e1f0c878633833f5c7c6d721d Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Tue, 28 May 2024 18:19:44 +0200 Subject: [PATCH 099/183] CU-86byyaner - Source view should be in a table format ie Rule 1 | Rule Content (no headers) --- .../src/pages/settings/deterministic/SourceView.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx index af34e22cd..833fe3237 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx @@ -5,7 +5,7 @@ import EditIcon from '@mui/icons-material/Edit'; import SaveIcon from '@mui/icons-material/Save'; import CancelIcon from '@mui/icons-material/Close'; -interface RowData { +export interface RowData { id: string; ruleNumber: number; ruleText: string; @@ -125,7 +125,10 @@ const SourceView: React.FC = ({ data }) => { }, '& .textPrimary': { color: '#fff' - } + }, + '& .MuiDataGrid-columnHeaders': { + display: 'none', + }, }} > = ({ data }) => { onRowModesModelChange={handleRowModesModelChange} onRowEditStop={handleRowEditStop} processRowUpdate={processRowUpdate} + hideFooter + disableColumnSelector /> ); From 978004814f17373922527c6d58acf8c068a25fd0 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Tue, 28 May 2024 18:23:32 +0200 Subject: [PATCH 100/183] CU-86byyamnw - Subtabs for Linking, Validating and Matching --- .../settings/deterministic/Deterministic.tsx | 4 +- .../deterministic/DeterministicContent.tsx | 55 ++++++++++--------- .../JeMPI_UI/src/types/Configuration.ts | 2 +- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx index ba8858f86..cc48fc93e 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx @@ -38,8 +38,8 @@ const Deterministic = ({ - - + + diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx index 7b3a74f4a..1f04b2025 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -9,9 +9,9 @@ import { Typography } from '@mui/material' import { useState, useEffect } from 'react' - import { DemographicField, Configuration } from 'types/Configuration' + import { DemographicField, Configuration, DeterministicRule } from 'types/Configuration' import { transformFieldName } from 'utils/helpers' -import SourceView from './SourceView' +import SourceView, { RowData } from './SourceView' interface DeterministicContentProps { demographicData: DemographicField[] @@ -29,6 +29,13 @@ import SourceView from './SourceView' AND = 'And', OR = 'Or' } + const transformRulesToRowData = (rules: DeterministicRule): RowData[] => { + return Object.keys(rules).map((key, index) => ({ + id: key, + ruleNumber: index + 1, + ruleText: rules[key].text + })); + }; const DeterministicContent = ({ demographicData = [], @@ -41,7 +48,7 @@ import SourceView from './SourceView' const [rules, setRules] = useState([]) const [isOperatorDisabled, setIsOperatorDisabled] = useState(true) - const deterministicRules = linkingRules.deterministic || {} + const deterministicRules = transformRulesToRowData(linkingRules.deterministic); useEffect(() => { const savedComparator = localStorage.getItem('selectedComparator') @@ -116,19 +123,35 @@ import SourceView from './SourceView' - {viewType != 0 ? ( + {viewType === 0 ? ( + + + + ) + : + ( - ) : ( - - {/* {Object.keys(deterministicRules).map((key, index) => ( - - {`Rule ${index + 1}: ${deterministicRules[key].text}`} - - ))} */} - - - )} + ) } - - - {viewType === 0 ? ( - - - - ) - : - ( - - - - Select Comparator Function - - - - - Select Field - - - - - Select Operator - - - - - ) } + setRules([...rules, newRule]) + setSelectedFields([]) + setSelectedOperator('') + } + + return ( + + + + - ) - } - - export default DeterministicContent - \ No newline at end of file + {viewType === 0 ? ( + + + + ) + : + ( + + + + Select Comparator Function + + + + + Select Field + + + + + Select Operator + + + + + ) } + + + ) +} + +export default DeterministicContent \ No newline at end of file diff --git a/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts b/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts index 8c26150d7..23c8bdcf4 100644 --- a/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts +++ b/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts @@ -89,7 +89,7 @@ export class ApiClient { } async fetchConfiguration() { - const { data } = await this.client.get( + const { data } = await moxios.get( ROUTES.GET_CONFIGURATION ) return data diff --git a/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts b/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts index ba98adce0..55fd2ff87 100644 --- a/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts +++ b/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts @@ -498,24 +498,30 @@ const configuration = { link: { deterministic: [ { - vars: ['national_id'], - text: 'eq(national_id)' - }, + vars: ["national_id"], + text: "eq(national_id)" + } + ] + }, + validate: { + deterministic: [ + { + vars: ["given_name", "family_name", "phone_number"], + text: "eq(given_name) and eq(family_name) and eq(phone_number)" + } + ] + }, + matchNotification: { + deterministic: [ { - vars: ['given_name', 'family_name', 'phone_number'], - text: 'eq(given_name) and eq(family_name) and eq(phone_number)' + vars: ["given_name", "family_name", "phone_number"], + text: "eq(given_name) and eq(family_name) and eq(phone_number)" } ], probabilistic: [ { - vars: [ - 'given_name', - 'family_name', - 'city', - 'phone_number', - 'national_id' - ], - text: 'match(given_name,3) and match(family_name,3) or match(given_name,3) and match(city,3) or match(family_name,3) and match(city,3) or match(phone_number,2) or match(national_id,3)' + vars: ["given_name", "family_name", "phone_number"], + text: "match(given_name,3) and match(family_name,3) or match(given_name,3) and match(phone_number,3) or match(family_name,3) and match(phone_number,3)" } ] } From 5d6723935bb4d852358640ea955a0d6dad80aeb3 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Fri, 31 May 2024 19:25:00 +0200 Subject: [PATCH 103/183] added unit tests --- .../src/test/Settings/Deterministic.test.tsx | 157 ------------------ .../settings/DeterministicContent.test.tsx | 113 +++++++++++++ .../src/test/settings/SourceView.test.tsx | 35 ++++ 3 files changed, 148 insertions(+), 157 deletions(-) delete mode 100644 JeMPI_Apps/JeMPI_UI/src/test/Settings/Deterministic.test.tsx create mode 100644 JeMPI_Apps/JeMPI_UI/src/test/settings/DeterministicContent.test.tsx create mode 100644 JeMPI_Apps/JeMPI_UI/src/test/settings/SourceView.test.tsx diff --git a/JeMPI_Apps/JeMPI_UI/src/test/Settings/Deterministic.test.tsx b/JeMPI_Apps/JeMPI_UI/src/test/Settings/Deterministic.test.tsx deleted file mode 100644 index 0cf3b0dd8..000000000 --- a/JeMPI_Apps/JeMPI_UI/src/test/Settings/Deterministic.test.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { render, fireEvent } from '@testing-library/react' -import '@testing-library/jest-dom/extend-expect' -import { Configuration, DemographicField } from 'types/Configuration' -import Deterministic from 'pages/settings/deterministic/Deterministic' - - -const mockDemographicData: DemographicField[] = [ - { - fieldName: 'given_name', - fieldType: 'String', - source: 'Column 1', - indexGoldenRecord: '@index(exact,trigram)', - indexInteraction: '@index(exact,trigram)', - linkMetaData: { - comparison: 'JARO_WINKLER_SIMILARITY', - comparisonLevels: [0.92], - m: 0.8806329, - u: 0.0026558 - } - }, - { - fieldName: 'family_name', - fieldType: 'String', - source: 'Column 2', - indexGoldenRecord: '@index(exact,trigram)', - indexInteraction: '@index(exact,trigram)', - linkMetaData: { - comparison: 'JARO_WINKLER_SIMILARITY', - comparisonLevels: [0.92], - m: 0.9140443, - u: 0.0006275 - } - }, - { - fieldName: 'gender', - fieldType: 'String', - source: 'Column 3', - indexGoldenRecord: '@index(exact,trigram)', - linkMetaData: { - comparison: 'JARO_WINKLER_SIMILARITY', - comparisonLevels: [0.92], - m: 0.9468393, - u: 0.4436446 - } - }, - { - fieldName: 'dob', - fieldType: 'String', - source: 'Column 4', - linkMetaData: { - comparison: 'JARO_WINKLER_SIMILARITY', - comparisonLevels: [0.92], - m: 0.7856196, - u: 0.0000465 - } - }, - { - fieldName: 'city', - fieldType: 'String', - source: 'Column 5', - indexGoldenRecord: '@index(trigram)', - linkMetaData: { - comparison: 'JARO_WINKLER_SIMILARITY', - comparisonLevels: [0.92], - m: 0.8445694, - u: 0.0355741 - } - }, - { - fieldName: 'phone_number', - fieldType: 'String', - source: 'Column 6', - indexGoldenRecord: '@index(exact,trigram)', - linkMetaData: { - comparison: 'JARO_WINKLER_SIMILARITY', - comparisonLevels: [0.92], - m: 0.84085, - u: 0.0000004 - } - }, - { - fieldName: 'national_id', - fieldType: 'String', - source: 'Column 7', - indexGoldenRecord: '@index(exact,trigram)', - indexInteraction: '@index(exact,trigram)', - linkMetaData: { - comparison: 'JARO_WINKLER_SIMILARITY', - comparisonLevels: [0.92], - m: 0.8441029, - u: 0.0000002 - } - } -] - -describe('Deterministic Component', () => { - const mockLinkingRules : Configuration['rules']['link'] = { - deterministic: { - rule1: { vars: ['national_id'], text: 'eq(national_id)' }, - rule2: { vars: ['given_name', 'family_name', 'phone_number'], text: 'eq(given_name) and eq(family_name) and eq(phone_number)' } - }, - probabilistic: { - rule1: { vars: ['given_name', 'family_name', 'city', 'phone_number', 'national_id'], text: 'match(given_name,3) and match(family_name,3) or match(given_name,3) and match(city,3) or match(family_name,3) and match(city,3) or match(phone_number,2) or match(national_id,3)' } - } - }; - - test('renders correctly', () => { - const { getByText, getByLabelText } = render( - - ) - - expect(getByText('Design View')).toBeInTheDocument() - expect(getByText('Source View')).toBeInTheDocument() - expect(getByLabelText('Select Comparator Function')).toBeInTheDocument() - expect(getByLabelText('Select Field')).toBeInTheDocument() - expect(getByLabelText('Select Operator')).toBeInTheDocument() - }) - - test('handles comparator change', () => { - const { getByLabelText } = render( - - ) - const selectComparator = getByLabelText( - 'Select Comparator Function' - ) as HTMLSelectElement - const changeEvent = new Event('change', { bubbles: true }) - Object.defineProperty(selectComparator, 'value', { - writable: true, - value: '1' - }) - fireEvent(selectComparator, changeEvent) - expect(selectComparator.value).toBe('1') - }) - - test('handles field change', () => { - const { getByLabelText } = render( - - ) - const selectField = getByLabelText('Select Field') as HTMLSelectElement - selectField.value = 'family_name' - fireEvent.change(selectField) - expect(selectField.value).toBe('family_name') - }) - - test('handles operator change', () => { - const { getByLabelText } = render( - - ) - const selectOperator = getByLabelText( - 'Select Operator' - ) as HTMLSelectElement - - selectOperator.value = 'And' - fireEvent.change(selectOperator) - expect(selectOperator.value).toBe('And') - }) -}) diff --git a/JeMPI_Apps/JeMPI_UI/src/test/settings/DeterministicContent.test.tsx b/JeMPI_Apps/JeMPI_UI/src/test/settings/DeterministicContent.test.tsx new file mode 100644 index 000000000..4e7a38463 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/test/settings/DeterministicContent.test.tsx @@ -0,0 +1,113 @@ +import React from 'react' +import { render, fireEvent, waitFor } from '@testing-library/react' +import DeterministicContent from 'pages/settings/deterministic/DeterministicContent' +import { Field } from 'types/Configuration' +import userEvent from '@testing-library/user-event' +import '@testing-library/jest-dom' + +describe('DeterministicContent', () => { + const demographicData: Field[] = [ + { fieldName: 'national_id', fieldType: 'String' }, + { fieldName: 'given_name', fieldType: 'String' }, + { fieldName: 'family_name', fieldType: 'String' } + ] + + const linkingRules = { + link: { + deterministic: [ + { vars: ['national_id'], text: 'eq(national_id)' }, + { + vars: ['given_name', 'family_name'], + text: 'eq(given_name) and eq(family_name)' + } + ] + } + } + + it('renders correctly', () => { + const { container } = render( + + ) + expect(container).toMatchSnapshot() + }) + + it('testing on change event for select comparator', () => { + render( + + ) + + const selectComparator = document.getElementById( + 'select-comparator-function' + ) as HTMLSelectElement + if (selectComparator) { + fireEvent.change(selectComparator, { target: { value: '1' } }) + expect(selectComparator).toHaveValue('1') + } + }) + + it('handles field change', () => { + render( + + ) + const selectField = document.getElementById('select-field') + if (selectField) { + fireEvent.change(selectField, { target: { value: 'family_name' } }) + expect(selectField).toBe('family_name') + } + }) + + it('handles operator change', async () => { + render( + + ) + const selectOperator = document.getElementById('select operator') + if (selectOperator) { + fireEvent.change(selectOperator, { target: { value: 'OR' } }) + await waitFor(() => expect(selectOperator).toBe('OR')) + } + }) + + it('handles add rule', () => { + render( + + ) + const addRuleButton = document.getElementById('add-rule-button') + if (addRuleButton) { + fireEvent.click(addRuleButton) + const ruleText = document.body.textContent + expect(ruleText).toContain('eq(given_name) OR eq(family_name)') + } + }) + + it('handles close', async () => { + render( + + ) + const closeButton = document.getElementById('close-button') + const sourceViewBUtton = document.getElementById('') + if (closeButton && sourceViewBUtton) { + userEvent.click(closeButton) + await waitFor(() => { + expect(sourceViewBUtton).toBeVisible() + }) + } + }) +}) diff --git a/JeMPI_Apps/JeMPI_UI/src/test/settings/SourceView.test.tsx b/JeMPI_Apps/JeMPI_UI/src/test/settings/SourceView.test.tsx new file mode 100644 index 000000000..333eb7bba --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/test/settings/SourceView.test.tsx @@ -0,0 +1,35 @@ +import React from 'react' +import { render, fireEvent, screen } from '@testing-library/react' +import '@testing-library/jest-dom/extend-expect' +import SourceView, { RowData } from 'pages/settings/deterministic/SourceView' +import { debug } from 'console' + +describe('SourceView Component', () => { + const mockData: RowData[] = [ + { id: 1, ruleNumber: 101, ruleText: 'eq(national_id)' }, + { + id: 2, + ruleNumber: 102, + ruleText: 'eq(given_name) and eq(family_name) and eq(phone_number)' + } + ] + const mockOnEditRow = jest.fn() + + it('renders the component with initial data', () => { + render() + expect(document.body).toHaveTextContent('eq(national_id)') + expect(document.body).toHaveTextContent( + 'eq(given_name) and eq(family_name) and eq(phone_number)' + ) + }) + + it('edits a row', async () => { + render() + fireEvent.click(document.getElementById('edit-button-1')!) + expect(mockOnEditRow).toHaveBeenCalledWith({ + id: 1, + ruleNumber: 101, + ruleText: 'eq(national_id)' + }) + }) +}) From a0a9838c8d20c45c1084fbf1f8699655bd79df3f Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Fri, 31 May 2024 19:26:28 +0200 Subject: [PATCH 104/183] refactored deterministic component --- .../settings/deterministic/BasicTabs.tsx | 29 -- .../deterministic/DeterministicContent.tsx | 393 +++++++++++------- .../settings/deterministic/SourceView.tsx | 119 +++--- 3 files changed, 298 insertions(+), 243 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/BasicTabs.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/BasicTabs.tsx index 6edc0020f..b17cc0f79 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/BasicTabs.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/BasicTabs.tsx @@ -35,33 +35,4 @@ export function a11yProps(index: number) { id: `simple-tab-${index}`, 'aria-controls': `simple-tabpanel-${index}`, }; -} - -export default function BasicTabs() { - const [value, setValue] = React.useState(0); - - const handleChange = (event: React.SyntheticEvent, newValue: number) => { - setValue(newValue); - }; - - return ( - - - - - - - - - - Validate - - - Linking - - - Matching - - - ); } \ No newline at end of file diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx index 2dc76903e..039ed5568 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -1,3 +1,4 @@ +import React, { useState, useEffect } from 'react'; import { Box, Button, @@ -5,22 +6,21 @@ import { InputLabel, MenuItem, Select, - SelectChangeEvent -} from '@mui/material' -import { useState, useEffect } from 'react' -import { Field, Rule } from 'types/Configuration' -import { transformFieldName } from 'utils/helpers' -import SourceView, { RowData } from './SourceView' - - + SelectChangeEvent, + IconButton +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import { Field, Rule } from 'types/Configuration'; +import { transformFieldName } from 'utils/helpers'; +import SourceView, { RowData } from './SourceView'; interface DeterministicContentProps { -demographicData: Field[]; -linkingRules: { - link: { - deterministic: Rule[]; + demographicData: Field[]; + linkingRules: { + link: { + deterministic: Rule[]; + }; }; -}; } const options = [ @@ -28,14 +28,15 @@ const options = [ { value: 1, label: 'Low Fuzziness' }, { value: 2, label: 'Medium Fuzziness' }, { value: 3, label: 'High Fuzziness' } -] +]; enum Operator { AND = 'And', OR = 'Or' } + const transformRulesToRowData = (rules: any): RowData[] => { - return rules.link.deterministic.map((rule: any, index:any) => ({ + return rules.link.deterministic.map((rule: any, index: any) => ({ id: index, ruleNumber: index + 1, ruleText: rule.text, @@ -46,86 +47,151 @@ const DeterministicContent = ({ demographicData = [], linkingRules }: DeterministicContentProps) => { - const [viewType, setViewType] = useState(0) - const [selectedComparator, setSelectedComparator] = useState(0) - const [selectedFields, setSelectedFields] = useState([]) - const [selectedOperator, setSelectedOperator] = useState('') - const [rules, setRules] = useState([]) - const [isOperatorDisabled, setIsOperatorDisabled] = useState(true) + const [viewType, setViewType] = useState(0); + const [comparators, setComparators] = useState([]); + const [fields, setFields] = useState([]); + const [operators, setOperators] = useState([]); + const [rules, setRules] = useState([]); + const [initialState, setInitialState] = useState({ + comparators: [] as number[], + fields: [] as string[], + operators: [] as Operator[], + }); const deterministicRules = transformRulesToRowData(linkingRules); - + useEffect(() => { - const savedComparator = localStorage.getItem('selectedComparator') - const savedFields = localStorage.getItem('selectedFields') - const savedOperator = localStorage.getItem('selectedOperator') - const savedRules = localStorage.getItem('rules') - - if (savedComparator || savedFields || savedOperator || savedRules) { - const parsedRules = savedRules ? JSON.parse(savedRules) : [] - setRules(parsedRules) - setSelectedComparator( - savedComparator ? Number(savedComparator) : selectedComparator - ) - setSelectedFields(savedFields ? JSON.parse(savedFields) : selectedFields) - setSelectedOperator(savedOperator ? (savedOperator as Operator) : '') - setIsOperatorDisabled(parsedRules.length === 0) + const savedComparators = localStorage.getItem('comparators'); + const savedFields = localStorage.getItem('fields'); + const savedOperators = localStorage.getItem('operators'); + const savedRules = localStorage.getItem('rules'); + + if (savedComparators || savedFields || savedOperators || savedRules) { + const parsedRules = savedRules ? JSON.parse(savedRules) : []; + setRules(parsedRules); + const parsedComparators = savedComparators ? JSON.parse(savedComparators) : []; + const parsedFields = savedFields ? JSON.parse(savedFields) : []; + const parsedOperators = savedOperators ? JSON.parse(savedOperators) : []; + setComparators(parsedComparators); + setFields(parsedFields); + setOperators(parsedOperators); + setInitialState({ + comparators: parsedComparators, + fields: parsedFields, + operators: parsedOperators, + }); } - }, []) + }, []); useEffect(() => { - localStorage.setItem('selectedComparator', selectedComparator.toString()) - }, [selectedComparator]) + localStorage.setItem('comparators', JSON.stringify(comparators)); + }, [comparators]); useEffect(() => { - localStorage.setItem('selectedFields', JSON.stringify(selectedFields)) - }, [selectedFields]) + localStorage.setItem('fields', JSON.stringify(fields)); + }, [fields]); useEffect(() => { - localStorage.setItem('selectedOperator', selectedOperator) - }, [selectedOperator]) + localStorage.setItem('operators', JSON.stringify(operators)); + }, [operators]); useEffect(() => { - localStorage.setItem('rules', JSON.stringify(rules)) - setIsOperatorDisabled(rules.length === 0) - }, [rules]) + localStorage.setItem('rules', JSON.stringify(rules)); + }, [rules]); const handleComparatorChange = ( - event: SelectChangeEvent + index: number, + event: SelectChangeEvent ) => { - setSelectedComparator(event.target.value as number) - } + const newComparators = [...comparators]; + newComparators[index] = event.target.value as number; + setComparators(newComparators); + }; const handleFieldChange = ( - event: SelectChangeEvent + index: number, + event: SelectChangeEvent ) => { - setSelectedFields(event.target.value as string[]) - } + const newFields = [...fields]; + newFields[index] = event.target.value as string; + setFields(newFields); + }; const handleOperatorChange = ( - event: SelectChangeEvent + index: number, + event: SelectChangeEvent ) => { - setSelectedOperator(event.target.value as Operator) - } + const newOperators = [...operators]; + newOperators[index] = event.target.value as Operator; + setOperators(newOperators); + }; const handleAddRule = () => { - const formattedFields = selectedFields.map(field => `eq(${field})`) - const newRule = { - vars: selectedFields, - text: formattedFields.join( - selectedOperator === Operator.AND ? ' and ' : ' or ' - ) + const vars = fields.filter(field => field !== ''); + const text = vars.map((field, index) => { + const operator = index > 0 ? ` ${operators[index - 1].toLowerCase()} ` : ''; + return `${operator}eq(${field})`; + }).join(''); + + const rule = { + function: comparators[0], + vars, + text + }; + console.log(rules) + setRules([...rules, rule]); + setInitialState({ + comparators: [...comparators], + fields: [...fields], + operators: [...operators], + }); + }; + + const handleRowEdit = (row: RowData) => { + const regex = /eq\(([^)]+)\)/g; + const matchedFields = []; + let match; + while ((match = regex.exec(row.ruleText)) !== null) { + matchedFields.push(match[1]); + } + + setComparators(new Array(matchedFields.length).fill(0)); + setFields(matchedFields); + setOperators(new Array(matchedFields.length - 1).fill(Operator.AND)); + setViewType(1); + }; + + const handleAddRow = () => { + setComparators([...comparators, 0]); + setFields([...fields, '']); + setOperators([...operators, Operator.AND]); + }; + + const handleClose = () => { + setViewType(0); + }; + + const isAddRuleDisabled = () => { + if ( + fields.length === 0 || + fields.some(field => field.length === 0) || + operators.some((operator, index) => index < fields.length - 1 && !operator) + ) { + return true; } - setRules([...rules, newRule]) - setSelectedFields([]) - setSelectedOperator('') - } + + return JSON.stringify(initialState) === JSON.stringify({ + comparators, + fields, + operators, + }); + }; return ( - - + - {viewType === 0 ? ( + {viewType === 0 ? ( - + - ) - : - ( - - - - Select Comparator Function - - - - - Select Field - handleComparatorChange(index, event)} + > + {options.map(option => ( + + {option.label} + + ))} + + + + Select Field + + + + + Select Operator + + + + + ))} + + + + + + + + Close + + + + )} - ) -} + ); +}; -export default DeterministicContent \ No newline at end of file +export default DeterministicContent; diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx index 833fe3237..b44018c2c 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx @@ -1,58 +1,75 @@ -import React, { useState, useEffect } from 'react'; -import Box from '@mui/material/Box'; -import { DataGrid, GridColDef, GridEventListener, GridRowEditStopReasons, GridRowId, GridRowModel, GridRowModes, GridRowModesModel, GridActionsCellItem } from '@mui/x-data-grid'; -import EditIcon from '@mui/icons-material/Edit'; -import SaveIcon from '@mui/icons-material/Save'; -import CancelIcon from '@mui/icons-material/Close'; +import React, { useState, useEffect } from 'react' +import Box from '@mui/material/Box' +import { + DataGrid, + GridColDef, + GridEventListener, + GridRowEditStopReasons, + GridRowId, + GridRowModel, + GridRowModes, + GridRowModesModel, + GridActionsCellItem +} from '@mui/x-data-grid' +import EditIcon from '@mui/icons-material/Edit' +import SaveIcon from '@mui/icons-material/Save' +import CancelIcon from '@mui/icons-material/Close' export interface RowData { - id: string; - ruleNumber: number; - ruleText: string; + id: number + ruleNumber: number + ruleText: string } interface SourceViewProps { - data: RowData[]; + data: RowData[] + onEditRow: (row: RowData) => void } -const SourceView: React.FC = ({ data }) => { - const [rows, setRows] = useState([]); - const [rowModesModel, setRowModesModel] = useState({}); +const SourceView: React.FC = ({ data, onEditRow }) => { + const [rows, setRows] = useState([]) + const [rowModesModel, setRowModesModel] = useState({}) useEffect(() => { - setRows(data); - }, [data]); + setRows(data) + }, [data]) const handleEditClick = (id: GridRowId) => () => { - setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } }); - }; + const row = rows.find(row => row.id === id) + if (row) { + onEditRow(row) + } + } const handleSaveClick = (id: GridRowId) => () => { - setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }); - }; + setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } }) + } const handleCancelClick = (id: GridRowId) => () => { setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View, ignoreModifications: true } - }); - }; + }) + } const processRowUpdate = (newRow: GridRowModel) => { - const { id, ...updatedRow } = newRow; - setRows(rows.map(row => (row.id === id ? updatedRow as RowData : row))); - return updatedRow as RowData; - }; + const { id, ...updatedRow } = newRow + setRows(rows.map(row => (row.id === id ? (updatedRow as RowData) : row))) + return updatedRow as RowData + } const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { - setRowModesModel(newRowModesModel); - }; + setRowModesModel(newRowModesModel) + } - const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { + const handleRowEditStop: GridEventListener<'rowEditStop'> = ( + params, + event + ) => { if (params.reason === GridRowEditStopReasons.rowFocusOut) { - event.defaultMuiPrevented = true; + event.defaultMuiPrevented = true } - }; + } const columns: GridColDef[] = [ { @@ -61,7 +78,7 @@ const SourceView: React.FC = ({ data }) => { width: 150, align: 'center', headerAlign: 'center', - editable: false, + editable: false }, { field: 'ruleText', @@ -69,7 +86,7 @@ const SourceView: React.FC = ({ data }) => { width: 500, align: 'left', headerAlign: 'left', - editable: true, + editable: true }, { field: 'actions', @@ -80,40 +97,20 @@ const SourceView: React.FC = ({ data }) => { headerAlign: 'center', cellClassName: 'actions', getActions: ({ id }) => { - const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; - if (isInEditMode) { - return [ - } - id="save-button" - label="Save" - sx={{ color: 'white' }} - onClick={handleSaveClick(id)} - />, - } - id="cancel-button" - label="Cancel" - className="textPrimary" - onClick={handleCancelClick(id)} - color="inherit" - /> - ]; - } - return [ } - id="edit-button" + id={`edit-button-${id}`} + data-testid={`edit-button-${id}`} label="Edit" className="textPrimary" onClick={handleEditClick(id)} color="inherit" /> - ]; + ] } } - ]; + ] return ( = ({ data }) => { color: '#fff' }, '& .MuiDataGrid-columnHeaders': { - display: 'none', - }, + display: 'none' + } }} > = ({ data }) => { disableColumnSelector /> - ); -}; + ) +} -export default SourceView; +export default SourceView From aed4b81f8e4984042e689e31c1d5325216adcee5 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Tue, 4 Jun 2024 10:02:04 +0200 Subject: [PATCH 105/183] CU-86bz1zxuq - Code Review Changes --- .../JeMPI_UI/src/pages/settings/Settings.tsx | 2 +- .../deterministic/DeterministicContent.tsx | 13 +- JeMPI_Apps/JeMPI_UI/yarn.lock | 19272 ++++++++-------- 3 files changed, 9674 insertions(+), 9613 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index fe66ce221..500b2083f 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -1,6 +1,6 @@ import { Grid, Tab, Tabs, Typography } from '@mui/material' import { Box } from '@mui/system' -import React, { SyntheticEvent, useEffect, useMemo, useState } from 'react' +import React, { SyntheticEvent, useMemo, useState } from 'react' import CommonSettings from './common/Common' import UniqueToGR from './uniqueToGR/UniqueToGR' import UniqueToInteraction from './uniqueToInteraction/UniqueToInteraction' diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx index 039ed5568..8eb4ccb78 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import { Box, Button, @@ -35,7 +35,7 @@ enum Operator { OR = 'Or' } -const transformRulesToRowData = (rules: any): RowData[] => { +const transformRulesToRowData = (rules: { link: { deterministic: Rule[] } }): RowData[] => { return rules.link.deterministic.map((rule: any, index: any) => ({ id: index, ruleNumber: index + 1, @@ -138,7 +138,6 @@ const DeterministicContent = ({ vars, text }; - console.log(rules) setRules([...rules, rule]); setInitialState({ comparators: [...comparators], @@ -238,12 +237,12 @@ const DeterministicContent = ({ }} > - + Select Comparator Function handleFieldChange(index, event)} @@ -284,7 +283,7 @@ const DeterministicContent = ({ - ) : ( - - Match (phone number, 3) - Or - Match (National ID, 3) Or - - Int (Match (given name , 3)) + Int(Match (family name, 3)) + - Int(Match (city, 3)) ≥ 3 - - - )} + ) } diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx index b44018c2c..8f304df82 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx @@ -12,8 +12,6 @@ import { GridActionsCellItem } from '@mui/x-data-grid' import EditIcon from '@mui/icons-material/Edit' -import SaveIcon from '@mui/icons-material/Save' -import CancelIcon from '@mui/icons-material/Close' export interface RowData { id: number From 66c98a8f646e640030210655f92a70d6bd27d74f Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Wed, 5 Jun 2024 15:42:33 +0200 Subject: [PATCH 107/183] CU-86bz2z6at - Update Select Comparator Function --- .../deterministic/DeterministicContent.tsx | 17 +++++++++++------ .../pages/settings/deterministic/SourceView.tsx | 2 -- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx index 8eb4ccb78..30fe3da09 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -23,14 +23,14 @@ interface DeterministicContentProps { }; } -const options = [ +export const options = [ { value: 0, label: 'Exact' }, { value: 1, label: 'Low Fuzziness' }, { value: 2, label: 'Medium Fuzziness' }, { value: 3, label: 'High Fuzziness' } ]; -enum Operator { +export enum Operator { AND = 'And', OR = 'Or' } @@ -130,7 +130,9 @@ const DeterministicContent = ({ const vars = fields.filter(field => field !== ''); const text = vars.map((field, index) => { const operator = index > 0 ? ` ${operators[index - 1].toLowerCase()} ` : ''; - return `${operator}eq(${field})`; + const comparator = comparators[index]; + const comparatorFunction = comparator === 0 ? `eq(${field})` : `match(${field}, ${comparator})`; + return `${operator}${comparatorFunction}`; }).join(''); const rule = { @@ -138,6 +140,7 @@ const DeterministicContent = ({ vars, text }; + console.log('rule', rule) setRules([...rules, rule]); setInitialState({ comparators: [...comparators], @@ -147,14 +150,16 @@ const DeterministicContent = ({ }; const handleRowEdit = (row: RowData) => { - const regex = /eq\(([^)]+)\)/g; + const regex = /(eq|match)\(([^),]+)(?:, (\d+))?\)/g; const matchedFields = []; + const matchedComparators = []; let match; while ((match = regex.exec(row.ruleText)) !== null) { - matchedFields.push(match[1]); + matchedFields.push(match[2]); + matchedComparators.push(match[1] === 'eq' ? 0 : parseInt(match[3], 10)); } - setComparators(new Array(matchedFields.length).fill(0)); + setComparators(matchedComparators); setFields(matchedFields); setOperators(new Array(matchedFields.length - 1).fill(Operator.AND)); setViewType(1); diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx index b44018c2c..8f304df82 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx @@ -12,8 +12,6 @@ import { GridActionsCellItem } from '@mui/x-data-grid' import EditIcon from '@mui/icons-material/Edit' -import SaveIcon from '@mui/icons-material/Save' -import CancelIcon from '@mui/icons-material/Close' export interface RowData { id: number From 1c55a54b16f8e114686a6c4580229251f19d325e Mon Sep 17 00:00:00 2001 From: Matthew Erispe Date: Thu, 6 Jun 2024 10:28:32 +0200 Subject: [PATCH 108/183] code clean up --- JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx | 8 ++-- JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts | 4 +- JeMPI_Apps/JeMPI_UI/src/services/mockData.ts | 16 ++++---- JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts | 37 ++++++++++--------- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx b/JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx index 005760b4b..356ddd0d1 100644 --- a/JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/router/BaseRouter.tsx @@ -22,9 +22,11 @@ const baseRouter = createBrowserRouter([ children: [ { path: '', - element: - - + element: ( + + + + ) }, { path: 'browse-records', diff --git a/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts b/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts index 23c8bdcf4..deaee014a 100644 --- a/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts +++ b/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts @@ -89,9 +89,7 @@ export class ApiClient { } async fetchConfiguration() { - const { data } = await moxios.get( - ROUTES.GET_CONFIGURATION - ) + const { data } = await moxios.get(ROUTES.GET_CONFIGURATION) return data } diff --git a/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts b/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts index 55fd2ff87..7db14d49f 100644 --- a/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts +++ b/JeMPI_Apps/JeMPI_UI/src/services/mockData.ts @@ -498,30 +498,30 @@ const configuration = { link: { deterministic: [ { - vars: ["national_id"], - text: "eq(national_id)" + vars: ['national_id'], + text: 'eq(national_id)' } ] }, validate: { deterministic: [ { - vars: ["given_name", "family_name", "phone_number"], - text: "eq(given_name) and eq(family_name) and eq(phone_number)" + vars: ['given_name', 'family_name', 'phone_number'], + text: 'eq(given_name) and eq(family_name) and eq(phone_number)' } ] }, matchNotification: { deterministic: [ { - vars: ["given_name", "family_name", "phone_number"], - text: "eq(given_name) and eq(family_name) and eq(phone_number)" + vars: ['given_name', 'family_name', 'phone_number'], + text: 'eq(given_name) and eq(family_name) and eq(phone_number)' } ], probabilistic: [ { - vars: ["given_name", "family_name", "phone_number"], - text: "match(given_name,3) and match(family_name,3) or match(given_name,3) and match(phone_number,3) or match(family_name,3) and match(phone_number,3)" + vars: ['given_name', 'family_name', 'phone_number'], + text: 'match(given_name,3) and match(family_name,3) or match(given_name,3) and match(phone_number,3) or match(family_name,3) and match(phone_number,3)' } ] } diff --git a/JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts b/JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts index 26c8f52d8..5d5fa676c 100644 --- a/JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts +++ b/JeMPI_Apps/JeMPI_UI/src/utils/helpers.ts @@ -1,5 +1,5 @@ import { GridColDef } from '@mui/x-data-grid' -import { Configuration, CustomNode, Field, LinkingRules } from 'types/Configuration' +import { Configuration, CustomNode, Field } from 'types/Configuration' import { AnyRecord } from 'types/PatientRecord' interface ValidationObject { @@ -14,8 +14,6 @@ type Params = { } } - - export const isInputValid = (value: unknown, validation?: ValidationObject) => { if (validation && typeof value === 'string') { const regexp = new RegExp(validation.regex || '') @@ -78,30 +76,35 @@ export const randomId = () => { return Math.random().toString(36).substring(2, 9) } - - export const generateId = (configuration: Configuration): Configuration => { const generateIdForFields = (fields: Field[]): Field[] => { - return fields.map((item) => ({ ...item, id: Math.random().toString(36).substr(2, 9) })); - }; + return fields.map(item => ({ + ...item, + id: Math.random().toString(36).substr(2, 9) + })) + } const generateIdForNodes = (nodes: CustomNode[]): CustomNode[] => { - return nodes.map((node) => ({ + return nodes.map(node => ({ ...node, id: Math.random().toString(36).substr(2, 9), - fields: generateIdForFields(node.fields), - })); - }; + fields: generateIdForFields(node.fields) + })) + } return { ...configuration, - auxInteractionFields: generateIdForFields(configuration.auxInteractionFields), - auxGoldenRecordFields: generateIdForFields(configuration.auxGoldenRecordFields), + auxInteractionFields: generateIdForFields( + configuration.auxInteractionFields + ), + auxGoldenRecordFields: generateIdForFields( + configuration.auxGoldenRecordFields + ), demographicFields: generateIdForFields(configuration.demographicFields), - additionalNodes: generateIdForNodes(configuration.additionalNodes), - }; -}; - + additionalNodes: generateIdForNodes(configuration.additionalNodes) + } +} + export function processIndex(index: string) { if (index) { return index.replace(/@index\(|\)(?=, trigram|$)/g, ' ').replace(/,/g, ', ') From 49a0aca59df913ab093ebb1a5a8b9f15db6b2bd2 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Thu, 6 Jun 2024 10:35:22 +0200 Subject: [PATCH 109/183] updated types --- .../src/pages/settings/deterministic/DeterministicContent.tsx | 4 ++-- .../JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx index 30fe3da09..e7274558a 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -36,7 +36,7 @@ export enum Operator { } const transformRulesToRowData = (rules: { link: { deterministic: Rule[] } }): RowData[] => { - return rules.link.deterministic.map((rule: any, index: any) => ({ + return rules.link.deterministic.map((rule: Rule, index: number) => ({ id: index, ruleNumber: index + 1, ruleText: rule.text, @@ -51,7 +51,7 @@ const DeterministicContent = ({ const [comparators, setComparators] = useState([]); const [fields, setFields] = useState([]); const [operators, setOperators] = useState([]); - const [rules, setRules] = useState([]); + const [rules, setRules] = useState([]); const [initialState, setInitialState] = useState({ comparators: [] as number[], fields: [] as string[], diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx index 8f304df82..e59f74bbb 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx @@ -98,6 +98,7 @@ const SourceView: React.FC = ({ data, onEditRow }) => { return [ } + key={`action-item-${id}`} id={`edit-button-${id}`} data-testid={`edit-button-${id}`} label="Edit" From 2b062579c99d6f809229886963e2657a83d57fb3 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Thu, 6 Jun 2024 17:40:36 +0200 Subject: [PATCH 110/183] refactored SEttings --- .../JeMPI_UI/src/pages/settings/Settings.tsx | 51 ++++--------------- .../settings/deterministic/BasicTabs.tsx | 26 +++++----- 2 files changed, 22 insertions(+), 55 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index 500b2083f..8a116d257 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -1,50 +1,20 @@ import { Grid, Tab, Tabs, Typography } from '@mui/material' import { Box } from '@mui/system' -import React, { SyntheticEvent, useMemo, useState } from 'react' +import { SyntheticEvent, useMemo, useState } from 'react' import CommonSettings from './common/Common' import UniqueToGR from './uniqueToGR/UniqueToGR' import UniqueToInteraction from './uniqueToInteraction/UniqueToInteraction' import Deterministic from './deterministic/Deterministic' import Blocking from './blocking/Blocking' import GoldenRecordLists from './goldenRecordLists/GoldenRecordLists' -import './Shapes.css' import { useConfig } from 'hooks/useConfig' import { useQuery } from '@tanstack/react-query' import { generateId } from 'utils/helpers' import Loading from 'components/common/Loading' import InteractiveNode from './interactiveNode/InteractiveNode' import { Configuration } from 'types/Configuration' +import { CustomTabPanel, a11yProps } from './deterministic/BasicTabs' -interface TabPanelProps { - children?: React.ReactNode - index: number - value: number -} - -function CustomTabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props - - return ( - - ) -} - -function a11yProps(index: number) { - return { - id: `settings-tab-${index}`, - 'aria-controls': `settings-tabpanel-${index}` - } -} const Settings = () => { const [value, setValue] = useState(0) const { apiClient } = useConfig() @@ -109,13 +79,11 @@ const Settings = () => { Setup common properties - + - Setup properties that are unique to the golden record + Setup properties that are unique to the golden record { - Setup properties that are unique to the interaction + Setup properties that are unique to the interaction { Setup properties for Golden record lists - + - + Blocking diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/BasicTabs.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/BasicTabs.tsx index b17cc0f79..cb2fc372e 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/BasicTabs.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/BasicTabs.tsx @@ -1,17 +1,15 @@ -import * as React from 'react'; -import Tabs from '@mui/material/Tabs'; -import Tab from '@mui/material/Tab'; -import Typography from '@mui/material/Typography'; -import Box from '@mui/material/Box'; +import * as React from 'react' +import Typography from '@mui/material/Typography' +import Box from '@mui/material/Box' interface TabPanelProps { - children?: React.ReactNode; - index: number; - value: number; + children?: React.ReactNode + index: number + value: number } export function CustomTabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; + const { children, value, index, ...other } = props return (
{value === index && ( - + {children} )}
- ); + ) } export function a11yProps(index: number) { return { id: `simple-tab-${index}`, - 'aria-controls': `simple-tabpanel-${index}`, - }; -} \ No newline at end of file + 'aria-controls': `simple-tabpanel-${index}` + } +} From bcc3673010f745e7ecfa3d3d536d8eae02ddcc59 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Thu, 6 Jun 2024 17:41:45 +0200 Subject: [PATCH 111/183] added hasUndefinedRule prop, changed data grid styles --- .../settings/deterministic/Deterministic.tsx | 62 ++-- .../deterministic/DeterministicContent.tsx | 311 ++++++++++-------- .../settings/deterministic/SourceView.tsx | 69 ++-- JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts | 2 +- .../settings/DeterministicContent.test.tsx | 6 + .../src/test/settings/SourceView.test.tsx | 22 +- 6 files changed, 282 insertions(+), 190 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx index 7ee6b87df..9960f59e5 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx @@ -1,40 +1,37 @@ -import { - Box, - Card, - CardContent, - Tab, - Tabs -} from '@mui/material' +import { Box, Card, CardContent, Tab, Tabs } from '@mui/material' import { useState } from 'react' import { Field, Rule } from 'types/Configuration' import { CustomTabPanel, a11yProps } from './BasicTabs' import DeterministicContent from './DeterministicContent' interface DeterministicProps { - demographicData: Field[]; + demographicData: Field[] rules: { - link: { - deterministic: Rule[]; - }; - validate: { - deterministic: Rule[]; - }; - matchNotification: { - deterministic: Rule[]; - }; - }; + link?: { + deterministic?: Rule[] + } + validate?: { + deterministic?: Rule[] + } + matchNotification?: { + deterministic?: Rule[] + } + } } - const Deterministic = ({ demographicData = [], - rules + rules = {} }: DeterministicProps) => { - const [value, setValue] = useState(0); + const [value, setValue] = useState(0) const handleChange = (event: React.SyntheticEvent, newValue: number) => { - setValue(newValue); - }; + setValue(newValue) + } + + const linkingRules = rules.link?.deterministic ?? [] + const validateRules = rules.validate?.deterministic ?? [] + const matchNotificationRules = rules.matchNotification?.deterministic ?? [] return ( @@ -43,12 +40,16 @@ const Deterministic = ({ width: '100%', display: 'flex', flexDirection: 'column', - gap: 6 + backgroundColor: '#f5f5f5' }} > - - + + @@ -57,19 +58,22 @@ const Deterministic = ({ diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx index e7274558a..b00c52166 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react' import { Box, Button, @@ -8,19 +8,20 @@ import { Select, SelectChangeEvent, IconButton -} from '@mui/material'; -import AddIcon from '@mui/icons-material/Add'; -import { Field, Rule } from 'types/Configuration'; -import { transformFieldName } from 'utils/helpers'; -import SourceView, { RowData } from './SourceView'; +} from '@mui/material' +import AddIcon from '@mui/icons-material/Add' +import { Field, Rule } from 'types/Configuration' +import { transformFieldName } from 'utils/helpers' +import SourceView, { RowData } from './SourceView' interface DeterministicContentProps { - demographicData: Field[]; + demographicData: Field[] + hasUndefinedRule: boolean linkingRules: { link: { - deterministic: Rule[]; - }; - }; + deterministic: Rule[] + } + } } export const options = [ @@ -28,172 +29,198 @@ export const options = [ { value: 1, label: 'Low Fuzziness' }, { value: 2, label: 'Medium Fuzziness' }, { value: 3, label: 'High Fuzziness' } -]; +] export enum Operator { AND = 'And', OR = 'Or' } -const transformRulesToRowData = (rules: { link: { deterministic: Rule[] } }): RowData[] => { +const transformRulesToRowData = (rules: { + link: { deterministic: Rule[] } +}): RowData[] => { return rules.link.deterministic.map((rule: Rule, index: number) => ({ id: index, ruleNumber: index + 1, - ruleText: rule.text, - })); -}; + ruleText: rule.text + })) +} const DeterministicContent = ({ demographicData = [], - linkingRules + linkingRules, + hasUndefinedRule }: DeterministicContentProps) => { - const [viewType, setViewType] = useState(0); - const [comparators, setComparators] = useState([]); - const [fields, setFields] = useState([]); - const [operators, setOperators] = useState([]); - const [rules, setRules] = useState([]); + const [viewType, setViewType] = useState(0) + const [comparators, setComparators] = useState([]) + const [fields, setFields] = useState([]) + const [operators, setOperators] = useState([]) + const [rules, setRules] = useState([]) const [initialState, setInitialState] = useState({ comparators: [] as number[], fields: [] as string[], - operators: [] as Operator[], - }); + operators: [] as Operator[] + }) - const deterministicRules = transformRulesToRowData(linkingRules); + const deterministicRules = transformRulesToRowData(linkingRules) useEffect(() => { - const savedComparators = localStorage.getItem('comparators'); - const savedFields = localStorage.getItem('fields'); - const savedOperators = localStorage.getItem('operators'); - const savedRules = localStorage.getItem('rules'); + const savedComparators = localStorage.getItem('comparators') + const savedFields = localStorage.getItem('fields') + const savedOperators = localStorage.getItem('operators') + const savedRules = localStorage.getItem('rules') if (savedComparators || savedFields || savedOperators || savedRules) { - const parsedRules = savedRules ? JSON.parse(savedRules) : []; - setRules(parsedRules); - const parsedComparators = savedComparators ? JSON.parse(savedComparators) : []; - const parsedFields = savedFields ? JSON.parse(savedFields) : []; - const parsedOperators = savedOperators ? JSON.parse(savedOperators) : []; - setComparators(parsedComparators); - setFields(parsedFields); - setOperators(parsedOperators); + const parsedRules = savedRules ? JSON.parse(savedRules) : [] + setRules(parsedRules) + const parsedComparators = savedComparators + ? JSON.parse(savedComparators) + : [] + const parsedFields = savedFields ? JSON.parse(savedFields) : [] + const parsedOperators = savedOperators ? JSON.parse(savedOperators) : [] + setComparators(parsedComparators) + setFields(parsedFields) + setOperators(parsedOperators) setInitialState({ comparators: parsedComparators, fields: parsedFields, - operators: parsedOperators, - }); + operators: parsedOperators + }) } - }, []); + }, []) useEffect(() => { - localStorage.setItem('comparators', JSON.stringify(comparators)); - }, [comparators]); + localStorage.setItem('comparators', JSON.stringify(comparators)) + }, [comparators]) useEffect(() => { - localStorage.setItem('fields', JSON.stringify(fields)); - }, [fields]); + localStorage.setItem('fields', JSON.stringify(fields)) + }, [fields]) useEffect(() => { - localStorage.setItem('operators', JSON.stringify(operators)); - }, [operators]); + localStorage.setItem('operators', JSON.stringify(operators)) + }, [operators]) useEffect(() => { - localStorage.setItem('rules', JSON.stringify(rules)); - }, [rules]); + localStorage.setItem('rules', JSON.stringify(rules)) + }, [rules]) const handleComparatorChange = ( index: number, event: SelectChangeEvent ) => { - const newComparators = [...comparators]; - newComparators[index] = event.target.value as number; - setComparators(newComparators); - }; + const newComparators = [...comparators] + newComparators[index] = event.target.value as number + setComparators(newComparators) + } const handleFieldChange = ( index: number, event: SelectChangeEvent ) => { - const newFields = [...fields]; - newFields[index] = event.target.value as string; - setFields(newFields); - }; + const newFields = [...fields] + newFields[index] = event.target.value as string + setFields(newFields) + } const handleOperatorChange = ( index: number, event: SelectChangeEvent ) => { - const newOperators = [...operators]; - newOperators[index] = event.target.value as Operator; - setOperators(newOperators); - }; + const newOperators = [...operators] + newOperators[index] = event.target.value as Operator + setOperators(newOperators) + } const handleAddRule = () => { - const vars = fields.filter(field => field !== ''); - const text = vars.map((field, index) => { - const operator = index > 0 ? ` ${operators[index - 1].toLowerCase()} ` : ''; - const comparator = comparators[index]; - const comparatorFunction = comparator === 0 ? `eq(${field})` : `match(${field}, ${comparator})`; - return `${operator}${comparatorFunction}`; - }).join(''); + const vars = fields.filter(field => field !== '') + const text = vars + .map((field, index) => { + const operator = + index > 0 ? ` ${operators[index - 1].toLowerCase()} ` : '' + const comparator = comparators[index] + const comparatorFunction = + comparator === 0 ? `eq(${field})` : `match(${field}, ${comparator})` + return `${operator}${comparatorFunction}` + }) + .join('') const rule = { function: comparators[0], vars, text - }; - console.log('rule', rule) - setRules([...rules, rule]); + } + setRules([...rules, rule]) setInitialState({ comparators: [...comparators], fields: [...fields], - operators: [...operators], - }); - }; + operators: [...operators] + }) + } const handleRowEdit = (row: RowData) => { - const regex = /(eq|match)\(([^),]+)(?:, (\d+))?\)/g; - const matchedFields = []; - const matchedComparators = []; - let match; + const regex = /(eq|match)\(([^),]+)(?:, (\d+))?\)/g + const matchedFields = [] + const matchedComparators = [] + let match while ((match = regex.exec(row.ruleText)) !== null) { - matchedFields.push(match[2]); - matchedComparators.push(match[1] === 'eq' ? 0 : parseInt(match[3], 10)); + matchedFields.push(match[2]) + matchedComparators.push(match[1] === 'eq' ? 0 : parseInt(match[3], 10)) } - setComparators(matchedComparators); - setFields(matchedFields); - setOperators(new Array(matchedFields.length - 1).fill(Operator.AND)); - setViewType(1); - }; + setComparators(matchedComparators) + setFields(matchedFields) + setOperators(new Array(matchedFields.length - 1).fill(Operator.AND)) + setViewType(1) + } const handleAddRow = () => { - setComparators([...comparators, 0]); - setFields([...fields, '']); - setOperators([...operators, Operator.AND]); - }; + setComparators([...comparators, 0]) + setFields([...fields, '']) + setOperators([...operators, Operator.AND]) + } const handleClose = () => { - setViewType(0); - }; + setViewType(0) + } const isAddRuleDisabled = () => { if ( fields.length === 0 || fields.some(field => field.length === 0) || - operators.some((operator, index) => index < fields.length - 1 && !operator) + operators.some( + (operator, index) => index < fields.length - 1 && !operator + ) ) { - return true; + return true } - return JSON.stringify(initialState) === JSON.stringify({ - comparators, - fields, - operators, - }); - }; + return ( + JSON.stringify(initialState) === + JSON.stringify({ + comparators, + fields, + operators + }) + ) + } + + const handleAddUndefinedRule = () => { + handleAddRow() + setViewType(1) + } return ( - + - - - - {viewType === 0 ? ( - - - {/* Match (phone number, 3) - Or - Match (National ID, 3) Or - - Int (Match (given name , 3)) + Int(Match (family name, 3)) + - Int(Match (city, 3)) ≥ 3 - */} - - ):( - + + + + + + + - - - Select Comparator Function - - - - - - Select Field - - - - - - Select Operator - - - - - ) } - - - - - - - - + /> +
+ + + ) } diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx new file mode 100644 index 000000000..baf10ae81 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx @@ -0,0 +1,373 @@ +import { AddOutlined } from '@mui/icons-material' +import { + Card, + CardContent, + Button, + FormControl, + InputLabel, + Select, + IconButton, + Box, + MenuItem, + SelectChangeEvent +} from '@mui/material' +import React, { useEffect, useState } from 'react' +import { Field, Rule } from 'types/Configuration'; +import SourceView, { RowData } from '../deterministic/SourceView'; +import { Operator, options } from '../deterministic/DeterministicContent'; +import { transformFieldName } from 'utils/helpers'; + +interface BlockingContentProps { + demographicData: Field[]; + hasUndefinedRule: boolean; + linkingRules: { + validate?: { deterministic?: Rule[] } + matchNotification?: { + probabilistic?: Rule[]; + }; + }; +} + +const transformRulesToRowData = (rules: { probabilistic: Rule[] }): RowData[] => { + return rules.probabilistic.map((rule: any, index: any) => ({ + id: index, + ruleNumber: index + 1, + ruleText: rule.text, + })); +}; + +const BlockingContent = ({ demographicData = [], hasUndefinedRule, linkingRules }: BlockingContentProps) => { + const [viewType, setViewType] = useState(0); + + const probabilisticRows = linkingRules.matchNotification?.probabilistic ?? []; + const [comparators, setComparators] = useState([]) + const [fields, setFields] = useState([]) + const [operators, setOperators] = useState([]) + const [rules, setRules] = useState([]) + const [initialState, setInitialState] = useState({ + comparators: [] as number[], + fields: [] as string[], + operators: [] as Operator[] + }) + + useEffect(() => { + const savedComparators = localStorage.getItem('comparators') + const savedFields = localStorage.getItem('fields') + const savedOperators = localStorage.getItem('operators') + const savedRules = localStorage.getItem('rules') + + if (savedComparators || savedFields || savedOperators || savedRules) { + const parsedRules = savedRules ? JSON.parse(savedRules) : [] + setRules(parsedRules) + const parsedComparators = savedComparators + ? JSON.parse(savedComparators) + : [] + const parsedFields = savedFields ? JSON.parse(savedFields) : [] + const parsedOperators = savedOperators ? JSON.parse(savedOperators) : [] + setComparators(parsedComparators) + setFields(parsedFields) + setOperators(parsedOperators) + setInitialState({ + comparators: parsedComparators, + fields: parsedFields, + operators: parsedOperators + }) + } + }, []) + + useEffect(() => { + localStorage.setItem('comparators', JSON.stringify(comparators)) + }, [comparators]) + + useEffect(() => { + localStorage.setItem('fields', JSON.stringify(fields)) + }, [fields]) + + useEffect(() => { + localStorage.setItem('operators', JSON.stringify(operators)) + }, [operators]) + + useEffect(() => { + localStorage.setItem('rules', JSON.stringify(linkingRules)) + }, [linkingRules]) + + const handleComparatorChange = ( + index: number, + event: SelectChangeEvent + ) => { + const newComparators = [...comparators] + newComparators[index] = event.target.value as number + setComparators(newComparators) + } + + const handleFieldChange = ( + index: number, + event: SelectChangeEvent + ) => { + const newFields = [...fields] + newFields[index] = event.target.value as string + setFields(newFields) + } + + const handleOperatorChange = ( + index: number, + event: SelectChangeEvent + ) => { + const newOperators = [...operators] + newOperators[index] = event.target.value as Operator + setOperators(newOperators) + } + + const handleAddRule = () => { + const vars = fields.filter(field => field !== '') + const text = vars + .map((field, index) => { + const operator = + index > 0 ? ` ${operators[index - 1].toLowerCase()} ` : '' + const comparator = comparators[index] + const comparatorFunction = + comparator === 0 ? `eq(${field})` : `match(${field}, ${comparator})` + return `${operator}${comparatorFunction}` + }) + .join('') + + const rule = { + function: comparators[0], + vars, + text + } + setRules([...rules, rule]) + setInitialState({ + comparators: [...comparators], + fields: [...fields], + operators: [...operators] + }) + } + + const handleRowEdit = (row: RowData) => { + const regex = /(eq|match)\(([^),]+)(?:, (\d+))?\)/g + const matchedFields = [] + const matchedComparators = [] + let match + while ((match = regex.exec(row.ruleText)) !== null) { + matchedFields.push(match[2]) + matchedComparators.push(match[1] === 'eq' ? 0 : parseInt(match[3], 10)) + } + + setComparators(matchedComparators) + setFields(matchedFields) + setOperators(new Array(matchedFields.length - 1).fill(Operator.AND)) + setViewType(1) + } + + const handleAddRow = () => { + setComparators([...comparators, 0]) + setFields([...fields, '']) + setOperators([...operators, Operator.AND]) + } + + const handleClose = () => { + setViewType(0) + } + + const isAddRuleDisabled = () => { + if ( + fields.length === 0 || + fields.some(field => field.length === 0) || + operators.some( + (operator, index) => index < fields.length - 1 && !operator + ) + ) { + return true + } + + return ( + JSON.stringify(initialState) === + JSON.stringify({ + comparators, + fields, + operators + }) + ) + } + + const handleAddUndefinedRule = () => { + handleAddRow() + setViewType(1) + } + + return ( + + + + + + + {viewType === 0 ? ( + + + + ) : ( + <> + {fields.map((field, index) => ( + + + + Select Operator + + + + + + Select Field + + + + + + Select Comparator Function + + + + + ))} + + + + + + + + + + + )} + + + ) +} + +export default BlockingContent diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx index 9960f59e5..e39b85a60 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/Deterministic.tsx @@ -43,7 +43,7 @@ const Deterministic = ({ backgroundColor: '#f5f5f5' }} > - + = ({ flexDirection: 'column', gap: 2, justifyContent: 'center', - alignItems: 'center' + alignItems: 'center', + backgroundColor: '#f5f5f5' }} > diff --git a/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts b/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts index 709f02945..deaee014a 100644 --- a/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts +++ b/JeMPI_Apps/JeMPI_UI/src/services/ApiClient.ts @@ -89,7 +89,7 @@ export class ApiClient { } async fetchConfiguration() { - const { data } = await this.client.get(ROUTES.GET_CONFIGURATION) + const { data } = await moxios.get(ROUTES.GET_CONFIGURATION) return data } From a11f189c253f1446b1bcbd3980ea850332cc0013 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Mon, 10 Jun 2024 09:56:53 +0200 Subject: [PATCH 113/183] refactored select operator --- .../pages/settings/deterministic/DeterministicContent.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx index b00c52166..1903c1dd2 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -280,10 +280,10 @@ const DeterministicContent = ({ handleOperatorChange(index, event)} - disabled={index === 0} + onChange={event => handleOperatorChange(index - 1, event)} + disabled={index === 0} > {Object.values(Operator).map(op => ( @@ -278,9 +247,7 @@ const BlockingContent = ({ demographicData = [], hasUndefinedRule, linkingRules - - Select Field - + Select Field handleComparatorChange(index, event)} > {options.map(option => ( - + {option.label} ))} @@ -325,49 +290,23 @@ const BlockingContent = ({ demographicData = [], hasUndefinedRule, linkingRules ))} - - + + - - - )} - - - ) -} + + ); +}; -export default BlockingContent +export default BlockingContent; diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx index 1903c1dd2..b2ae4e8e2 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/DeterministicContent.tsx @@ -283,7 +283,7 @@ const DeterministicContent = ({ value={index === 0 ? '' : operators[index - 1] || ''} label="Select Operator" onChange={event => handleOperatorChange(index - 1, event)} - disabled={index === 0} + disabled={index === 0} > {Object.values(Operator).map(op => ( @@ -347,7 +347,11 @@ const DeterministicContent = ({ marginTop: '20px' }} > - + From 1fd3bffafd8a9f24c2a5b45b654bfacc50a554df Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Tue, 11 Jun 2024 17:24:43 +0200 Subject: [PATCH 116/183] added configuration to local storage --- .../JeMPI_UI/src/hooks/useUIConfiguration.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/hooks/useUIConfiguration.tsx b/JeMPI_Apps/JeMPI_UI/src/hooks/useUIConfiguration.tsx index fea95b1d0..a7d3c6bea 100644 --- a/JeMPI_Apps/JeMPI_UI/src/hooks/useUIConfiguration.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/hooks/useUIConfiguration.tsx @@ -38,13 +38,25 @@ export const ConfigurationProvider = ({ refetchOnWindowFocus: false }) - const [configuration, setConfiguration] = useState(null) + const [configuration, setConfiguration] = useState( + () => { + const savedConfig = localStorage.getItem('configuration') + return savedConfig ? JSON.parse(savedConfig) : null + } + ) useEffect(() => { - if (data) { + if (data && !configuration) { setConfiguration(data) + localStorage.setItem('configuration', JSON.stringify(data)) } - }, [data]) + }, [data, configuration]) + + useEffect(() => { + if (configuration) { + localStorage.setItem('configuration', JSON.stringify(configuration)) + } + }, [configuration]) if (isLoading) { return @@ -54,10 +66,6 @@ export const ConfigurationProvider = ({ return
Error: {(error as AxiosError).message}
} - if (!configuration) { - return
Loading...
- } - return ( {children} From 635879a96a3932460e87bfed2d5ac1a27bd2b363 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Tue, 11 Jun 2024 17:25:09 +0200 Subject: [PATCH 117/183] refactored get configuration --- JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx index 66f559d2b..290ab4944 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/Settings.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import { Grid, Tab, Tabs, Typography } from '@mui/material' import { Box } from '@mui/system' import { SyntheticEvent } from 'react' @@ -9,7 +9,6 @@ import Deterministic from './deterministic/Deterministic' import Blocking from './blocking/Blocking' import GoldenRecordLists from './goldenRecordLists/GoldenRecordLists' import InteractiveNode from './interactiveNode/InteractiveNode' -import { useConfiguration } from 'hooks/useUIConfiguration' import { CustomTabPanel, a11yProps } from './deterministic/BasicTabs' import { Configuration } from 'types/Configuration' @@ -17,15 +16,15 @@ import { generateId } from 'utils/helpers' const Settings = () => { const [value, setValue] = useState(0) - const { configuration: data } = useConfiguration() - + const storeddata = localStorage.getItem('configuration') const handleChange = (event: SyntheticEvent, newValue: number) => { setValue(newValue) } - const configurationData = useMemo(() => { - return data ? generateId(data) : ({} as Configuration) - }, [data]) + return storeddata + ? generateId(JSON.parse(storeddata)) + : ({} as Configuration) + }, [storeddata]) return ( From 3bc10fbb5a63eeb74d77ebef453e92074f2442fd Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Tue, 11 Jun 2024 17:26:11 +0200 Subject: [PATCH 118/183] added update configuration function --- .../src/pages/settings/blocking/Blocking.tsx | 10 +- .../settings/blocking/BlockingContent.tsx | 607 +++++++++++------- .../settings/deterministic/SourceView.tsx | 1 + 3 files changed, 370 insertions(+), 248 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/Blocking.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/Blocking.tsx index c68d01074..3069ac8f0 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/Blocking.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/Blocking.tsx @@ -1,6 +1,6 @@ import { Card, CardContent, Tab, Tabs } from '@mui/material' import { Box } from '@mui/system' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { Field, Rule } from 'types/Configuration' import { a11yProps, CustomTabPanel } from '../deterministic/BasicTabs' import BlockingContent from './BlockingContent' @@ -9,7 +9,7 @@ interface BlockingProps { demographicData: Field[] rules: { link?: { - deterministic?: Rule[] + probabilistic?: Rule[] } validate?: { probabilistic?: Rule[] @@ -27,7 +27,7 @@ const Blocking = ({ demographicData = [], rules = {} }: BlockingProps) => { } const matchNotificationRules = rules.matchNotification?.probabilistic ?? [] - const linkingRules = rules.link?.deterministic ?? [] + const linkingRules = rules.link?.probabilistic ?? [] return ( @@ -51,8 +51,8 @@ const Blocking = ({ demographicData = [], rules = {} }: BlockingProps) => { diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx index 2d053ceb5..5ee2cc791 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from 'react'; -import { AddOutlined } from '@mui/icons-material'; +import React, { useEffect, useState } from 'react' +import { AddOutlined, DeleteOutline } from '@mui/icons-material' import { Button, FormControl, @@ -8,305 +8,426 @@ import { IconButton, Box, MenuItem, - SelectChangeEvent, -} from '@mui/material'; -import SourceView, { RowData } from '../deterministic/SourceView'; -import { Field, Rule } from 'types/Configuration'; -import { Operator, options } from '../deterministic/DeterministicContent'; -import { transformFieldName } from 'utils/helpers'; + SelectChangeEvent +} from '@mui/material' +import SourceView, { RowData } from '../deterministic/SourceView' +import { Configuration, Field, Rule } from 'types/Configuration' +import { Operator, options } from '../deterministic/DeterministicContent' +import { transformFieldName } from 'utils/helpers' +import { useConfiguration } from 'hooks/useUIConfiguration' interface BlockingContentProps { - demographicData: Field[]; - hasUndefinedRule: boolean; + demographicData: Field[] + hasUndefinedRule: boolean linkingRules: { - validate?: { deterministic?: Rule[] }; - matchNotification?: { - probabilistic?: Rule[]; - }; - }; + link?: { probabilistic?: Rule[] } + matchNotification?: { probabilistic?: Rule[] } + } } -const transformRulesToRowData = (rules: { probabilistic: Rule[] }): RowData[] => { - return rules.probabilistic.map((rule: any, index: any) => ({ +const transformRulesToRowData = (rules: { + probabilistic: Rule[] +}): RowData[] => { + return rules.probabilistic.map((rule, index) => ({ id: index, ruleNumber: index + 1, - ruleText: rule.text, - })); -}; - -const BlockingContent = ({ demographicData = [], hasUndefinedRule, linkingRules }: BlockingContentProps) => { - const [viewType, setViewType] = useState(0); + ruleText: rule.text + })) +} - const probabilisticRows = linkingRules.matchNotification?.probabilistic ?? []; - const [comparators, setComparators] = useState([]); - const [fields, setFields] = useState([]); - const [operators, setOperators] = useState([]); - const [rules, setRules] = useState([]); +const BlockingContent = ({ + demographicData = [], + hasUndefinedRule, + linkingRules +}: BlockingContentProps) => { + const { configuration, setConfiguration } = useConfiguration() + const probabilisticRows = + linkingRules.matchNotification?.probabilistic ?? + linkingRules.link?.probabilistic ?? + [] + const [comparators, setComparators] = useState([]) + const [fields, setFields] = useState([]) + const [operators, setOperators] = useState([]) + const [rules, setRules] = useState([]) + const [editIndex, setEditIndex] = useState(null) // Track the index of the rule being edited const [initialState, setInitialState] = useState({ comparators: [] as number[], fields: [] as string[], - operators: [] as Operator[], - }); + operators: [] as Operator[] + }) + const [hasChanges, setHasChanges] = useState(false) // Track if there are changes useEffect(() => { - const savedComparators = localStorage.getItem('comparators'); - const savedFields = localStorage.getItem('fields'); - const savedOperators = localStorage.getItem('operators'); - const savedRules = localStorage.getItem('rules'); - - if (savedComparators || savedFields || savedOperators || savedRules) { - try { - const parsedRules = savedRules ? JSON.parse(savedRules) : []; - setRules(parsedRules); - const parsedComparators = savedComparators ? JSON.parse(savedComparators) : []; - const parsedFields = savedFields ? JSON.parse(savedFields) : []; - const parsedOperators = savedOperators ? JSON.parse(savedOperators) : []; - - setComparators(parsedComparators); - setFields(parsedFields); - setOperators(parsedOperators); - setInitialState({ - comparators: parsedComparators, - fields: parsedFields, - operators: parsedOperators, - }); - } catch (e) { - console.error('Error parsing saved data from localStorage:', e); - } + console.log('linking rules', linkingRules) + if (configuration) { + const initialRules = + linkingRules.matchNotification?.probabilistic ?? + linkingRules.link?.probabilistic ?? + [] + setRules(initialRules) } - }, []); + }, [configuration, linkingRules]) - useEffect(() => { - localStorage.setItem('comparators', JSON.stringify(comparators)); - }, [comparators]); + const handleComparatorChange = ( + index: number, + event: SelectChangeEvent + ) => { + const newComparators = [...comparators] + newComparators[index] = event.target.value as number + setComparators(newComparators) + setHasChanges(true) + } - useEffect(() => { - localStorage.setItem('fields', JSON.stringify(fields)); - }, [fields]); + const handleFieldChange = ( + index: number, + event: SelectChangeEvent + ) => { + const newFields = [...fields] + newFields[index] = event.target.value as string + setFields(newFields) + setHasChanges(true) + } - useEffect(() => { - localStorage.setItem('operators', JSON.stringify(operators)); - }, [operators]); + const handleOperatorChange = ( + index: number, + event: SelectChangeEvent + ) => { + const newOperators = [...operators] + newOperators[index] = event.target.value as Operator + setOperators(newOperators) + setHasChanges(true) + } - useEffect(() => { - localStorage.setItem('rules', JSON.stringify(rules)); - }, [rules]); + const handleUpdateConfiguration = ( + newRules: Rule[], + ruleType: 'matchNotification' | 'link' + ) => { + setConfiguration(prevConfig => { + if (!prevConfig) return prevConfig - const handleComparatorChange = (index: number, event: SelectChangeEvent) => { - const newComparators = [...comparators]; - newComparators[index] = event.target.value as number; - setComparators(newComparators); - }; - - const handleFieldChange = (index: number, event: SelectChangeEvent) => { - const newFields = [...fields]; - newFields[index] = event.target.value as string; - setFields(newFields); - }; + const updatedConfig: Configuration = { + ...prevConfig, + rules: { + ...prevConfig.rules, + [ruleType]: { + ...prevConfig.rules[ruleType], + probabilistic: newRules + } + } + } - const handleOperatorChange = (index: number, event: SelectChangeEvent) => { - const newOperators = [...operators]; - newOperators[index] = event.target.value as Operator; - setOperators(newOperators); - }; + localStorage.setItem('configuration', JSON.stringify(updatedConfig)) + console.log('Updated configuration in local storage', updatedConfig) + return updatedConfig + }) + } - const handleAddRule = () => { - const vars = fields.filter((field, index) => field !== '' && fields.indexOf(field) === index); + const handleAddRule = (ruleType: 'matchNotification' | 'link') => { + const vars = fields.filter( + (field, index) => field !== '' && fields.indexOf(field) === index + ) const text = vars .map((field, index) => { - const operator = index > 0 ? ` ${operators[index - 1].toLowerCase()} ` : ''; - const comparator = comparators[index]; - const comparatorFunction = comparator === 0 ? `eq(${field})` : `match(${field}, ${comparator})`; - return `${operator}${comparatorFunction}`; + const operator = + index > 0 ? ` ${operators[index - 1].toLowerCase()} ` : '' + const comparator = comparators[index] + const comparatorFunction = + comparator === 0 ? `eq(${field})` : `match(${field}, ${comparator})` + return `${operator}${comparatorFunction}` }) - .join(''); - - const rule = { - function: comparators[0], + .join('') + + const newRule: Rule = { vars, - text, - }; - setRules([...rules, rule]); + text + } + + let updatedRules = [...rules] + if (editIndex !== null) { + updatedRules[editIndex] = newRule + setEditIndex(null) + } else { + updatedRules = [...rules, newRule] + } + + handleUpdateConfiguration(updatedRules, ruleType) + setRules(updatedRules) setInitialState({ comparators: [...comparators], fields: [...fields], - operators: [...operators], - }); - }; - + operators: [...operators] + }) + setHasChanges(false) + } const handleRowEdit = (row: RowData) => { - const regex = /(?:eq|match)\(([^),]+)(?:,\s*(\d+))?\)/g; - const operatorsRegex = /\s+(and|or)\s+/g; - const matchedFields: string[] = []; - const matchedComparators: number[] = []; - const matchedOperators: string[] = []; - let match; + const regex = /(?:eq|match)\(([^),]+)(?:,\s*(\d+))?\)/g + const operatorsRegex = /\s+(and|or)\s+/g + const matchedFields: string[] = [] + const matchedComparators: number[] = [] + const matchedOperators: string[] = [] + let match while ((match = regex.exec(row.ruleText)) !== null) { - matchedFields.push(match[1]); - matchedComparators.push(match[2] ? parseInt(match[2], 10) : 0); + matchedFields.push(match[1]) + matchedComparators.push(match[2] ? parseInt(match[2], 10) : 0) } - let operatorMatch; + let operatorMatch while ((operatorMatch = operatorsRegex.exec(row.ruleText)) !== null) { - matchedOperators.push(operatorMatch[1]); + matchedOperators.push(operatorMatch[1]) } - setFields(matchedFields); - setComparators(matchedComparators); - - const operatorLength = Math.max(matchedFields.length - 1, 0); - const filledOperators = matchedOperators.length > 0 ? matchedOperators.slice(0, operatorLength) : []; - setOperators(filledOperators.map(op => op === 'or' ? Operator.OR : Operator.AND)); - setViewType(1); -}; + setFields(matchedFields) + setComparators(matchedComparators) + const operatorLength = Math.max(matchedFields.length - 1, 0) + const filledOperators = + matchedOperators.length > 0 + ? matchedOperators.slice(0, operatorLength) + : [] + setOperators( + filledOperators.map(op => (op === 'or' ? Operator.OR : Operator.AND)) + ) + setEditIndex(row.id) + setHasChanges(false) + } const handleAddRow = () => { - setComparators([...comparators, 0]); - setFields([...fields, '']); - setOperators([...operators, Operator.AND]); - }; + setComparators([...comparators, 0]) + setFields([...fields, '']) + setOperators([...operators, Operator.AND]) + setHasChanges(true) + } const handleClose = () => { - setViewType(0); - }; + setEditIndex(null) + setFields([]) + setComparators([]) + setOperators([]) + setHasChanges(false) + } const isAddRuleDisabled = () => { - if (fields.length === 0 || fields.some(field => field.length === 0) || operators.some((operator, index) => index < fields.length - 1 && !operator)) { - return true; + if ( + fields.length === 0 || + fields.some(field => field.length === 0) || + operators.some( + (operator, index) => index < fields.length - 1 && !operator + ) + ) { + return true } - return JSON.stringify(initialState) === JSON.stringify({ comparators, fields, operators }); - }; + return !hasChanges + } const handleAddUndefinedRule = () => { - handleAddRow(); - setViewType(1); - }; + handleAddRow() + setEditIndex(null) + } + + const handleDeleteRow = (index: number) => { + const newFields = fields.filter((_, i) => i !== index) + const newComparators = comparators.filter((_, i) => i !== index) + const newOperators = operators.filter((_, i) => i !== index) + + const updatedRules = rules.filter((_, i) => i !== index) + + setFields(newFields) + setComparators(newComparators) + setOperators(newOperators) + setRules(updatedRules) + setHasChanges(true) + + const ruleType = linkingRules.matchNotification?.probabilistic + ? 'matchNotification' + : 'link' + handleUpdateConfiguration(updatedRules, ruleType) + } return ( - - - + + + + + {editIndex === null ? ( + + - {viewType === 0 ? ( + ) : ( + <> + {fields.map((field, index) => ( + + + + Select Operator + + + + + + Select Field + + + + + + Select Comparator + + + + handleDeleteRow(index)} + sx={{ alignSelf: 'center' }} + > + + + + ))} - + + + - ) : ( - <> - {fields.map((field, index) => ( - - - Select Operator - - - - Select Field - - - - Select Comparator - - - - ))} - - - - - - - - - - - )} - - ); -}; + + + + + + )} + + ) +} -export default BlockingContent; +export default BlockingContent diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx index 853b5dcbe..d51962d89 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/deterministic/SourceView.tsx @@ -38,6 +38,7 @@ const SourceView: React.FC = ({ useEffect(() => { setRows(data) + console.log(data) }, [data]) const handleEditClick = (id: GridRowId) => () => { From e80a6c47152af27b0aae4882d5d8d3bd67b49264 Mon Sep 17 00:00:00 2001 From: Matthew Erispe Date: Wed, 12 Jun 2024 12:15:40 +0200 Subject: [PATCH 119/183] update all api config for keycloak standalone --- .../src/main/java/org/jembi/jempi/AppConfig.java | 4 ++++ .../src/main/java/org/jembi/jempi/api/APIKC.java | 8 ++++---- devops/linux/docker/conf/env/create-env-linux-low-1.sh | 7 ++++++- devops/linux/docker/conf/stack/docker-stack-high-0.yml | 3 +++ devops/linux/docker/conf/stack/docker-stack-high-1.yml | 3 +++ devops/linux/docker/conf/stack/docker-stack-low-0.yml | 3 +++ devops/linux/docker/conf/stack/docker-stack-low-1.yml | 3 +++ .../docker/data-config/config-reference-link-dp-api.json | 8 ++++---- 8 files changed, 30 insertions(+), 9 deletions(-) diff --git a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/AppConfig.java b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/AppConfig.java index 79d621c49..496442ba1 100644 --- a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/AppConfig.java +++ b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/AppConfig.java @@ -47,6 +47,10 @@ public final class AppConfig { public static final Integer CONTROLLER_HTTP_PORT = CONFIG.getInt("CONTROLLER_HTTP_PORT"); public static final String SESSION_SECRET = CONFIG.getString("JEMPI_SESSION_SECRET"); public static final Level GET_LOG_LEVEL = Level.toLevel(CONFIG.getString("LOG4J2_LEVEL")); + public static final String SYSTEM_CONFIG_DIR = CONFIG.getString("SYSTEM_CONFIG_DIR"); + public static final String API_CONFIG_REFERENCE_FILENAME = CONFIG.getString("API_CONFIG_REFERENCE_FILENAME"); + public static final String API_CONFIG_MASTER_FILENAME = CONFIG.getString("API_CONFIG_MASTER_FILENAME"); + public static final String API_FIELDS_CONFIG_FILENAME = CONFIG.getString("API_FIELDS_CONFIG_FILENAME"); private AppConfig() { } diff --git a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/APIKC.java b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/APIKC.java index d4bbd85a2..2f6af9ff8 100644 --- a/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/APIKC.java +++ b/JeMPI_Apps/JeMPI_API_KC/src/main/java/org/jembi/jempi/api/APIKC.java @@ -42,10 +42,10 @@ public Behavior create() { AppConfig.POSTGRESQL_AUDIT_DB, AppConfig.KAFKA_BOOTSTRAP_SERVERS, "CLIENT_ID_API_KC-" + UUID.randomUUID(), - null, - null, - null, - null), + AppConfig.SYSTEM_CONFIG_DIR, + AppConfig.API_CONFIG_REFERENCE_FILENAME, + AppConfig.API_CONFIG_MASTER_FILENAME, + AppConfig.API_FIELDS_CONFIG_FILENAME), "BackEnd"); context.watch(backEnd); final DispatcherSelector selector = DispatcherSelector.fromConfig("akka.actor.default-dispatcher"); diff --git a/devops/linux/docker/conf/env/create-env-linux-low-1.sh b/devops/linux/docker/conf/env/create-env-linux-low-1.sh index 57b46920c..6e4ca458b 100755 --- a/devops/linux/docker/conf/env/create-env-linux-low-1.sh +++ b/devops/linux/docker/conf/env/create-env-linux-low-1.sh @@ -58,9 +58,14 @@ export JEMPI_SESSION_DOMAIN_NAME="localhost" # NODE_ENV production || development export NODE_ENV="production" export REACT_APP_JEMPI_BASE_API_HOST=http://${NODE1_IP} + +#enable use of keycloak +#export REACT_APP_JEMPI_BASE_API_PORT=50001 +#export REACT_APP_ENABLE_SSO="true" + export REACT_APP_JEMPI_BASE_API_PORT=50000 -export REACT_APP_MOCK_BACKEND="false" export REACT_APP_ENABLE_SSO="false" +export REACT_APP_MOCK_BACKEND="false" export KC_FRONTEND_URL=http://${NODE1_IP}:8080 # ram limit for linker diff --git a/devops/linux/docker/conf/stack/docker-stack-high-0.yml b/devops/linux/docker/conf/stack/docker-stack-high-0.yml index ee73dccde..6fb4e0735 100644 --- a/devops/linux/docker/conf/stack/docker-stack-high-0.yml +++ b/devops/linux/docker/conf/stack/docker-stack-high-0.yml @@ -698,6 +698,9 @@ services: LINKER_HTTP_PORT: ${LINKER_HTTP_PORT} CONTROLLER_IP: controller CONTROLLER_HTTP_PORT: ${CONTROLLER_HTTP_PORT} + SYSTEM_CONFIG_DIR: ${SYSTEM_CONFIG_DIR} + API_CONFIG_REFERENCE_FILENAME: ${API_CONFIG_REFERENCE_FILENAME} + API_CONFIG_MASTER_FILENAME: ${API_CONFIG_MASTER_FILENAME} API_FIELDS_CONFIG_FILENAME: ${API_FIELDS_CONFIG_FILENAME} networks: - frontend diff --git a/devops/linux/docker/conf/stack/docker-stack-high-1.yml b/devops/linux/docker/conf/stack/docker-stack-high-1.yml index 0df702ebb..c7a4ea053 100644 --- a/devops/linux/docker/conf/stack/docker-stack-high-1.yml +++ b/devops/linux/docker/conf/stack/docker-stack-high-1.yml @@ -698,6 +698,9 @@ services: LINKER_HTTP_PORT: ${LINKER_HTTP_PORT} CONTROLLER_IP: controller CONTROLLER_HTTP_PORT: ${CONTROLLER_HTTP_PORT} + SYSTEM_CONFIG_DIR: ${SYSTEM_CONFIG_DIR} + API_CONFIG_REFERENCE_FILENAME: ${API_CONFIG_REFERENCE_FILENAME} + API_CONFIG_MASTER_FILENAME: ${API_CONFIG_MASTER_FILENAME} API_FIELDS_CONFIG_FILENAME: ${API_FIELDS_CONFIG_FILENAME} networks: - frontend diff --git a/devops/linux/docker/conf/stack/docker-stack-low-0.yml b/devops/linux/docker/conf/stack/docker-stack-low-0.yml index 0a584f72b..0ce88ef0f 100644 --- a/devops/linux/docker/conf/stack/docker-stack-low-0.yml +++ b/devops/linux/docker/conf/stack/docker-stack-low-0.yml @@ -510,6 +510,9 @@ services: LINKER_HTTP_PORT: ${LINKER_HTTP_PORT} CONTROLLER_IP: controller CONTROLLER_HTTP_PORT: ${CONTROLLER_HTTP_PORT} + SYSTEM_CONFIG_DIR: ${SYSTEM_CONFIG_DIR} + API_CONFIG_REFERENCE_FILENAME: ${API_CONFIG_REFERENCE_FILENAME} + API_CONFIG_MASTER_FILENAME: ${API_CONFIG_MASTER_FILENAME} API_FIELDS_CONFIG_FILENAME: ${API_FIELDS_CONFIG_FILENAME} networks: - frontend diff --git a/devops/linux/docker/conf/stack/docker-stack-low-1.yml b/devops/linux/docker/conf/stack/docker-stack-low-1.yml index 8da0584c3..8d80696ef 100644 --- a/devops/linux/docker/conf/stack/docker-stack-low-1.yml +++ b/devops/linux/docker/conf/stack/docker-stack-low-1.yml @@ -510,6 +510,9 @@ services: LINKER_HTTP_PORT: ${LINKER_HTTP_PORT} CONTROLLER_IP: controller CONTROLLER_HTTP_PORT: ${CONTROLLER_HTTP_PORT} + SYSTEM_CONFIG_DIR: ${SYSTEM_CONFIG_DIR} + API_CONFIG_REFERENCE_FILENAME: ${API_CONFIG_REFERENCE_FILENAME} + API_CONFIG_MASTER_FILENAME: ${API_CONFIG_MASTER_FILENAME} API_FIELDS_CONFIG_FILENAME: ${API_FIELDS_CONFIG_FILENAME} networks: - frontend diff --git a/devops/linux/docker/data-config/config-reference-link-dp-api.json b/devops/linux/docker/data-config/config-reference-link-dp-api.json index dd50fe5cf..89d3a75b1 100644 --- a/devops/linux/docker/data-config/config-reference-link-dp-api.json +++ b/devops/linux/docker/data-config/config-reference-link-dp-api.json @@ -18,7 +18,7 @@ "accessLevel": [] }, { - "fieldName": "given_name", + "fieldName": "givenName", "fieldType": "String", "fieldLabel": "First Name", "groups": [ @@ -45,7 +45,7 @@ "accessLevel": [] }, { - "fieldName": "family_name", + "fieldName": "familyName", "fieldType": "String", "fieldLabel": "Last Name", "groups": [ @@ -147,7 +147,7 @@ } }, { - "fieldName": "phone_number", + "fieldName": "phoneNumber", "fieldType": "String", "fieldLabel": "Phone No", "groups": [ @@ -166,7 +166,7 @@ "accessLevel": [] }, { - "fieldName": "national_id", + "fieldName": "nationalId", "fieldType": "String", "fieldLabel": "National ID", "groups": [ From 6c44c6d1e30c24c7648315db07849117d1dbc320 Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Wed, 12 Jun 2024 14:28:39 +0200 Subject: [PATCH 120/183] added blocking unit test --- .../test/settings/BlockingContent.test.tsx | 161 ++++++++++++++++++ .../settings/DeterministicContent.test.tsx | 21 +-- 2 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 JeMPI_Apps/JeMPI_UI/src/test/settings/BlockingContent.test.tsx diff --git a/JeMPI_Apps/JeMPI_UI/src/test/settings/BlockingContent.test.tsx b/JeMPI_Apps/JeMPI_UI/src/test/settings/BlockingContent.test.tsx new file mode 100644 index 000000000..d1da27db6 --- /dev/null +++ b/JeMPI_Apps/JeMPI_UI/src/test/settings/BlockingContent.test.tsx @@ -0,0 +1,161 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import '@testing-library/jest-dom/extend-expect' +import { BrowserRouter } from 'react-router-dom' +import BlockingContent from 'pages/settings/blocking/BlockingContent' +import mockData from 'services/mockData' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { ConfigProvider } from 'hooks/useConfig' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: {} + } +}) + +const demographicData: any = mockData.configuration.demographicFields +const linkingRules: any = mockData.configuration.rules + +describe('BlockingContent Component', () => { + it('renders correctly', () => { + const { container } = render( + + + + + + + + ) + expect(container).toMatchSnapshot() + }) + + it('toggles between Source View and Design View', () => { + render( + + + + + + + + ) + + const sourceViewButton = document.getElementById('source-view-button') + const designViewButton = document.getElementById('design-view-button') + + if (sourceViewButton && designViewButton) { + fireEvent.click(designViewButton) + expect(screen.getByLabelText('Select Field')).toBeInTheDocument() + } + }) + + it('adds a new rule row', () => { + render( + + + + + + + + ) + + const addRuleButton = document.getElementById('add-rule-button') + if (addRuleButton) { + fireEvent.click(addRuleButton) + expect(screen.getByText('Rule 1')).toBeInTheDocument() + } + }) + + it('calls handleAddRule when Add Rule button is clicked', () => { + const handleAddRuleMock = jest.fn() + render( + + + + + + + + ) + + const addRuleButton = document.getElementById('add-rule-button') + if (addRuleButton) { + fireEvent.click(addRuleButton) + expect(handleAddRuleMock).toHaveBeenCalledTimes(1) + } + }) + + it('calls handleDeleteRow when Delete button is clicked', () => { + const handleDeleteRowMock = jest.fn() + render( + + + + + + + + ) + const deleteButton = document.getElementById('delete-button') + if (deleteButton) { + fireEvent.click(deleteButton) + expect(handleDeleteRowMock).toHaveBeenCalledTimes(1) + } + }) + + it('calls handleRowEdit when a row is edited', () => { + const handleRowEditMock = jest.fn() + render( + + + + + + + + ) + const editButton = document.getElementById('edit-button') as HTMLElement + if (editButton) { + fireEvent.click(editButton) + expect(handleRowEditMock).toHaveBeenCalledTimes(1) + } + }) +}) diff --git a/JeMPI_Apps/JeMPI_UI/src/test/settings/DeterministicContent.test.tsx b/JeMPI_Apps/JeMPI_UI/src/test/settings/DeterministicContent.test.tsx index ab3f93298..e4da5806c 100644 --- a/JeMPI_Apps/JeMPI_UI/src/test/settings/DeterministicContent.test.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/test/settings/DeterministicContent.test.tsx @@ -1,28 +1,13 @@ -import React from 'react' import { render, fireEvent, waitFor } from '@testing-library/react' import DeterministicContent from 'pages/settings/deterministic/DeterministicContent' -import { Field } from 'types/Configuration' import userEvent from '@testing-library/user-event' import '@testing-library/jest-dom' +import mockData from 'services/mockData' describe('DeterministicContent', () => { - const demographicData: Field[] = [ - { fieldName: 'national_id', fieldType: 'String' }, - { fieldName: 'given_name', fieldType: 'String' }, - { fieldName: 'family_name', fieldType: 'String' } - ] + const demographicData: any = mockData.configuration.demographicFields - const linkingRules = { - link: { - deterministic: [ - { vars: ['national_id'], text: 'eq(national_id)' }, - { - vars: ['given_name', 'family_name'], - text: 'eq(given_name) and eq(family_name)' - } - ] - } - } + const linkingRules = mockData.configuration.rules it('renders correctly', () => { const { container } = render( From 55ac2e38f3baead58fa66d11b2a810a4f84339ff Mon Sep 17 00:00:00 2001 From: NyashaMuusha Date: Wed, 12 Jun 2024 14:28:55 +0200 Subject: [PATCH 121/183] refactored blocking content --- .../settings/blocking/BlockingContent.tsx | 323 ++++++++++-------- 1 file changed, 187 insertions(+), 136 deletions(-) diff --git a/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx index 5ee2cc791..64b1e38ae 100644 --- a/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx +++ b/JeMPI_Apps/JeMPI_UI/src/pages/settings/blocking/BlockingContent.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useReducer, useCallback, useEffect } from 'react' import { AddOutlined, DeleteOutline } from '@mui/icons-material' import { Button, @@ -23,6 +23,9 @@ interface BlockingContentProps { link?: { probabilistic?: Rule[] } matchNotification?: { probabilistic?: Rule[] } } + handleAddRule?: () => void + handleDeleteRow?: () => void + handleRowEdit?: () => void } const transformRulesToRowData = (rules: { @@ -35,132 +38,175 @@ const transformRulesToRowData = (rules: { })) } +const initialState = { + comparators: [] as number[], + fields: [] as string[], + operators: [] as Operator[], + rules: [] as Rule[], + editIndex: null as number | null, + viewType: 0, + hasChanges: false +} + +type State = typeof initialState +type Action = + | { type: 'SET_FIELDS'; payload: string[] } + | { type: 'SET_COMPARATORS'; payload: number[] } + | { type: 'SET_OPERATORS'; payload: Operator[] } + | { type: 'SET_RULES'; payload: Rule[] } + | { type: 'SET_EDIT_INDEX'; payload: number | null } + | { type: 'SET_VIEW_TYPE'; payload: number } + | { type: 'SET_HAS_CHANGES'; payload: boolean } + | { type: 'RESET' } + +const reducer = (state: State, action: Action): State => { + switch (action.type) { + case 'SET_FIELDS': + return { ...state, fields: action.payload } + case 'SET_COMPARATORS': + return { ...state, comparators: action.payload } + case 'SET_OPERATORS': + return { ...state, operators: action.payload } + case 'SET_RULES': + return { ...state, rules: action.payload } + case 'SET_EDIT_INDEX': + return { ...state, editIndex: action.payload } + case 'SET_VIEW_TYPE': + return { ...state, viewType: action.payload } + case 'SET_HAS_CHANGES': + return { ...state, hasChanges: action.payload } + case 'RESET': + return initialState + default: + return state + } +} + const BlockingContent = ({ demographicData = [], hasUndefinedRule, linkingRules }: BlockingContentProps) => { const { configuration, setConfiguration } = useConfiguration() - const probabilisticRows = - linkingRules.matchNotification?.probabilistic ?? - linkingRules.link?.probabilistic ?? + const [state, dispatch] = useReducer(reducer, initialState) + const [probabilisticRows, setProbabilisticRows] = React.useState( [] - const [comparators, setComparators] = useState([]) - const [fields, setFields] = useState([]) - const [operators, setOperators] = useState([]) - const [rules, setRules] = useState([]) - const [editIndex, setEditIndex] = useState(null) // Track the index of the rule being edited - const [initialState, setInitialState] = useState({ - comparators: [] as number[], - fields: [] as string[], - operators: [] as Operator[] - }) - const [hasChanges, setHasChanges] = useState(false) // Track if there are changes + ) useEffect(() => { - console.log('linking rules', linkingRules) if (configuration) { const initialRules = linkingRules.matchNotification?.probabilistic ?? linkingRules.link?.probabilistic ?? [] - setRules(initialRules) + dispatch({ type: 'SET_RULES', payload: initialRules }) + setProbabilisticRows( + transformRulesToRowData({ probabilistic: initialRules }) + ) } }, [configuration, linkingRules]) - const handleComparatorChange = ( - index: number, - event: SelectChangeEvent - ) => { - const newComparators = [...comparators] - newComparators[index] = event.target.value as number - setComparators(newComparators) - setHasChanges(true) - } + const handleUpdateConfiguration = useCallback( + (newRules: Rule[], ruleType: 'matchNotification' | 'link') => { + setConfiguration(prevConfig => { + if (!prevConfig) return prevConfig + + const updatedConfig: Configuration = { + ...prevConfig, + rules: { + ...prevConfig.rules, + [ruleType]: { + ...prevConfig.rules[ruleType], + probabilistic: newRules + } + } + } + + localStorage.setItem('configuration', JSON.stringify(updatedConfig)) + return updatedConfig + }) + + setProbabilisticRows(transformRulesToRowData({ probabilistic: newRules })) + }, + [setConfiguration] + ) + + const handleAddRule = useCallback( + (ruleType: 'matchNotification' | 'link') => { + const vars = state.fields.filter( + (field, index) => field !== '' && state.fields.indexOf(field) === index + ) + const text = vars + .map((field, index) => { + const operator = + index > 0 ? ` ${state.operators[index - 1].toLowerCase()} ` : '' + const comparator = state.comparators[index] + const comparatorFunction = + comparator === 0 ? `eq(${field})` : `match(${field}, ${comparator})` + return `${operator}${comparatorFunction}` + }) + .join('') + + const newRule: Rule = { + vars, + text + } + + let updatedRules = [...state.rules] + if (state.editIndex !== null) { + updatedRules[state.editIndex] = newRule + dispatch({ type: 'SET_EDIT_INDEX', payload: null }) + } else { + updatedRules = [...state.rules, newRule] + } + + handleUpdateConfiguration(updatedRules, ruleType) + dispatch({ type: 'SET_RULES', payload: updatedRules }) + dispatch({ type: 'SET_HAS_CHANGES', payload: false }) + dispatch({ type: 'SET_VIEW_TYPE', payload: 0 }) + }, + [ + state.fields, + state.operators, + state.comparators, + state.rules, + state.editIndex, + handleUpdateConfiguration + ] + ) const handleFieldChange = ( index: number, event: SelectChangeEvent ) => { - const newFields = [...fields] + const newFields = [...state.fields] newFields[index] = event.target.value as string - setFields(newFields) - setHasChanges(true) + dispatch({ type: 'SET_FIELDS', payload: newFields }) + dispatch({ type: 'SET_HAS_CHANGES', payload: true }) } - const handleOperatorChange = ( + const handleComparatorChange = ( index: number, - event: SelectChangeEvent + event: SelectChangeEvent ) => { - const newOperators = [...operators] - newOperators[index] = event.target.value as Operator - setOperators(newOperators) - setHasChanges(true) + const newComparators = [...state.comparators] + newComparators[index] = event.target.value as number + dispatch({ type: 'SET_COMPARATORS', payload: newComparators }) + dispatch({ type: 'SET_HAS_CHANGES', payload: true }) } - const handleUpdateConfiguration = ( - newRules: Rule[], - ruleType: 'matchNotification' | 'link' + const handleOperatorChange = ( + index: number, + event: SelectChangeEvent ) => { - setConfiguration(prevConfig => { - if (!prevConfig) return prevConfig - - const updatedConfig: Configuration = { - ...prevConfig, - rules: { - ...prevConfig.rules, - [ruleType]: { - ...prevConfig.rules[ruleType], - probabilistic: newRules - } - } - } - - localStorage.setItem('configuration', JSON.stringify(updatedConfig)) - console.log('Updated configuration in local storage', updatedConfig) - return updatedConfig - }) - } - - const handleAddRule = (ruleType: 'matchNotification' | 'link') => { - const vars = fields.filter( - (field, index) => field !== '' && fields.indexOf(field) === index - ) - const text = vars - .map((field, index) => { - const operator = - index > 0 ? ` ${operators[index - 1].toLowerCase()} ` : '' - const comparator = comparators[index] - const comparatorFunction = - comparator === 0 ? `eq(${field})` : `match(${field}, ${comparator})` - return `${operator}${comparatorFunction}` - }) - .join('') - - const newRule: Rule = { - vars, - text - } - - let updatedRules = [...rules] - if (editIndex !== null) { - updatedRules[editIndex] = newRule - setEditIndex(null) - } else { - updatedRules = [...rules, newRule] - } - - handleUpdateConfiguration(updatedRules, ruleType) - setRules(updatedRules) - setInitialState({ - comparators: [...comparators], - fields: [...fields], - operators: [...operators] - }) - setHasChanges(false) + const newOperators = [...state.operators] + newOperators[index] = event.target.value as Operator + dispatch({ type: 'SET_OPERATORS', payload: newOperators }) + dispatch({ type: 'SET_HAS_CHANGES', payload: true }) } const handleRowEdit = (row: RowData) => { + dispatch({ type: 'SET_VIEW_TYPE', payload: 1 }) const regex = /(?:eq|match)\(([^),]+)(?:,\s*(\d+))?\)/g const operatorsRegex = /\s+(and|or)\s+/g const matchedFields: string[] = [] @@ -178,67 +224,68 @@ const BlockingContent = ({ matchedOperators.push(operatorMatch[1]) } - setFields(matchedFields) - setComparators(matchedComparators) + dispatch({ type: 'SET_FIELDS', payload: matchedFields }) + dispatch({ type: 'SET_COMPARATORS', payload: matchedComparators }) const operatorLength = Math.max(matchedFields.length - 1, 0) const filledOperators = matchedOperators.length > 0 ? matchedOperators.slice(0, operatorLength) : [] - setOperators( - filledOperators.map(op => (op === 'or' ? Operator.OR : Operator.AND)) - ) - setEditIndex(row.id) - setHasChanges(false) + dispatch({ + type: 'SET_OPERATORS', + payload: filledOperators.map(op => + op === 'or' ? Operator.OR : Operator.AND + ) + }) + dispatch({ type: 'SET_EDIT_INDEX', payload: row.id }) + dispatch({ type: 'SET_HAS_CHANGES', payload: false }) } const handleAddRow = () => { - setComparators([...comparators, 0]) - setFields([...fields, '']) - setOperators([...operators, Operator.AND]) - setHasChanges(true) + dispatch({ type: 'SET_COMPARATORS', payload: [...state.comparators, 0] }) + dispatch({ type: 'SET_FIELDS', payload: [...state.fields, ''] }) + dispatch({ + type: 'SET_OPERATORS', + payload: [...state.operators, Operator.AND] + }) + dispatch({ type: 'SET_HAS_CHANGES', payload: true }) } const handleClose = () => { - setEditIndex(null) - setFields([]) - setComparators([]) - setOperators([]) - setHasChanges(false) + dispatch({ type: 'RESET' }) + dispatch({ type: 'SET_VIEW_TYPE', payload: 0 }) } const isAddRuleDisabled = () => { if ( - fields.length === 0 || - fields.some(field => field.length === 0) || - operators.some( - (operator, index) => index < fields.length - 1 && !operator + state.fields.length === 0 || + state.fields.some(field => field.length === 0) || + state.operators.some( + (operator, index) => index < state.fields.length - 1 && !operator ) ) { return true } - return !hasChanges + return !state.hasChanges } const handleAddUndefinedRule = () => { handleAddRow() - setEditIndex(null) + dispatch({ type: 'SET_VIEW_TYPE', payload: 1 }) } const handleDeleteRow = (index: number) => { - const newFields = fields.filter((_, i) => i !== index) - const newComparators = comparators.filter((_, i) => i !== index) - const newOperators = operators.filter((_, i) => i !== index) - - const updatedRules = rules.filter((_, i) => i !== index) - - setFields(newFields) - setComparators(newComparators) - setOperators(newOperators) - setRules(updatedRules) - setHasChanges(true) + const newFields = state.fields.filter((_, i) => i !== index) + const newComparators = state.comparators.filter((_, i) => i !== index) + const newOperators = state.operators.filter((_, i) => i !== index) + const updatedRules = state.rules.filter((_, i) => i !== index) + dispatch({ type: 'SET_FIELDS', payload: newFields }) + dispatch({ type: 'SET_COMPARATORS', payload: newComparators }) + dispatch({ type: 'SET_OPERATORS', payload: newOperators }) + dispatch({ type: 'SET_RULES', payload: updatedRules }) + dispatch({ type: 'SET_HAS_CHANGES', payload: true }) const ruleType = linkingRules.matchNotification?.probabilistic ? 'matchNotification' @@ -259,22 +306,24 @@ const BlockingContent = ({ >
- {editIndex === null ? ( + {state.viewType !== 1 ? ( ) : ( <> - {fields.map((field, index) => ( + {state.fields.map((field, index) => ( handleOperatorChange(index - 1, event)} disabled={index === 0} @@ -361,7 +410,7 @@ const BlockingContent = ({