Skip to content

Commit

Permalink
Merge pull request #184 from cisagov/RSC-Demo
Browse files Browse the repository at this point in the history
Display User Assessment on Dashboard
  • Loading branch information
schmelz21 authored Apr 23, 2024
2 parents 82bb263 + a5f504a commit 372e467
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 161 deletions.
6 changes: 6 additions & 0 deletions backend/src/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as savedSearches from './saved-searches';
import rateLimit from 'express-rate-limit';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { UserType } from '../models';
import * as assessments from './assessments';

if (
(process.env.IS_OFFLINE || process.env.IS_LOCAL) &&
Expand Down Expand Up @@ -450,6 +451,11 @@ authenticatedRoute.put(
handlerToExpress(users.registrationDenial)
);

//Authenticated ReadySetCyber Routes
authenticatedRoute.get('/assessments', handlerToExpress(assessments.list));

authenticatedRoute.get('/assessments/:id', handlerToExpress(assessments.get));

//************* */
// V2 Routes //
//************* */
Expand Down
17 changes: 16 additions & 1 deletion backend/src/api/assessments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,29 @@ export const get = wrapHandler(async (event) => {
relations: [
'responses',
'responses.question',
'responses.question.category'
'responses.question.category',
'responses.question.resources'
]
});

if (!assessment) {
return NotFound;
}

// Sort responses by question.number and then by category.number
assessment.responses.sort((a, b) => {
const questionNumberComparison = a.question.number.localeCompare(
b.question.number
);
if (questionNumberComparison !== 0) {
return questionNumberComparison;
} else {
return a.question.category.number.localeCompare(
b.question.category.number
);
}
});

const responsesByCategory = assessment.responses.reduce((acc, response) => {
const categoryName = response.question.category.name;
if (!acc[categoryName]) {
Expand Down
47 changes: 35 additions & 12 deletions frontend/src/components/ReadySetCyber/RSCDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
import React from 'react';
import React, { useEffect, useCallback } from 'react';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Divider from '@mui/material/Divider';
import { RSCSideNav } from './RSCSideNav';
import { RSCDefaultSideNav } from './RSCDefaultSideNav';
import { RSCResult } from './RSCResult';
import { dummyResults } from './dummyData';

const results = dummyResults;
import { useAuthContext } from 'context';

export const RSCDashboard: React.FC = () => {
const { apiGet } = useAuthContext();

const [results, setResults] = React.useState<
{
id: string;
createdAt: string;
updatedAt: string;
rscID: string;
type: string;
}[]
>([]);

const fetchResults = useCallback(async () => {
try {
const data = await apiGet('/assessments');
console.log(data);
setResults(data);
} catch (e) {
console.error(e);
}
}, [apiGet]);

useEffect(() => {
fetchResults();
}, [fetchResults]);

return (
<Box sx={{ flexGrow: 1, padding: 2 }}>
<Grid container spacing={2}>
<Grid item sm={4} sx={{ display: { xs: 'none', sm: 'grid' } }}>
<RSCSideNav />
<Grid item xs={4}>
<RSCDefaultSideNav />
</Grid>
<Grid item xs={12} sm={8}>
<Grid item xs={8}>
<Box sx={{ flexGrow: 1, padding: 2, backgroundColor: 'white' }}>
<Stack>
<h2>Assessment Results</h2>
Expand All @@ -39,13 +63,12 @@ export const RSCDashboard: React.FC = () => {
key={result.id}
id={result.id}
type={result.type}
date={result.date}
categories={result.categories}
questions={result.questions}
createdAt={result.createdAt}
updatedAt={result.updatedAt}
rscID={result.rscID}
/>
))}
</Stack>
<Divider />
</Stack>
</Box>
</Grid>
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/ReadySetCyber/RSCDefaultSideNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import Divider from '@mui/material/Divider';

export const RSCDefaultSideNav: React.FC = () => {
return (
<div>
<Box sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
<List>
<ListItem>Welcome User</ListItem>
<Divider component="li" />
<ListItem>Take Questionnaire Again</ListItem>
<Divider component="li" />
<ListItem>Logout</ListItem>
</List>
</Box>
</div>
);
};
105 changes: 45 additions & 60 deletions frontend/src/components/ReadySetCyber/RSCDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Divider from '@mui/material/Divider';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import { RSCSideNav } from './RSCSideNav';
import { RSCResult } from './RSCResult';
import { RSCQuestion } from './RSCQuestion';
import { dummyResults } from './dummyData';
import {
Accordion,
AccordionSummary,
AccordionDetails,
Typography
} from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Category, Entry, RSCQuestion } from './RSCQuestion';
import { useAuthContext } from 'context';

export const RSCDetail: React.FC = () => {
const { apiGet } = useAuthContext();
const { id } = useParams<{ id: string }>();
const result = dummyResults.find((result) => result.id === parseInt(id)) || {
id: 0,
type: '',
date: ''
};
const { questions } = dummyResults.find(
(result) => result.id === parseInt(id)
) || { questions: [] };
const [categories, setCategories] = useState<Category[]>([]);

const categories =
dummyResults.find((result) => result.id === parseInt(id))?.categories || [];
const fetchResult = useCallback(async () => {
try {
const data = await apiGet(`/assessments/${id}`);
console.log('API Response:', data); // Continue to log the data for verification
if (data && typeof data === 'object' && !Array.isArray(data)) {
const transformedCategories = Object.entries(data).map(
([name, entries]) => ({
name,
entries: entries as Entry[]
})
);
setCategories(transformedCategories);
} else {
console.error('Unexpected response format:', data);
setCategories([]); // Fallback to an empty array if the format isn't correct
}
} catch (e) {
console.error('Failed to fetch categories:', e);
setCategories([]); // Ensure categories is reset to an empty array on error
}
}, [apiGet, id]);

useEffect(() => {
fetchResult();
}, [fetchResult]);
console.log('Transformed categories:', categories);

return (
<Box sx={{ flexGrow: 1, padding: 2 }}>
<Grid container spacing={2}>
<Grid item sm={4} sx={{ display: { xs: 'none', sm: 'grid' } }}>
<RSCSideNav />
<Grid item xs={4}>
<RSCSideNav categories={categories} />
</Grid>
<Grid item xs={12} sm={8}>
<Grid item xs={8}>
<Box sx={{ flexGrow: 1, padding: 2, backgroundColor: 'white' }}>
<Stack>
<Stack spacing={2}>
<Stack
direction="row"
justifyContent={'space-between'}
justifyContent="space-between"
alignItems="center"
padding={2}
>
Expand All @@ -54,8 +65,10 @@ export const RSCDetail: React.FC = () => {
</Button>
</Stack>
<Divider />
<h3>Thank you for completing the ReadySetCyber questionnaire!</h3>
<p>
<Typography variant="h6" component="h3" gutterBottom>
Thank you for completing the ReadySetCyber questionnaire!
</Typography>
<Typography>
Below, you’ll find a full summary of your completed
ReadySetCyber questionnaire. Please note the areas where you can
improve your organization’s cybersecurity posture, along with
Expand All @@ -65,38 +78,10 @@ export const RSCDetail: React.FC = () => {
Crossfeed, CISA’s Attack Surface Management platform, for free
vulnerability scanning services to kickstart or enhance your
cybersecurity measures.
</p>
<Box>
<RSCResult
id={result.id}
type={result.type}
date={result.date}
categories={[]}
questions={[]}
/>
</Box>
<br />
<Accordion sx={{ display: { xs: 'block', sm: 'none' } }}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header"
>
{' '}
Categories
</AccordionSummary>
{categories.map((category) => (
<AccordionDetails key={category.id}>
{category.name}
</AccordionDetails>
))}
</Accordion>
<br />
<Stack spacing={2}>
{questions.map((question) => (
<RSCQuestion key={question.id} question={question} />
))}
</Stack>
</Typography>
{categories.map((category, index) => (
<RSCQuestion key={index} categories={[category]} />
))}
</Stack>
</Box>
</Grid>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/ReadySetCyber/RSCHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const RSCHeader: React.FC = () => {
};

return (
<Box sx={{ flexGrow: 1 }}>
<Box>
<AppBar position="static" sx={{ bgcolor: 'white' }}>
<Container maxWidth="xl">
<Toolbar disableGutters>
Expand Down
Loading

0 comments on commit 372e467

Please sign in to comment.