Skip to content

Commit 311a358

Browse files
peacheymfluxcd
andauthored
Create custom GraphQL types for UserPage and UserList (canada-ca#279)
* Link userList to userPage through an onClick function in each box of userList * Move queries and mutations to frontend/src/graphql. * Remove redundant <BOX> and update test to include barebone mocked userlist test. * Add Mocked Data test and expectations. Optimize most imports. This is the commit with the strange unused 'import App' that when removed breaks the tests. * Add barebones UserPage test. Update error and loading state handlers for UserPage. * Update docs to maintain eslint * Add a redirect test for UserList. This test will check that when a listElement is clicked, the user is redirected to the user page which is at route '/user'. * Remove unnecessary console log that was included in the '.then()' function. * Add badge rendering tests. Update logic for rendering in UserList.js * Disable functions on userPage when accessed from userList. Add tests. * Auto-release multiple images - gcr.io/track-compliance/api:master-596f9ee - gcr.io/track-compliance/frontend:master-91c4299 [ci skip] * Auto-release gcr.io/track-compliance/api:master-4301b10 [ci skip] * Add custom type for userList * Add custom faker types for userPage and userList. Add these types to the query type as well. * Update schema.faker.graphql to use a UserItemEdges. Update UserList.js to bind data based on new query format. Update /graphql/queries to user new query format based on custom type. Updated tests to user new query format. * Add faker values for custom types * Use custom object query. Bind admin value to checkbox. * Add prop type string in userPage * Make additional subtype for admin and org. Use new subtype in query. Update test and component to use new query. * Refactor userCard into its own component. Update UserList and UserList.test to accomodate these changes. Update UserCard Test. Add color check tests back to UserCard.test.js Co-authored-by: fluxcd <fluxcd@users.noreply.github.com>
1 parent c66987d commit 311a358

10 files changed

Lines changed: 611 additions & 249 deletions

File tree

frontend/schema.faker.graphql

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,16 @@ type Query {
735735
An api endpoint that will send a verification email to a given email address.
736736
"""
737737
sendValidationEmail(email: EmailAddress!): NotificationEmail
738+
739+
"""
740+
An api endpoint that will be used to populate a userList component in the front end.
741+
"""
742+
userList(organizaion: Acronym!): UserList
743+
744+
"""
745+
An api endpoint that will be used to populate a userPage component in the front end.
746+
"""
747+
userPage(userName: EmailAddress!): UserPage
738748

739749
queryDmarcReport(reportId: String!): QueryDmarcReport
740750
}
@@ -1093,6 +1103,73 @@ type WWWScanEdge {
10931103
cursor: String!
10941104
}
10951105

1106+
"""
1107+
This custom object is used to populate the userList componenet in the front end.
1108+
"""
1109+
type UserList {
1110+
""" Indicates which organization this list is being queried for."""
1111+
organization: Acronym!
1112+
1113+
"""Pagination data for this connection."""
1114+
pageInfo: PageInfo!
1115+
1116+
"""Contains the nodes in this connection. Aka: A list of userItems that will make up the list on the front end."""
1117+
edges: [UserItemEdge]!
1118+
}
1119+
1120+
type UserListItem implements Node {
1121+
"""The ID of the object."""
1122+
id: ID!
1123+
1124+
""" The users email address or userName """
1125+
userName: EmailAddress! @fake(type: email)
1126+
1127+
""" The users display name"""
1128+
displayName: String! @fake(type: firstName)
1129+
1130+
""" Indicates wether or not this user has enabled two factor authentication"""
1131+
tfa: Boolean! @examples(values: [true, false])
1132+
1133+
""" Indicates if this user is an admin of the organization specified in UserList query."""
1134+
admin: Boolean! @examples(values: [true, false])
1135+
}
1136+
1137+
"""A Relay edge containing a `UserItem` and its cursor."""
1138+
type UserItemEdge {
1139+
"""The item at the end of the edge"""
1140+
node: UserListItem!
1141+
1142+
"""A cursor for use in pagination"""
1143+
cursor: String!
1144+
}
1145+
1146+
"""
1147+
This custom gql object is used to populate a userPage component in the front end.
1148+
"""
1149+
type UserPage{
1150+
""" The users email address or userName"""
1151+
userName: EmailAddress! @fake(type: email)
1152+
1153+
""" The users display name"""
1154+
displayName: String! @fake(type: firstName)
1155+
1156+
""" Indicates the preferred language of this user."""
1157+
lang: String! @examples(values: ["English, "French])
1158+
1159+
""" Indicates wether or not this user has enabled two factor authentication"""
1160+
tfa: Boolean! @examples(values: [true, false])
1161+
1162+
""" Indicates if this user is an admin of the organization specified."""
1163+
userAffiliations: [UserPageAffiliations]!
1164+
}
1165+
1166+
type UserPageAffiliations{
1167+
""" Indicates if this user is an admin of the organization"""
1168+
admin: Boolean! @examples(values: [true, false])
1169+
1170+
""" Indicates which organization this users data is being displayed for."""
1171+
organization: Acronym! @examples(values: ["GC", "ABC", "ASDF", "NSTIR", "BC"])
1172+
10961173
"""A custom type used to query a DmarcReport for the front end."""
10971174

10981175
type QueryDmarcReport {

frontend/src/UserCard.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React from 'react'
2+
3+
import { Badge, Box, Text, PseudoBox } from '@chakra-ui/core'
4+
import { Trans } from '@lingui/macro'
5+
6+
import { useHistory } from 'react-router-dom'
7+
8+
import { bool, string } from 'prop-types'
9+
10+
export function UserCard(props) {
11+
const history = useHistory()
12+
return (
13+
<PseudoBox
14+
width="100%"
15+
display={{ md: 'flex' }}
16+
alignItems="center"
17+
onClick={() => {
18+
history.push({
19+
pathname: '/user',
20+
state: { detail: props.userName },
21+
})
22+
}}
23+
_hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
24+
p="30px"
25+
>
26+
<Box flexShrink="0" minW="15%">
27+
<Text mt={1} fontSize="lg" fontWeight="semibold">
28+
{props.displayName}
29+
</Text>
30+
</Box>
31+
<Box flexShrink="0" ml={{ md: 4 }} mr={{ md: 4 }} minW="35%">
32+
<Text fontSize="lg" minW="10%">
33+
{props.userName}
34+
</Text>
35+
</Box>
36+
<Box flexShrink="0" ml={{ md: 4 }} mr={{ md: 4 }} minW="15%">
37+
<Badge variantColor={props.tfa ? 'green' : 'red'} minW="15%">
38+
<Trans>TwoFactor</Trans>
39+
</Badge>
40+
<Badge
41+
variantColor={props.admin ? 'green' : 'red'}
42+
ml="10px"
43+
mr={{ md: 4 }}
44+
>
45+
<Trans>Admin</Trans>
46+
</Badge>
47+
</Box>
48+
<Box flexShrink="0" ml={{ md: 4 }} mr={{ md: 4 }} mt={2} minW="15%"></Box>
49+
</PseudoBox>
50+
)
51+
}
52+
53+
UserCard.propTypes = {
54+
displayName: string.isRequired,
55+
userName: string.isRequired,
56+
admin: bool.isRequired,
57+
tfa: bool.isRequired,
58+
}

frontend/src/UserList.js

Lines changed: 40 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,30 @@
11
import React from 'react'
22

33
import {
4-
Badge,
54
Stack,
65
SimpleGrid,
76
Divider,
8-
Box,
9-
Text,
107
Button,
118
Icon,
129
InputGroup,
1310
InputLeftElement,
1411
Input,
15-
PseudoBox,
1612
} from '@chakra-ui/core'
1713

1814
import { Trans } from '@lingui/macro'
19-
import gql from 'graphql-tag'
15+
import { QUERY_USERLIST } from './graphql/queries'
2016
import { useQuery } from '@apollo/react-hooks'
2117
import { PaginationButtons } from './PaginationButtons'
18+
import { UserCard } from './UserCard'
2219

2320
export function UserList() {
2421
// This function generates the URL when the page loads
25-
const { loading, error, data } = useQuery(
26-
gql`
27-
{
28-
user {
29-
affiliations {
30-
edges {
31-
node {
32-
organization {
33-
acronym
34-
affiliatedUsers {
35-
pageInfo {
36-
hasNextPage
37-
hasPreviousPage
38-
startCursor
39-
endCursor
40-
}
41-
edges {
42-
node {
43-
id
44-
user {
45-
userName
46-
displayName
47-
tfa
48-
affiliations {
49-
edges {
50-
node {
51-
id
52-
organization {
53-
acronym
54-
}
55-
permission
56-
}
57-
}
58-
}
59-
}
60-
}
61-
}
62-
}
63-
}
64-
}
65-
}
66-
}
67-
}
68-
}
69-
`,
70-
)
22+
const { loading, error, data } = useQuery(QUERY_USERLIST)
7123
if (loading) {
7224
return <p>Loading...</p>
7325
}
7426
if (error) {
75-
console.log(error)
76-
}
77-
if (data) {
78-
console.log(data)
27+
return <p>Error :(</p>
7928
}
8029

8130
return (
@@ -99,112 +48,46 @@ export function UserList() {
9948
</Button>
10049
</SimpleGrid>
10150
<Divider />
102-
10351
{data
104-
? data.user.affiliations.edges[0].node.organization.affiliatedUsers.edges.map(
105-
edge => {
106-
return (
107-
<Box key={edge.node.id} width="100%">
108-
<PseudoBox
109-
display={{ md: 'flex' }}
110-
alignItems="center"
111-
onClick={() => {
112-
window.alert('clicked box')
113-
}}
114-
_hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
115-
p="30px"
116-
>
117-
<Box flexShrink="0" minW="15%">
118-
<Text mt={1} fontSize="lg" fontWeight="semibold">
119-
{edge.node.user.displayName}
120-
</Text>
121-
</Box>
122-
<Box
123-
flexShrink="0"
124-
ml={{ md: 4 }}
125-
mr={{ md: 4 }}
126-
minW="35%"
127-
>
128-
<Text fontSize="lg" minW="10%">
129-
{edge.node.user.userName}
130-
</Text>
131-
</Box>
132-
<Box
133-
flexShrink="0"
134-
ml={{ md: 4 }}
135-
mr={{ md: 4 }}
136-
minW="25%"
137-
>
138-
<Box mt={2} color="gray.500">
139-
Orgs:&nbsp;
140-
{// Populate the user-orgs list.
141-
edge.node.user.affiliations.edges.map(
142-
(edge, i, arr) => {
143-
if (arr.length - 1 === i) {
144-
return (
145-
<Text display="inline" key={edge.node.id + i}>
146-
{edge.node.organization.acronym}
147-
</Text>
148-
)
149-
}
150-
return (
151-
<Text display="inline" key={edge.node.id + i}>
152-
{edge.node.organization.acronym + ' | '}
153-
</Text>
154-
)
155-
},
156-
)}
157-
</Box>
158-
</Box>
159-
<Box
160-
flexShrink="0"
161-
ml={{ md: 4 }}
162-
mr={{ md: 4 }}
163-
minW="15%"
164-
>
165-
<Badge
166-
variantColor={edge.node.user.tfa ? 'green' : 'red'}
167-
minW="15%"
168-
>
169-
<Trans>TwoFactor</Trans>
170-
</Badge>
171-
<Badge
172-
variantColor={
173-
edge.node.permission === 'ADMIN' ||
174-
edge.node.permission === 'SUPER_ADMIN'
175-
? 'green'
176-
: 'red'
177-
}
178-
ml="10px"
179-
mr={{ md: 4 }}
180-
>
181-
<Trans>Admin</Trans>
182-
</Badge>
183-
</Box>
184-
<Box
185-
flexShrink="0"
186-
ml={{ md: 4 }}
187-
mr={{ md: 4 }}
188-
mt={2}
189-
minW="15%"
190-
></Box>
191-
</PseudoBox>
192-
</Box>
193-
)
194-
},
195-
)
52+
? data.userList.edges.map((edge) => {
53+
return (
54+
<UserCard
55+
key={edge.node.id}
56+
userName={edge.node.userName}
57+
tfa={edge.node.tfa}
58+
admin={edge.node.admin}
59+
displayName={edge.node.displayName}
60+
/>
61+
)
62+
})
19663
: null}
197-
19864
<PaginationButtons
199-
next={
200-
data.user.affiliations.edges[0].node.organization.affiliatedUsers
201-
.pageInfo.hasNextPage
202-
}
203-
previous={
204-
data.user.affiliations.edges[0].node.organization.affiliatedUsers
205-
.pageInfo.hasPreviousPage
206-
}
65+
next={data.userList.pageInfo.hasNextPage}
66+
previous={data.userList.pageInfo.hasPreviousPage}
20767
/>
20868
</Stack>
20969
)
21070
}
71+
72+
/* -- Source code for adding organizations, not being used. --
73+
74+
<Box flexShrink="0" ml={{ md: 4 }} mr={{ md: 4 }} minW="25%">
75+
<Box mt={2} color="gray.500">
76+
Orgs:&nbsp;
77+
{// Populate the user-orgs list.
78+
edge.node.user.affiliations.edges.map((edge, i, arr) => {
79+
if (arr.length - 1 === i) {
80+
return (
81+
<Text display="inline" key={edge.node.id + i}>
82+
{edge.node.organization.acronym}
83+
</Text>
84+
)
85+
}
86+
return (
87+
<Text display="inline" key={edge.node.id + i}>
88+
{edge.node.organization.acronym + ' | '}
89+
</Text>
90+
)
91+
})}
92+
</Box>
93+
</Box>*/

0 commit comments

Comments
 (0)