Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logic to update missing user state and region values #334

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading