Skip to content

Commit

Permalink
Merge pull request #334 from cisagov/feature/add-logic-to=update-miss…
Browse files Browse the repository at this point in the history
…ing-user-state-and-regionId

Add logic to update missing user state and region values
  • Loading branch information
schmelz21 authored Jun 18, 2024
2 parents dc6ad4c + 716158b commit 46bc01a
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 64 deletions.
4 changes: 4 additions & 0 deletions backend/src/api/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,10 @@ export const updateV2 = wrapHandler(async (event) => {
return NotFound;
}

if (body.state) {
body.regionId = REGION_STATE_MAP[body.state];
}

// Update the user
const updatedResp = await User.update(userId, body);

Expand Down
115 changes: 115 additions & 0 deletions frontend/src/components/Register/UpdateUserStateForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { useState } from 'react';
import * as registerFormStyles from './registerFormStyle';
import {
Button,
CircularProgress,
DialogActions,
DialogContent,
DialogTitle,
MenuItem,
Select,
Typography
} from '@mui/material';
import { Save } from '@mui/icons-material';
import { SelectChangeEvent } from '@mui/material/Select';
import { STATE_OPTIONS } from '../../constants/constants';
import { useAuthContext } from 'context';

const StyledDialog = registerFormStyles.StyledDialog;

export interface UpdateStateFormValues {
state: string;
}

export const UpdateStateForm: React.FC<{
open: boolean;
userId: string;
onClose: () => void;
}> = ({ open, userId, onClose }) => {
const defaultValues = () => ({
state: ''
});

const [values, setValues] = useState<UpdateStateFormValues>(defaultValues);
const [errorRequestMessage, setErrorRequestMessage] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(false);
const { apiPut } = useAuthContext();

const handleChange = (event: SelectChangeEvent) => {
setValues((values: any) => ({
...values,
[event.target.name]: event.target.value
}));
};

const onSave = async () => {
setIsLoading(true);
const body = {
state: values.state
};

try {
await apiPut(`/v2/users/${userId}`, {
body
});
setIsLoading(false);
onClose();
} catch (error) {
setErrorRequestMessage(
'Something went wrong updating the state. Please try again.'
);
setIsLoading(false);
}
};

return (
<StyledDialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
<DialogTitle id="form-dialog-title">Update State Information</DialogTitle>
<DialogContent>
{errorRequestMessage && (
<p className="text-error">{errorRequestMessage}</p>
)}
State
<Select
displayEmpty
size="small"
id="state"
value={values.state}
name="state"
onChange={handleChange}
fullWidth
renderValue={
values.state !== ''
? undefined
: () => <Typography color="#bdbdbd">Select your State</Typography>
}
>
{STATE_OPTIONS.map((state: string, index: number) => (
<MenuItem key={index} value={state}>
{state}
</MenuItem>
))}
</Select>
</DialogContent>
<DialogActions>
<Button variant="outlined" onClick={onClose}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={onSave}
startIcon={
isLoading ? (
<CircularProgress color="secondary" size={16} />
) : (
<Save />
)
}
>
Save
</Button>
</DialogActions>
</StyledDialog>
);
};
1 change: 1 addition & 0 deletions frontend/src/components/Register/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './RegisterForm';
export * from './UpdateUserStateForm';
88 changes: 24 additions & 64 deletions frontend/src/pages/Risk/Risk.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React, { useCallback, useState, useEffect } from 'react';
import classes from './Risk.module.scss';
import { Card, CardContent, Typography } from '@mui/material';
import { Card, CardContent, Typography, Paper } from '@mui/material';
import VulnerabilityCard from './VulnerabilityCard';
import TopVulnerablePorts from './TopVulnerablePorts';
import TopVulnerableDomains from './TopVulnerableDomains';
import VulnerabilityPieChart from './VulnerabilityPieChart';
import * as RiskStyles from './style';
// import { delay, getSeverityColor, offsets, severities } from './utils';
import { getSeverityColor, offsets, severities } from './utils';
import { useAuthContext } from 'context';
import { Paper } from '@mui/material';
import { geoCentroid } from 'd3-geo';
import {
ComposableMap,
Expand All @@ -21,9 +19,7 @@ import {
} from 'react-simple-maps';
import { scaleLinear } from 'd3-scale';
import { Vulnerability } from 'types';
// import { jsPDF } from 'jspdf';
// import html2canvas from 'html2canvas';
// import { Button as USWDSButton } from '@trussworks/react-uswds';
import { UpdateStateForm } from 'components/Register';

export interface Point {
id: string;
Expand Down Expand Up @@ -71,7 +67,8 @@ const Risk: React.FC = (props) => {
useAuthContext();

const [stats, setStats] = useState<Stats | undefined>(undefined);
// const [isLoading, setIsLoading] = useState(false);
const [isUpdateStateFormOpen, setIsUpdateStateFormOpen] = useState(false);

const RiskRoot = RiskStyles.RiskRoot;
const { cardRoot, content, contentWrapper, header, panel } =
RiskStyles.classesRisk;
Expand All @@ -95,7 +92,6 @@ const Risk: React.FC = (props) => {
}
});
const max = Math.max(...result.vulnerabilities.byOrg.map((p) => p.value));
// Adjust color scale based on highest count
colorScale = scaleLinear<string>()
.domain([0, Math.log(max)])
.range(['#c7e8ff', '#135787']);
Expand All @@ -108,7 +104,15 @@ const Risk: React.FC = (props) => {
fetchStats();
}, [fetchStats]);

// TODO: Move MapCard to a separate component; requires refactoring of how fetchStats and authContext are passed
useEffect(() => {
if (user) {
console.log('User state:', user.state);
if (!user.state || user.state === '') {
setIsUpdateStateFormOpen(true);
}
}
}, [user]);

const MapCard = ({
title,
geoUrl,
Expand All @@ -125,7 +129,6 @@ const Risk: React.FC = (props) => {
<div className={header}>
<h2>{title}</h2>
</div>

<ComposableMap
data-tip="hello world"
projection="geoAlbersUsa"
Expand Down Expand Up @@ -188,13 +191,11 @@ const Risk: React.FC = (props) => {
</Geographies>
</ZoomableGroup>
</ComposableMap>
{/* <ReactTooltip>{tooltipContent}</ReactTooltip> */}
</div>
</div>
</Paper>
);

// Group latest vulns together
const latestVulnsGrouped: {
[key: string]: VulnerabilityCount;
} = {};
Expand All @@ -220,7 +221,17 @@ const Risk: React.FC = (props) => {
}
}

if (user && user.invitePending) {
if (isUpdateStateFormOpen) {
return (
<UpdateStateForm
open={isUpdateStateFormOpen}
userId={user?.id ?? ''}
onClose={() => setIsUpdateStateFormOpen(false)}
/>
);
}

if (user?.invitePending) {
return (
<div
style={{
Expand All @@ -245,59 +256,8 @@ const Risk: React.FC = (props) => {
);
}

// TODO: Move generatePDF to a separate component
// const generatePDF = async () => {
// const dateTimeNow = new Date(); // UTC Date Time
// const localDate = new Date(dateTimeNow); // Local Date Time
// setIsLoading(true);
// await delay(650);
// const input = document.getElementById('wrapper')!;
// input.style.width = '1400px';
// await delay(1);
// await html2canvas(input, {
// scrollX: 0,
// scrollY: 0,
// ignoreElements: function (element) {
// return 'mapWrapper' === element.id;
// }
// }).then((canvas) => {
// const imgData = canvas.toDataURL('image/png');
// const imgWidth = 190;
// const imgHeight = (canvas.height * imgWidth) / canvas.width;
// const pdf = new jsPDF('p', 'mm');
// pdf.setFontSize(18);
// pdf.text('Crossfeed Report', 12, 10);
// pdf.setFontSize(10);
// pdf.text(dateTimeNow.toISOString(), 12, 17);
// pdf.addImage(imgData, 'PNG', 10, 20, imgWidth, imgHeight); // charts
// pdf.line(3, 290, 207, 290);
// pdf.setFontSize(8);
// pdf.text('Prepared by ' + user?.fullName + ', ' + localDate, 3, 293); // print the name of the person who printed the report as well as a human friendly date/time
// pdf.save('Crossfeed_Report_' + dateTimeNow.toISOString() + '.pdf'); // sets the filename and adds the date and time
// });
// input.style.removeProperty('width');
// setIsLoading(false);
// };

return (
<RiskRoot className={classes.root}>
{/* {isLoading && (
<div className="cisa-crossfeed-loading">
<div></div>
<div></div>
</div>
)} */}
{/* <p>
<USWDSButton
outline
type="button"
onClick={() => {
generatePDF();
}}
>
Generate Report
</USWDSButton>
</p> */}
<div id="wrapper" className={contentWrapper}>
{stats && (
<div className={content}>
Expand Down

0 comments on commit 46bc01a

Please sign in to comment.