Skip to content

Commit b546da1

Browse files
Add Summary Charts to Landing Page (canada-ca#519)
* add summary charts with mocked data to landing page, create domain summary table * npm install react-table * moved mock data generation from SummaryCard to SummaryGroup * update summarycard test with mocked data * Correct typo in test data * 🍩-- This commit reigns in the number of doughnuts on the landing page and slims down the code as well. The nav bar has also been changed to match the title bar in the doughnut box, and the green used for the "pass" color has been lightened: both of these changes are being done to reach a better colour contrast for low vision users. * Update translations * Sync test data with domains query to fix warnings This commit updates the test data for the DomainsPage that had fallen out of sync with the query. This resolves some warnings that were displayed when the tests ran. Co-authored-by: Mike Williamson <mike@korora.ca>
1 parent ff8a448 commit b546da1

16 files changed

Lines changed: 863 additions & 99 deletions

frontend/package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"react-dom": "^16.13.1",
3939
"react-minimal-pie-chart": "^8.0.1",
4040
"react-router-dom": "^5.2.0",
41+
"react-table": "^7.1.0",
4142
"react-test-renderer": "^16.13.1",
4243
"recharts": "^1.8.5",
4344
"yup": "^0.29.1"

frontend/schema.faker.graphql

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -927,10 +927,6 @@ type Mutation {
927927
"""
928928
userName: EmailAddress!
929929

930-
"""
931-
Preferred language of the user
932-
"""
933-
preferredLang: LanguageEnums!
934930
): SignUp
935931

936932
"""
@@ -1811,7 +1807,7 @@ type UserPage implements Node {
18111807
"""
18121808
Indicates the preferred language of this user
18131809
"""
1814-
lang: String @examples(values: ["English", "French"])
1810+
lang: String @examples(values: ["ENGLISH", "FRENCH"])
18151811

18161812
"""
18171813
Indicates wether or not this user has enabled tfa

frontend/src/LandingPage.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ import React from 'react'
22
import { Trans } from '@lingui/macro'
33
import { Layout } from './Layout'
44
import { Heading, Text, Stack } from '@chakra-ui/core'
5+
import { SummaryGroup } from './SummaryGroup'
56

67
export function LandingPage() {
78
return (
89
<Layout>
910
<Stack spacing={10} shouldWrapChildren>
1011
<Heading as="h1">
11-
<Trans>Track web security compliance</Trans>
12+
<Trans>Track Web Security Compliance</Trans>
1213
</Heading>
1314
<Stack spacing={4}>
1415
<Stack spacing={4} direction="row" flexWrap="wrap">
15-
<Text>
16+
<Text fontSize="lg">
1617
<Trans>
1718
Canadians rely on the Government of Canada to provide secure
1819
digital services. A new policy notice guides government websites
@@ -22,7 +23,15 @@ export function LandingPage() {
2223
</Text>
2324
</Stack>
2425
</Stack>
26+
<Stack align="center">
27+
<SummaryGroup name="dashboard" />
28+
</Stack>
2529
</Stack>
30+
<Text>
31+
<Trans>
32+
*All data represented is mocked for demonstration purposes
33+
</Trans>
34+
</Text>
2635
</Layout>
2736
)
2837
}

frontend/src/Navigation.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export const Navigation = ({ children, ...props }) => {
1111
justify="space-between"
1212
wrap="wrap"
1313
padding={{ sm: '0.6rem', md: '0.80rem', lg: '1rem', xl: '1rem' }}
14-
bg="teal.500"
14+
bg="gray.550"
15+
py={15.5}
1516
color="white"
1617
{...props}
1718
>

frontend/src/SummaryCard.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React from 'react'
2+
import theme from './theme/canada'
3+
import { Text, Stack, Box, Badge } from '@chakra-ui/core'
4+
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts'
5+
import { string, array } from 'prop-types'
6+
7+
const { colors } = theme
8+
9+
export function SummaryCard({ ...props }) {
10+
const { name, title, description, data } = props
11+
12+
const reducer = (accumulator, currentValue) => {
13+
return accumulator + currentValue
14+
}
15+
16+
data.forEach((entry) => {
17+
entry.value = entry.categories
18+
.map((category) => category.qty)
19+
.reduce(reducer)
20+
})
21+
22+
const totalQty = data
23+
.map((entry) => {
24+
return entry.value
25+
})
26+
.reduce(reducer)
27+
28+
data.forEach((entry) => {
29+
entry.percent = Math.round((entry.value / totalQty) * 100 * 10) / 10
30+
})
31+
32+
return (
33+
<Box w="100%" rounded="lg" bg="white" overflow="hidden">
34+
<Box bg="gray.550" px="2em">
35+
<Text
36+
fontSize="xl"
37+
fontWeight="semibold"
38+
textAlign="center"
39+
color="white"
40+
>
41+
{title}
42+
</Text>
43+
{name === 'dashboard' && (
44+
<Text fontSize="md" textAlign="center" color="white">
45+
{description}
46+
</Text>
47+
)}
48+
</Box>
49+
50+
<ResponsiveContainer width="100%" height={300}>
51+
<PieChart>
52+
<Pie
53+
data={data}
54+
cx="50%"
55+
cy="50%"
56+
innerRadius="50%"
57+
outerRadius="90%"
58+
paddingAngle={2}
59+
dataKey="value"
60+
>
61+
{data.map(({ name, strength }) => {
62+
return <Cell key={name} dataKey={name} fill={colors[strength]} />
63+
})}
64+
</Pie>
65+
<Tooltip />
66+
</PieChart>
67+
</ResponsiveContainer>
68+
69+
<Stack align="center">
70+
{data.map(({ strength, percent, name }) => {
71+
if (!(name === 'web' && strength === 'moderate')) {
72+
// stop moderate badge from appearing on web donuts
73+
return (
74+
<Text
75+
color="white"
76+
key={`${name}:${strength}:${percent}`}
77+
px="1em"
78+
bg={strength}
79+
alignItems="center"
80+
>
81+
{name}: {percent}%
82+
</Text>
83+
)
84+
}
85+
})}
86+
</Stack>
87+
88+
<br />
89+
90+
{name === 'dashboard' && (
91+
<Stack isInline display="flex">
92+
{data.map(({ strength, categories }) => {
93+
return categories.map((category) => {
94+
return (
95+
<Text
96+
flex="1"
97+
key={`${name}:${strength}:${category}`}
98+
color="black"
99+
backgroundColor={strength}
100+
rounded="md"
101+
fontWeight="bold"
102+
>
103+
{category.qty}
104+
<br />
105+
{category.name}
106+
</Text>
107+
)
108+
})
109+
})}
110+
</Stack>
111+
)}
112+
</Box>
113+
)
114+
}
115+
116+
SummaryCard.propTypes = {
117+
name: string,
118+
title: string,
119+
description: string,
120+
data: array,
121+
}

frontend/src/SummaryGroup.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import React from 'react'
2+
import { t } from '@lingui/macro'
3+
import { useLingui } from '@lingui/react'
4+
import { Text, Stack, SimpleGrid } from '@chakra-ui/core'
5+
import { SummaryCard } from './SummaryCard'
6+
import { string } from 'prop-types'
7+
8+
export function SummaryGroup({ ...props }) {
9+
const { name, title, description } = props
10+
const { i18n } = useLingui()
11+
12+
// randomized data used to populate charts before API is connected
13+
function makeData() {
14+
return [
15+
{
16+
strength: i18n._(t`strong`),
17+
name:
18+
name === 'web' ? i18n._(t`Enforced`) : i18n._(t`Fully Implemented`),
19+
categories: [
20+
{
21+
name: i18n._(t`pass`),
22+
qty: Math.floor(Math.random() * 1000 + 1),
23+
},
24+
],
25+
},
26+
{
27+
strength: i18n._(t`moderate`),
28+
name: i18n._(t`Partially Implemented`),
29+
categories: [
30+
{
31+
name: i18n._(t`partial pass`),
32+
qty: name === 'web' ? null : Math.floor(Math.random() * 300 + 1),
33+
},
34+
],
35+
},
36+
{
37+
strength: i18n._(t`weak`),
38+
name:
39+
name === 'web' ? i18n._(t`Not Enforced`) : i18n._(t`Not Implemented`),
40+
categories: [
41+
{
42+
name: i18n._(t`fail`),
43+
qty: Math.floor(Math.random() * 300 + 1),
44+
},
45+
],
46+
},
47+
]
48+
}
49+
50+
const dashOverview = [
51+
{
52+
title: i18n._(t`Web Configuration`),
53+
description: i18n._(t`Web encryption settings summary`),
54+
},
55+
{
56+
title: i18n._(t`Email Configuration`),
57+
description: i18n._(t`Email security settings summary`),
58+
},
59+
]
60+
61+
const webOverview = [
62+
{
63+
title: 'HTTPS',
64+
description: 'description',
65+
},
66+
{
67+
title: 'HSTS',
68+
description: 'description',
69+
},
70+
{
71+
title: i18n._(t`HSTS Preloaded`),
72+
description: 'description',
73+
},
74+
{
75+
title: 'SSL',
76+
description: 'description',
77+
},
78+
{
79+
title: i18n._(t`Protocols & Ciphers`),
80+
description: 'description',
81+
},
82+
{
83+
title: i18n._(t`Approved Certificate Use`),
84+
description: 'description',
85+
},
86+
]
87+
88+
const emailOverview = [
89+
{
90+
title: 'SPF',
91+
description: 'description',
92+
},
93+
{
94+
title: 'DKIM',
95+
description: 'description',
96+
},
97+
{
98+
title: 'DMARC',
99+
description: 'description',
100+
},
101+
]
102+
103+
const getReportQty = () => {
104+
let reportQty
105+
if (name === 'dashboard') {
106+
reportQty = dashOverview.length
107+
} else if (name === 'web') {
108+
reportQty = webOverview.length
109+
} else {
110+
reportQty = emailOverview.length
111+
}
112+
return reportQty
113+
}
114+
115+
const createReports = () => {
116+
const reports = []
117+
let reportData
118+
if (name === 'dashboard') {
119+
reportData = dashOverview
120+
} else if (name === 'web') {
121+
reportData = webOverview
122+
} else {
123+
reportData = emailOverview
124+
}
125+
126+
for (let i = 0; i < getReportQty(); i++) {
127+
reports.push(
128+
<SummaryCard
129+
name={name}
130+
key={reportData[i].title}
131+
title={reportData[i].title}
132+
description={reportData[i].description}
133+
data={makeData()}
134+
/>,
135+
)
136+
}
137+
return reports
138+
}
139+
140+
return (
141+
<Stack textAlign="center" align="center">
142+
<SimpleGrid columns={[1, 1, 1, 1, 2]} spacing="30px">
143+
{createReports()}
144+
</SimpleGrid>
145+
)
146+
</Stack>
147+
)
148+
}
149+
150+
SummaryGroup.propTypes = {
151+
name: string,
152+
title: string,
153+
description: string,
154+
}

0 commit comments

Comments
 (0)