From 9b5b416d4d3b256aea5c549f12610d27efd06793 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Fri, 3 Apr 2020 08:32:11 -0300
Subject: [PATCH 01/20] Link userList to userPage through an onClick function
in each box of userList
---
frontend/src/UserList.js | 9 +++++++--
frontend/src/UserPage.js | 12 ++++++++++--
2 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js
index 85cabeabb9..c47d68560b 100644
--- a/frontend/src/UserList.js
+++ b/frontend/src/UserList.js
@@ -19,8 +19,10 @@ import { Trans } from '@lingui/macro'
import gql from 'graphql-tag'
import { useQuery } from '@apollo/react-hooks'
import { PaginationButtons } from './PaginationButtons'
+import { useHistory } from 'react-router-dom'
export function UserList() {
+ const history = useHistory()
// This function generates the URL when the page loads
const { loading, error, data } = useQuery(
gql`
@@ -102,14 +104,17 @@ export function UserList() {
{data
? data.user.affiliations.edges[0].node.organization.affiliatedUsers.edges.map(
- edge => {
+ (edge) => {
return (
{
- window.alert('clicked box')
+ history.push({
+ pathname: '/user',
+ state: { detail: edge.node.user.userName },
+ })
}}
_hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
p="30px"
diff --git a/frontend/src/UserPage.js b/frontend/src/UserPage.js
index e6e46b91ac..4c9fff3528 100644
--- a/frontend/src/UserPage.js
+++ b/frontend/src/UserPage.js
@@ -21,6 +21,10 @@ import {
import { useApolloClient, useMutation, useQuery } from '@apollo/react-hooks'
import { PasswordConfirmation } from './PasswordConfirmation'
import gql from 'graphql-tag'
+import { useLocation } from 'react-router-dom'
+
+export function UserPage(props) {
+ const location = useLocation()
export function UserPage() {
const userName = 'mike@korora.ca'
@@ -71,7 +75,9 @@ export function UserPage() {
error: queryUserError,
data: queryUserData,
} = useQuery(QUERY_USER, {
- variables: { userName: userName },
+ variables: {
+ userName: location.state ? location.state.detail : props.userName,
+ },
})
if (updatePasswordLoading || queryUserLoading) {
@@ -86,7 +92,7 @@ export function UserPage() {
)
}
+
+UserPage.propTypes = { userName: string }
From 3c4256b55bba3cee2b4e290bf17363a3e99fddbf Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Fri, 3 Apr 2020 09:36:26 -0300
Subject: [PATCH 02/20] Move queries and mutations to frontend/src/graphql.
---
frontend/src/UserList.js | 49 ++-------------------------
frontend/src/UserPage.js | 36 ++------------------
frontend/src/graphql/mutations.js | 18 ++++++++++
frontend/src/graphql/queries.js | 55 +++++++++++++++++++++++++++++++
4 files changed, 78 insertions(+), 80 deletions(-)
diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js
index c47d68560b..5a91d24487 100644
--- a/frontend/src/UserList.js
+++ b/frontend/src/UserList.js
@@ -16,7 +16,7 @@ import {
} from '@chakra-ui/core'
import { Trans } from '@lingui/macro'
-import gql from 'graphql-tag'
+import { QUERY_USERLIST } from './graphql/queries'
import { useQuery } from '@apollo/react-hooks'
import { PaginationButtons } from './PaginationButtons'
import { useHistory } from 'react-router-dom'
@@ -24,52 +24,7 @@ import { useHistory } from 'react-router-dom'
export function UserList() {
const history = useHistory()
// This function generates the URL when the page loads
- const { loading, error, data } = useQuery(
- gql`
- {
- user {
- affiliations {
- edges {
- node {
- organization {
- acronym
- affiliatedUsers {
- pageInfo {
- hasNextPage
- hasPreviousPage
- startCursor
- endCursor
- }
- edges {
- node {
- id
- user {
- userName
- displayName
- tfa
- affiliations {
- edges {
- node {
- id
- organization {
- acronym
- }
- permission
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- `,
- )
+ const { loading, error, data } = useQuery(QUERY_USERLIST)
if (loading) {
return Loading...
}
diff --git a/frontend/src/UserPage.js b/frontend/src/UserPage.js
index 4c9fff3528..b2701e8fb5 100644
--- a/frontend/src/UserPage.js
+++ b/frontend/src/UserPage.js
@@ -20,43 +20,13 @@ import {
} from '@chakra-ui/core'
import { useApolloClient, useMutation, useQuery } from '@apollo/react-hooks'
import { PasswordConfirmation } from './PasswordConfirmation'
-import gql from 'graphql-tag'
import { useLocation } from 'react-router-dom'
+import { QUERY_USER } from './graphql/queries'
+import { UPDATE_PASSWORD } from './graphql/mutations'
+
export function UserPage(props) {
const location = useLocation()
-
-export function UserPage() {
- const userName = 'mike@korora.ca'
-
- // TODO: Move to mutations folder
- const UPDATE_PASSWORD = gql`
- mutation UpdatePassword(
- $userName: EmailAddress!
- $password: String!
- $confirmPassword: String!
- ) {
- updatePassword(
- userName: $userName
- password: $password
- confirmPassword: $confirmPassword
- ) {
- user {
- userName
- }
- }
- }
- `
-
- const QUERY_USER = gql`
- query User($userName: EmailAddress!) {
- user(userName: $userName) {
- displayName
- lang
- }
- }
- `
-
const client = useApolloClient()
const toast = useToast()
const history = useHistory()
diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js
index 698dcd8ce5..04da8bec60 100644
--- a/frontend/src/graphql/mutations.js
+++ b/frontend/src/graphql/mutations.js
@@ -42,4 +42,22 @@ export const VALIDATE_TWO_FACTOR = gql`
}
`
+export const UPDATE_PASSWORD = gql`
+ mutation UpdatePassword(
+ $userName: EmailAddress!
+ $password: String!
+ $confirmPassword: String!
+ ) {
+ updatePassword(
+ userName: $userName
+ password: $password
+ confirmPassword: $confirmPassword
+ ) {
+ user {
+ userName
+ }
+ }
+ }
+`
+
export default ''
diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js
index 0250cfbf65..3ed053db7b 100644
--- a/frontend/src/graphql/queries.js
+++ b/frontend/src/graphql/queries.js
@@ -17,3 +17,58 @@ export const GENERATE_OTP_URL = gql`
generateOtpUrl(email: $email)
}
`
+
+export const QUERY_USERLIST = gql`
+ {
+ user {
+ affiliations {
+ edges {
+ node {
+ organization {
+ acronym
+ affiliatedUsers {
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ }
+ edges {
+ node {
+ id
+ user {
+ userName
+ displayName
+ tfa
+ affiliations {
+ edges {
+ node {
+ id
+ organization {
+ acronym
+ }
+ permission
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+`
+
+export const QUERY_USER = gql`
+ query User($userName: EmailAddress!) {
+ user(userName: $userName) {
+ userName
+ displayName
+ lang
+ }
+ }
+`
From 1c4376620505c7d0d386a482ef8750b788a680dd Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Fri, 3 Apr 2020 10:46:46 -0300
Subject: [PATCH 03/20] Remove redundant and update test to include
barebone mocked userlist test.
---
frontend/src/UserList.js | 156 +++++++++++-------------
frontend/src/__tests__/UserList.test.js | 96 ++++++++++++++-
2 files changed, 165 insertions(+), 87 deletions(-)
diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js
index 5a91d24487..a245a980dd 100644
--- a/frontend/src/UserList.js
+++ b/frontend/src/UserList.js
@@ -29,7 +29,7 @@ export function UserList() {
return Loading...
}
if (error) {
- console.log(error)
+ return Error :(
}
if (data) {
console.log(data)
@@ -61,95 +61,79 @@ export function UserList() {
? data.user.affiliations.edges[0].node.organization.affiliatedUsers.edges.map(
(edge) => {
return (
-
- {
- history.push({
- pathname: '/user',
- state: { detail: edge.node.user.userName },
- })
- }}
- _hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
- p="30px"
- >
-
-
- {edge.node.user.displayName}
-
-
-
-
- {edge.node.user.userName}
-
-
-
-
- Orgs:
- {// Populate the user-orgs list.
- edge.node.user.affiliations.edges.map(
- (edge, i, arr) => {
- if (arr.length - 1 === i) {
- return (
-
- {edge.node.organization.acronym}
-
- )
- }
- return (
-
- {edge.node.organization.acronym + ' | '}
-
- )
- },
- )}
-
+ {
+ history.push({
+ pathname: '/user',
+ state: { detail: edge.node.user.userName },
+ })
+ }}
+ _hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
+ p="30px"
+ >
+
+
+ {edge.node.user.displayName}
+
+
+
+
+ {edge.node.user.userName}
+
+
+
+
+ Orgs:
+ {// Populate the user-orgs list.
+ edge.node.user.affiliations.edges.map((edge, i, arr) => {
+ if (arr.length - 1 === i) {
+ return (
+
+ {edge.node.organization.acronym}
+
+ )
+ }
+ return (
+
+ {edge.node.organization.acronym + ' | '}
+
+ )
+ })}
-
+
+
-
- TwoFactor
-
-
- Admin
-
-
- TwoFactor
+
+
-
-
+ >
+ Admin
+
+
+
+
)
},
)
diff --git a/frontend/src/__tests__/UserList.test.js b/frontend/src/__tests__/UserList.test.js
index 4479e56d65..2172600d32 100644
--- a/frontend/src/__tests__/UserList.test.js
+++ b/frontend/src/__tests__/UserList.test.js
@@ -7,10 +7,12 @@ import { ThemeProvider, theme } from '@chakra-ui/core'
import { I18nProvider } from '@lingui/react'
import { MockedProvider } from '@apollo/react-testing'
+import { QUERY_USERLIST } from '../graphql/queries'
+
describe('', () => {
afterEach(cleanup)
- it('the component renders', async () => {
+ it('renders', async () => {
const { container } = render(
@@ -24,4 +26,96 @@ describe('', () => {
)
expect(container).toBeTruthy()
})
+
+ it('the component renders correctly with mocked data', async () => {
+ const mocks = [
+ {
+ request: {
+ query: QUERY_USERLIST,
+ },
+ result: {
+ data: {
+ user: {
+ affiliations: {
+ edges: [
+ {
+ node: {
+ organization: {
+ acronym: 'TEST',
+ affiliatedUsers: {
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: true,
+ startCursor: 'string',
+ endCursor: 'string',
+ },
+ edges: [
+ {
+ node: {
+ id: 'NzYzMzQ1MzQ1Ng==',
+ user: {
+ userName: 'testuser@testemail.ca',
+ displayName: 'Test User',
+ tfa: false,
+ affiliations: {
+ edges: [
+ {
+ node: {
+ id: 'ODAyOTY1MDMyMQ==',
+ organization: {
+ acronym: 'GC',
+ },
+ permission: 'SUPER_ADMIN',
+ },
+ },
+ {
+ node: {
+ id: 'NjI3NzcyNDQ=',
+ organization: {
+ acronym: 'BC',
+ },
+ permission: 'USER_WRITE',
+ },
+ },
+ {
+ node: {
+ id: 'NjkyMzAyOTAx',
+ organization: {
+ acronym: 'BC',
+ },
+ permission: 'ADMIN',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ ]
+ const { container } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeTruthy()
+
+ // TODO: Write test for this.
+ })
})
From d10b99a35c9305ea30574d4808415a0a53037a52 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Mon, 6 Apr 2020 08:12:14 -0300
Subject: [PATCH 04/20] 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.
---
frontend/src/UserList.js | 3 --
frontend/src/__tests__/UserList.test.js | 47 +++++++++++++++++--------
2 files changed, 32 insertions(+), 18 deletions(-)
diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js
index a245a980dd..3688295456 100644
--- a/frontend/src/UserList.js
+++ b/frontend/src/UserList.js
@@ -31,9 +31,6 @@ export function UserList() {
if (error) {
return Error :(
}
- if (data) {
- console.log(data)
- }
return (
diff --git a/frontend/src/__tests__/UserList.test.js b/frontend/src/__tests__/UserList.test.js
index 2172600d32..f4e3fefb83 100644
--- a/frontend/src/__tests__/UserList.test.js
+++ b/frontend/src/__tests__/UserList.test.js
@@ -1,7 +1,11 @@
import React from 'react'
import { UserList } from '../UserList'
import { i18n } from '@lingui/core'
-import { render, cleanup } from '@testing-library/react'
+import {
+ render,
+ cleanup,
+ waitForElementToBeRemoved,
+} from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { ThemeProvider, theme } from '@chakra-ui/core'
import { I18nProvider } from '@lingui/react'
@@ -9,10 +13,13 @@ import { MockedProvider } from '@apollo/react-testing'
import { QUERY_USERLIST } from '../graphql/queries'
+// If this unused import, the mocked data test fails. VERY weird.
+import App from '../App'
+
describe('', () => {
afterEach(cleanup)
- it('renders', async () => {
+ it('successfully renders', async () => {
const { container } = render(
@@ -24,10 +31,10 @@ describe('', () => {
,
)
- expect(container).toBeTruthy()
+ expect(container).toBeDefined()
})
- it('the component renders correctly with mocked data', async () => {
+ it('successfully renders with mocked data', async () => {
const mocks = [
{
request: {
@@ -103,19 +110,29 @@ describe('', () => {
},
},
]
- const { container } = render(
-
-
-
-
+ // Set the inital history item to user-list
+ const { container, queryAllByRole, getByText } = render(
+
+
+
+
-
-
-
- ,
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+
+ expect(getByText('Loading...')).toBeInTheDocument()
+ const loadingElement = getByText('Loading...')
+
+ await waitForElementToBeRemoved(loadingElement).then(() =>
+ console.log('Element no longer in DOM'),
)
- expect(container).toBeTruthy()
- // TODO: Write test for this.
+ // Get all of the mocked user cards, and expect there to be only one entry.
+ const userCards = queryAllByRole('userCard')
+ expect(userCards).toHaveLength(1)
})
})
From bdfb713296d39dab93f8f0a5d70d960fc76c5db0 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Mon, 6 Apr 2020 08:47:42 -0300
Subject: [PATCH 05/20] Add barebones UserPage test. Update error and loading
state handlers for UserPage.
---
frontend/src/UserPage.js | 12 +++-
frontend/src/__tests__/UserPage.test.js | 75 +++++++++++++++++++++++++
2 files changed, 85 insertions(+), 2 deletions(-)
create mode 100644 frontend/src/__tests__/UserPage.test.js
diff --git a/frontend/src/UserPage.js b/frontend/src/UserPage.js
index b2701e8fb5..9d91f77c96 100644
--- a/frontend/src/UserPage.js
+++ b/frontend/src/UserPage.js
@@ -50,11 +50,19 @@ export function UserPage(props) {
},
})
- if (updatePasswordLoading || queryUserLoading) {
+ if (queryUserLoading) {
return Loading...
}
- if (queryUserError || updatePasswordError) {
+ if (queryUserError) {
+ return Error
+ }
+
+ if (updatePasswordLoading) {
+ return Loading...
+ }
+
+ if (updatePasswordError) {
return Error
}
diff --git a/frontend/src/__tests__/UserPage.test.js b/frontend/src/__tests__/UserPage.test.js
new file mode 100644
index 0000000000..c10f44f05b
--- /dev/null
+++ b/frontend/src/__tests__/UserPage.test.js
@@ -0,0 +1,75 @@
+import React from 'react'
+import { UserPage } from '../UserPage'
+import { i18n } from '@lingui/core'
+import {
+ render,
+ cleanup,
+ waitForElementToBeRemoved,
+} from '@testing-library/react'
+import { MemoryRouter } from 'react-router-dom'
+import { ThemeProvider, theme } from '@chakra-ui/core'
+import { I18nProvider } from '@lingui/react'
+import { MockedProvider } from '@apollo/react-testing'
+
+import { QUERY_USER } from '../graphql/queries'
+import { UPDATE_PASSWORD } from '../graphql/mutations'
+
+describe('', () => {
+ afterEach(cleanup)
+
+ it('successfully renders', async () => {
+ const { container } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+ })
+
+ it('successfully renders with mock data', async () => {
+ const mocks = [
+ {
+ request: {
+ query: QUERY_USER,
+ },
+ result: {
+ user: {
+ userName: 'testuser@testemail.gc.ca',
+ displayName: 'Test User',
+ lang: 'English',
+ },
+ },
+ },
+ {
+ request: {
+ query: UPDATE_PASSWORD,
+ },
+ result: {
+ updatePassword: {
+ user: {
+ userName: 'Gregg_Grady4@hotmail.com',
+ },
+ },
+ },
+ },
+ ]
+ const { container, getByText } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+ })
+})
From 883090940d9b3a4c93947aab2ca666963a7d9ccf Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Mon, 6 Apr 2020 08:48:41 -0300
Subject: [PATCH 06/20] Update docs to maintain eslint
---
frontend/src/__tests__/UserPage.test.js | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/frontend/src/__tests__/UserPage.test.js b/frontend/src/__tests__/UserPage.test.js
index c10f44f05b..f865c73cde 100644
--- a/frontend/src/__tests__/UserPage.test.js
+++ b/frontend/src/__tests__/UserPage.test.js
@@ -1,11 +1,7 @@
import React from 'react'
import { UserPage } from '../UserPage'
import { i18n } from '@lingui/core'
-import {
- render,
- cleanup,
- waitForElementToBeRemoved,
-} from '@testing-library/react'
+import { render, cleanup } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { ThemeProvider, theme } from '@chakra-ui/core'
import { I18nProvider } from '@lingui/react'
@@ -59,7 +55,7 @@ describe('', () => {
},
},
]
- const { container, getByText } = render(
+ const { container } = render(
From 220da1b446d528b61bb00e82907928421f46c8c7 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Mon, 6 Apr 2020 09:15:33 -0300
Subject: [PATCH 07/20] 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'.
---
frontend/src/__tests__/UserList.test.js | 124 +++++++++++++++++++++++-
frontend/src/__tests__/UserPage.test.js | 1 +
2 files changed, 124 insertions(+), 1 deletion(-)
diff --git a/frontend/src/__tests__/UserList.test.js b/frontend/src/__tests__/UserList.test.js
index f4e3fefb83..e18a5fb55e 100644
--- a/frontend/src/__tests__/UserList.test.js
+++ b/frontend/src/__tests__/UserList.test.js
@@ -5,11 +5,14 @@ import {
render,
cleanup,
waitForElementToBeRemoved,
+ fireEvent,
+ waitFor,
} from '@testing-library/react'
-import { MemoryRouter } from 'react-router-dom'
+import { MemoryRouter, Router } from 'react-router-dom'
import { ThemeProvider, theme } from '@chakra-ui/core'
import { I18nProvider } from '@lingui/react'
import { MockedProvider } from '@apollo/react-testing'
+import { createMemoryHistory } from 'history'
import { QUERY_USERLIST } from '../graphql/queries'
@@ -135,4 +138,123 @@ describe('', () => {
const userCards = queryAllByRole('userCard')
expect(userCards).toHaveLength(1)
})
+
+ it('redirects to userPage when a list element is clicked', async () => {
+ const mocks = [
+ {
+ request: {
+ query: QUERY_USERLIST,
+ },
+ result: {
+ data: {
+ user: {
+ affiliations: {
+ edges: [
+ {
+ node: {
+ organization: {
+ acronym: 'TEST',
+ affiliatedUsers: {
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: true,
+ startCursor: 'string',
+ endCursor: 'string',
+ },
+ edges: [
+ {
+ node: {
+ id: 'NzYzMzQ1MzQ1Ng==',
+ user: {
+ userName: 'testuser@testemail.ca',
+ displayName: 'Test User',
+ tfa: false,
+ affiliations: {
+ edges: [
+ {
+ node: {
+ id: 'ODAyOTY1MDMyMQ==',
+ organization: {
+ acronym: 'GC',
+ },
+ permission: 'SUPER_ADMIN',
+ },
+ },
+ {
+ node: {
+ id: 'NjI3NzcyNDQ=',
+ organization: {
+ acronym: 'BC',
+ },
+ permission: 'USER_WRITE',
+ },
+ },
+ {
+ node: {
+ id: 'NjkyMzAyOTAx',
+ organization: {
+ acronym: 'BC',
+ },
+ permission: 'ADMIN',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ ]
+
+ // create a history object and inject it so we can inspect it afterwards
+ // for the side effects of our form submission (a redirect to /!).
+ const history = createMemoryHistory({
+ initialEntries: ['user-list'],
+ initialIndex: 0,
+ })
+
+ // Set the inital history item to user-list
+ const { container, queryAllByRole, getByText } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+
+ expect(getByText('Loading...')).toBeInTheDocument()
+ const loadingElement = getByText('Loading...')
+
+ await waitForElementToBeRemoved(loadingElement).then(() =>
+ console.log('Element no longer in DOM'),
+ )
+
+ // Get all of the mocked user cards, and expect there to be only one entry.
+ const userCards = queryAllByRole('userCard')
+ expect(userCards).toHaveLength(1)
+
+ const leftClick = { button: 0 }
+ fireEvent.click(userCards[0], leftClick)
+ // default `button` property for click events is set to `0` which is a left click.
+
+ await waitFor(() => {
+ // Path should be '/user', so expect that value
+ expect(history.location.pathname).toEqual('/user')
+ })
+ })
})
diff --git a/frontend/src/__tests__/UserPage.test.js b/frontend/src/__tests__/UserPage.test.js
index f865c73cde..5a9a6443ce 100644
--- a/frontend/src/__tests__/UserPage.test.js
+++ b/frontend/src/__tests__/UserPage.test.js
@@ -55,6 +55,7 @@ describe('', () => {
},
},
]
+
const { container } = render(
From bee8cb27783b298dbfad8bd9e2e7b5ed42300e45 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Mon, 6 Apr 2020 09:17:25 -0300
Subject: [PATCH 08/20] Remove unnecessary console log that was included in the
'.then()' function.
---
frontend/src/__tests__/UserList.test.js | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/frontend/src/__tests__/UserList.test.js b/frontend/src/__tests__/UserList.test.js
index e18a5fb55e..b50cf87fa3 100644
--- a/frontend/src/__tests__/UserList.test.js
+++ b/frontend/src/__tests__/UserList.test.js
@@ -130,9 +130,7 @@ describe('', () => {
expect(getByText('Loading...')).toBeInTheDocument()
const loadingElement = getByText('Loading...')
- await waitForElementToBeRemoved(loadingElement).then(() =>
- console.log('Element no longer in DOM'),
- )
+ await waitForElementToBeRemoved(loadingElement)
// Get all of the mocked user cards, and expect there to be only one entry.
const userCards = queryAllByRole('userCard')
@@ -240,9 +238,7 @@ describe('', () => {
expect(getByText('Loading...')).toBeInTheDocument()
const loadingElement = getByText('Loading...')
- await waitForElementToBeRemoved(loadingElement).then(() =>
- console.log('Element no longer in DOM'),
- )
+ await waitForElementToBeRemoved(loadingElement)
// Get all of the mocked user cards, and expect there to be only one entry.
const userCards = queryAllByRole('userCard')
From 28078ed407f0e0bbb9d44874924f646c26f13727 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Mon, 6 Apr 2020 10:25:31 -0300
Subject: [PATCH 09/20] Add badge rendering tests. Update logic for rendering
in UserList.js
---
frontend/src/UserList.js | 4 +-
frontend/src/__tests__/UserList.test.js | 181 ++++++++++++++++++++++++
2 files changed, 183 insertions(+), 2 deletions(-)
diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js
index 3688295456..fc6c70f802 100644
--- a/frontend/src/UserList.js
+++ b/frontend/src/UserList.js
@@ -112,8 +112,8 @@ export function UserList() {
', () => {
expect(history.location.pathname).toEqual('/user')
})
})
+
+ it('badges are green when TwoFactor and Admin values are true', async () => {
+ const mocks = [
+ {
+ request: {
+ query: QUERY_USERLIST,
+ },
+ result: {
+ data: {
+ user: {
+ affiliations: {
+ edges: [
+ {
+ node: {
+ organization: {
+ acronym: 'TEST',
+ affiliatedUsers: {
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: true,
+ startCursor: 'string',
+ endCursor: 'string',
+ },
+ edges: [
+ {
+ node: {
+ id: 'NzYzMzQ1MzQ1Ng==',
+ user: {
+ userName: 'testuser@testemail.ca',
+ displayName: 'Test User',
+ tfa: true,
+ affiliations: {
+ edges: [
+ {
+ node: {
+ id: 'ODAyOTY1MDMyMQ==',
+ organization: {
+ acronym: 'GC',
+ },
+ permission: 'SUPER_ADMIN',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ ]
+
+ const { container, queryAllByRole, getByText } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+
+ expect(getByText('Loading...')).toBeInTheDocument()
+ const loadingElement = getByText('Loading...')
+
+ await waitForElementToBeRemoved(loadingElement)
+
+ // Get all of the mocked user cards, and expect there to be only one entry.
+ const userCards = queryAllByRole('userCard')
+ expect(userCards).toHaveLength(1)
+
+ const tfaBadge = getByText(/TwoFactor/i)
+ const adminBadge = getByText(/Admin/i)
+
+ expect(tfaBadge).toBeDefined()
+ expect(adminBadge).toBeDefined()
+
+ expect(tfaBadge).toHaveStyle('background-color: rgb(198, 246, 213)')
+ expect(adminBadge).toHaveStyle('background-color: rgb(198, 246, 213)')
+ })
+ it('badges are red when TwoFactor and Admin values are false', async () => {
+ const mocks = [
+ {
+ request: {
+ query: QUERY_USERLIST,
+ },
+ result: {
+ data: {
+ user: {
+ affiliations: {
+ edges: [
+ {
+ node: {
+ organization: {
+ acronym: 'TEST',
+ affiliatedUsers: {
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: true,
+ startCursor: 'string',
+ endCursor: 'string',
+ },
+ edges: [
+ {
+ node: {
+ id: 'NzYzMzQ1MzQ1Ng==',
+ user: {
+ userName: 'testuser@testemail.ca',
+ displayName: 'Test User',
+ tfa: false,
+ affiliations: {
+ edges: [
+ {
+ node: {
+ id: 'ODAyOTY1MDMyMQ==',
+ organization: {
+ acronym: 'GC',
+ },
+ permission: 'USER_READ',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ ]
+
+ const { container, queryAllByRole, getByText } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+
+ expect(getByText('Loading...')).toBeInTheDocument()
+ const loadingElement = getByText('Loading...')
+
+ await waitForElementToBeRemoved(loadingElement)
+
+ // Get all of the mocked user cards, and expect there to be only one entry.
+ const userCards = queryAllByRole('userCard')
+ expect(userCards).toHaveLength(1)
+
+ const tfaBadge = getByText(/TwoFactor/i)
+ const adminBadge = getByText(/Admin/i)
+
+ expect(tfaBadge).toBeDefined()
+ expect(adminBadge).toBeDefined()
+
+ expect(tfaBadge).toHaveStyle('background-color: rgb(254, 215, 215)')
+ expect(adminBadge).toHaveStyle('background-color: rgb(254, 215, 215)')
+ })
})
From 185f79427701a7cfdcc9e7aca3096b2615b035ca Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Tue, 7 Apr 2020 11:06:17 -0300
Subject: [PATCH 10/20] Disable functions on userPage when accessed from
userList. Add tests.
---
frontend/src/UserList.js | 8 +-
frontend/src/UserPage.js | 102 +++++++++++++-----------
frontend/src/__tests__/UserPage.test.js | 85 +++++++++-----------
3 files changed, 98 insertions(+), 97 deletions(-)
diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js
index fc6c70f802..300b5a0cd0 100644
--- a/frontend/src/UserList.js
+++ b/frontend/src/UserList.js
@@ -53,7 +53,6 @@ export function UserList() {
-
{data
? data.user.affiliations.edges[0].node.organization.affiliatedUsers.edges.map(
(edge) => {
@@ -112,8 +111,10 @@ export function UserList() {
Loading...
+ return Loading user...
}
if (queryUserError) {
- return Error
+ return {String(queryUserError)}
}
if (updatePasswordLoading) {
@@ -63,7 +63,7 @@ export function UserPage(props) {
}
if (updatePasswordError) {
- return Error
+ return {String(updatePasswordError)}
}
return (
@@ -150,6 +150,7 @@ export function UserPage(props) {
onClick={() => {
history.push('/two-factor-code')
}}
+ isDisabled={location.state ? true : false}
>
Enable 2FA
@@ -163,6 +164,7 @@ export function UserPage(props) {
Manage API keys
+
@@ -186,52 +189,57 @@ export function UserPage(props) {
Change Password
-
- Change your password below by entering and confirming a new password.
-
- {
- // Submit GraphQL mutation
- console.log(values)
- await updatePassword({
- variables: {
- userName: 'testuser@test.ca', // This needs to be retreived from a seperate GQL query or props that will populate this entire page with data.
- password: values.password,
- confirmPassword: values.confirmPassword,
- },
- })
-
- if (!updatePasswordError) {
- console.log(updatePasswordData)
- toast({
- title: 'Password Updated.',
- description: 'You have successfully changed your password.',
- status: 'success',
- duration: 9000,
- isClosable: true,
+ {location.state ? (
+ You can only change the password for your own account.
+ ) : (
+ {
+ // Submit GraphQL mutation
+ console.log(values)
+ await updatePassword({
+ variables: {
+ userName: 'testuser@test.ca', // This needs to be retreived from a seperate GQL query or props that will populate this entire page with data.
+ password: values.password,
+ confirmPassword: values.confirmPassword,
+ },
})
- }
- }}
- >
- {({ handleSubmit, isSubmitting }) => (
-
- )}
-
+
+ if (!updatePasswordError) {
+ console.log(updatePasswordData)
+ toast({
+ title: 'Password Updated.',
+ description: 'You have successfully changed your password.',
+ status: 'success',
+ duration: 9000,
+ isClosable: true,
+ })
+ }
+ }}
+ >
+ {({ handleSubmit, isSubmitting }) => (
+
+ )}
+
+ )}
)
diff --git a/frontend/src/__tests__/UserPage.test.js b/frontend/src/__tests__/UserPage.test.js
index 5a9a6443ce..0cecfd7f02 100644
--- a/frontend/src/__tests__/UserPage.test.js
+++ b/frontend/src/__tests__/UserPage.test.js
@@ -1,7 +1,7 @@
import React from 'react'
import { UserPage } from '../UserPage'
import { i18n } from '@lingui/core'
-import { render, cleanup } from '@testing-library/react'
+import { render, cleanup, act } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { ThemeProvider, theme } from '@chakra-ui/core'
import { I18nProvider } from '@lingui/react'
@@ -13,60 +13,53 @@ import { UPDATE_PASSWORD } from '../graphql/mutations'
describe('', () => {
afterEach(cleanup)
- it('successfully renders', async () => {
- const { container } = render(
-
-
-
-
-
-
-
-
- ,
- )
- expect(container).toBeDefined()
- })
+ const values = {
+ userName: 'testuser@testemail.gc.ca',
+ }
- it('successfully renders with mock data', async () => {
- const mocks = [
- {
- request: {
- query: QUERY_USER,
- },
- result: {
- user: {
- userName: 'testuser@testemail.gc.ca',
- displayName: 'Test User',
- lang: 'English',
- },
+ const mocks = [
+ {
+ request: {
+ query: QUERY_USER,
+ variables: {
+ userName: values.userName,
},
},
- {
- request: {
- query: UPDATE_PASSWORD,
+ result: {
+ user: {
+ userName: 'testuser@testemail.gc.ca',
+ displayName: 'Test User',
+ lang: 'English',
},
- result: {
- updatePassword: {
- user: {
- userName: 'Gregg_Grady4@hotmail.com',
- },
+ },
+ },
+ {
+ request: {
+ query: UPDATE_PASSWORD,
+ },
+ result: {
+ updatePassword: {
+ user: {
+ userName: 'Gregg_Grady4@hotmail.com',
},
},
},
- ]
+ },
+ ]
- const { container } = render(
-
-
+ it('renders without error', () => {
+ act(() => {
+ render(
+
-
-
-
+
+
+
+
+
-
- ,
- )
- expect(container).toBeDefined()
+ ,
+ )
+ })
})
})
From 392fc52ca103f9c69f0ff0ce9a7e4d728b598438 Mon Sep 17 00:00:00 2001
From: fluxcd
Date: Fri, 3 Apr 2020 14:41:29 +0000
Subject: [PATCH 11/20] Auto-release multiple images
- gcr.io/track-compliance/api:master-596f9ee
- gcr.io/track-compliance/frontend:master-91c4299
[ci skip]
---
platform/overlays/gke/kustomization.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/platform/overlays/gke/kustomization.yaml b/platform/overlays/gke/kustomization.yaml
index 1883da27bc..bd3afab976 100644
--- a/platform/overlays/gke/kustomization.yaml
+++ b/platform/overlays/gke/kustomization.yaml
@@ -8,9 +8,9 @@ resources:
- publicgateway.yaml
images:
- name: gcr.io/track-compliance/api
- newTag: master-4301b10
+ newTag: master-596f9ee
- name: gcr.io/track-compliance/frontend
- newTag: master-7ee5ba8
+ newTag: master-91c4299
patchesStrategicMerge:
- tracker-api-deployment.yaml
- tracker-frontend-deployment.yaml
From 8e77464cdf5603e6619263b9ee8e2d2042843fe4 Mon Sep 17 00:00:00 2001
From: fluxcd
Date: Mon, 6 Apr 2020 10:21:30 +0000
Subject: [PATCH 12/20] Auto-release gcr.io/track-compliance/api:master-4301b10
[ci skip]
---
platform/overlays/gke/kustomization.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/platform/overlays/gke/kustomization.yaml b/platform/overlays/gke/kustomization.yaml
index bd3afab976..5edd8475b6 100644
--- a/platform/overlays/gke/kustomization.yaml
+++ b/platform/overlays/gke/kustomization.yaml
@@ -8,7 +8,7 @@ resources:
- publicgateway.yaml
images:
- name: gcr.io/track-compliance/api
- newTag: master-596f9ee
+ newTag: master-4301b10
- name: gcr.io/track-compliance/frontend
newTag: master-91c4299
patchesStrategicMerge:
From 3e525fcab54779bc8d7a6d161b16c925de5b601c Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Tue, 7 Apr 2020 14:51:13 -0300
Subject: [PATCH 13/20] Add custom type for userList
---
frontend/schema.faker.graphql | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql
index cfb3eebc8f..ea0f33d6b7 100644
--- a/frontend/schema.faker.graphql
+++ b/frontend/schema.faker.graphql
@@ -735,6 +735,11 @@ type Query {
An api endpoint that will send a verification email to a given email address.
"""
sendValidationEmail(email: EmailAddress!): NotificationEmail
+
+ """
+ An api endpoint that will be used to populate a userList component in the front end.
+ """
+ userList(organizaion: Acronym!): UserList
}
"""DMARC aggregate report record"""
@@ -1090,3 +1095,20 @@ type WWWScanEdge {
"""A cursor for use in pagination"""
cursor: String!
}
+
+"""
+This custom object is used to populate the userList componenet in the front end.
+"""
+type UserList {
+ organizaion: Acronym!
+ userItems: [UserListItem]!
+}
+
+type UserListItem implements Node {
+ """The ID of the object."""
+ id: ID!
+ userName: EmailAddress!
+ displayName: String!
+ tfa: Boolean!
+ admin: Boolean!
+}
\ No newline at end of file
From c95f27f71f5635d399ec0a756173e56bc6981258 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Wed, 8 Apr 2020 08:25:16 -0300
Subject: [PATCH 14/20] Add custom faker types for userPage and userList. Add
these types to the query type as well.
---
frontend/schema.faker.graphql | 44 +++++++++++++++++++++++++++++++++--
1 file changed, 42 insertions(+), 2 deletions(-)
diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql
index ea0f33d6b7..e933c55264 100644
--- a/frontend/schema.faker.graphql
+++ b/frontend/schema.faker.graphql
@@ -740,6 +740,12 @@ type Query {
An api endpoint that will be used to populate a userList component in the front end.
"""
userList(organizaion: Acronym!): UserList
+
+ """
+ An api endpoint that will be used to populate a userPage component in the front end.
+ """
+ userPage(userName: EmailAddress!): UserPage
+
}
"""DMARC aggregate report record"""
@@ -1100,15 +1106,49 @@ type WWWScanEdge {
This custom object is used to populate the userList componenet in the front end.
"""
type UserList {
- organizaion: Acronym!
+ """ Indicates which organization this list is being queried for."""
+ organization: Acronym!
+
+ """ A list of userItems that will make up the list on the front end. An item per user in the org."
userItems: [UserListItem]!
}
type UserListItem implements Node {
"""The ID of the object."""
id: ID!
+
+ """ The users email address or userName
userName: EmailAddress!
+
+ """ The users display name"""
displayName: String!
+
+ """ Indicates wether or not this user has enabled two factor authentication"
tfa: Boolean!
+
+ """ Indicates if this user is an admin of the organization specified in UserList query."""
admin: Boolean!
-}
\ No newline at end of file
+}
+
+"""
+This custom gql object is used to populate a userPage component in the front end.
+"""
+type UserPage{
+ """ The users email address or userName
+ userName: EmailAddress!
+
+ """ The users display name"""
+ displayName: String!
+
+ """ Indicates which organization this users data is being displayed for."""
+ organization: Acronym!
+
+ """ Indicates the preferred language of this user."""
+ lang: String!
+
+ """ Indicates wether or not this user has enabled two factor authentication"
+ tfa: Boolean!
+
+ """ Indicates if this user is an admin of the organization specified."""
+ admin: Boolean!
+}
From 1efeeb7161f12faf29b8b7edcb05f984bdbf9948 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Wed, 8 Apr 2020 09:19:15 -0300
Subject: [PATCH 15/20] 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.
---
frontend/schema.faker.graphql | 24 +-
frontend/src/UserList.js | 162 +++++++-------
frontend/src/__tests__/UserList.test.js | 278 ++++++------------------
frontend/src/graphql/queries.js | 50 ++---
4 files changed, 170 insertions(+), 344 deletions(-)
diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql
index e933c55264..5d18a34210 100644
--- a/frontend/schema.faker.graphql
+++ b/frontend/schema.faker.graphql
@@ -1109,21 +1109,24 @@ type UserList {
""" Indicates which organization this list is being queried for."""
organization: Acronym!
- """ A list of userItems that will make up the list on the front end. An item per user in the org."
- userItems: [UserListItem]!
+ """Pagination data for this connection."""
+ pageInfo: PageInfo!
+
+ """Contains the nodes in this connection. Aka: A list of userItems that will make up the list on the front end."""
+ edges: [UserItemEdge]!
}
type UserListItem implements Node {
"""The ID of the object."""
id: ID!
- """ The users email address or userName
+ """ The users email address or userName """
userName: EmailAddress!
""" The users display name"""
displayName: String!
- """ Indicates wether or not this user has enabled two factor authentication"
+ """ Indicates wether or not this user has enabled two factor authentication"""
tfa: Boolean!
""" Indicates if this user is an admin of the organization specified in UserList query."""
@@ -1134,7 +1137,7 @@ type UserListItem implements Node {
This custom gql object is used to populate a userPage component in the front end.
"""
type UserPage{
- """ The users email address or userName
+ """ The users email address or userName"""
userName: EmailAddress!
""" The users display name"""
@@ -1146,9 +1149,18 @@ type UserPage{
""" Indicates the preferred language of this user."""
lang: String!
- """ Indicates wether or not this user has enabled two factor authentication"
+ """ Indicates wether or not this user has enabled two factor authentication"""
tfa: Boolean!
""" Indicates if this user is an admin of the organization specified."""
admin: Boolean!
}
+
+"""A Relay edge containing a `UserItem` and its cursor."""
+type UserItemEdge {
+ """The item at the end of the edge"""
+ node: UserListItem!
+
+ """A cursor for use in pagination"""
+ cursor: String!
+}
\ No newline at end of file
diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js
index 300b5a0cd0..a3c829322d 100644
--- a/frontend/src/UserList.js
+++ b/frontend/src/UserList.js
@@ -54,98 +54,84 @@ export function UserList() {
{data
- ? data.user.affiliations.edges[0].node.organization.affiliatedUsers.edges.map(
- (edge) => {
- return (
- {
- history.push({
- pathname: '/user',
- state: { detail: edge.node.user.userName },
- })
- }}
- _hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
- p="30px"
- >
-
-
- {edge.node.user.displayName}
-
-
-
-
- {edge.node.user.userName}
-
-
-
-
- Orgs:
- {// Populate the user-orgs list.
- edge.node.user.affiliations.edges.map((edge, i, arr) => {
- if (arr.length - 1 === i) {
- return (
-
- {edge.node.organization.acronym}
-
- )
- }
- return (
-
- {edge.node.organization.acronym + ' | '}
-
- )
- })}
-
-
-
-
- TwoFactor
-
-
- Admin
-
-
- {
+ return (
+ {
+ history.push({
+ pathname: '/user',
+ state: { detail: edge.node.userName },
+ })
+ }}
+ _hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
+ p="30px"
+ >
+
+
+ {edge.node.displayName}
+
+
+
+
+ {edge.node.userName}
+
+
+
+
-
- )
- },
- )
+ >
+ TwoFactor
+
+
+ Admin
+
+
+
+
+ )
+ })
: null}
)
}
+
+/*
+
+ Orgs:
+ {// Populate the user-orgs list.
+ edge.node.user.affiliations.edges.map((edge, i, arr) => {
+ if (arr.length - 1 === i) {
+ return (
+
+ {edge.node.organization.acronym}
+
+ )
+ }
+ return (
+
+ {edge.node.organization.acronym + ' | '}
+
+ )
+ })}
+
+*/
diff --git a/frontend/src/__tests__/UserList.test.js b/frontend/src/__tests__/UserList.test.js
index f7ce533240..de97229af0 100644
--- a/frontend/src/__tests__/UserList.test.js
+++ b/frontend/src/__tests__/UserList.test.js
@@ -45,74 +45,29 @@ describe('', () => {
},
result: {
data: {
- user: {
- affiliations: {
- edges: [
- {
- node: {
- organization: {
- acronym: 'TEST',
- affiliatedUsers: {
- pageInfo: {
- hasNextPage: true,
- hasPreviousPage: true,
- startCursor: 'string',
- endCursor: 'string',
- },
- edges: [
- {
- node: {
- id: 'NzYzMzQ1MzQ1Ng==',
- user: {
- userName: 'testuser@testemail.ca',
- displayName: 'Test User',
- tfa: false,
- affiliations: {
- edges: [
- {
- node: {
- id: 'ODAyOTY1MDMyMQ==',
- organization: {
- acronym: 'GC',
- },
- permission: 'SUPER_ADMIN',
- },
- },
- {
- node: {
- id: 'NjI3NzcyNDQ=',
- organization: {
- acronym: 'BC',
- },
- permission: 'USER_WRITE',
- },
- },
- {
- node: {
- id: 'NjkyMzAyOTAx',
- organization: {
- acronym: 'BC',
- },
- permission: 'ADMIN',
- },
- },
- ],
- },
- },
- },
- },
- ],
- },
- },
- },
- },
- ],
+ userList: {
+ organization: 'TEST',
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: true,
},
+ edges: [
+ {
+ node: {
+ id: 'ODY0MDEzMTE1NA==',
+ userName: 'testuser@testemail.gc.ca',
+ admin: false,
+ tfa: false,
+ displayName: 'Test User',
+ },
+ },
+ ],
},
},
},
},
]
+
// Set the inital history item to user-list
const { container, queryAllByRole, getByText } = render(
@@ -145,69 +100,23 @@ describe('', () => {
},
result: {
data: {
- user: {
- affiliations: {
- edges: [
- {
- node: {
- organization: {
- acronym: 'TEST',
- affiliatedUsers: {
- pageInfo: {
- hasNextPage: true,
- hasPreviousPage: true,
- startCursor: 'string',
- endCursor: 'string',
- },
- edges: [
- {
- node: {
- id: 'NzYzMzQ1MzQ1Ng==',
- user: {
- userName: 'testuser@testemail.ca',
- displayName: 'Test User',
- tfa: false,
- affiliations: {
- edges: [
- {
- node: {
- id: 'ODAyOTY1MDMyMQ==',
- organization: {
- acronym: 'GC',
- },
- permission: 'SUPER_ADMIN',
- },
- },
- {
- node: {
- id: 'NjI3NzcyNDQ=',
- organization: {
- acronym: 'BC',
- },
- permission: 'USER_WRITE',
- },
- },
- {
- node: {
- id: 'NjkyMzAyOTAx',
- organization: {
- acronym: 'BC',
- },
- permission: 'ADMIN',
- },
- },
- ],
- },
- },
- },
- },
- ],
- },
- },
- },
- },
- ],
+ userList: {
+ organization: 'TEST',
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: true,
},
+ edges: [
+ {
+ node: {
+ id: 'ODY0MDEzMTE1NA==',
+ userName: 'testuser@testemail.gc.ca',
+ admin: false,
+ tfa: false,
+ displayName: 'Test User',
+ },
+ },
+ ],
},
},
},
@@ -262,51 +171,23 @@ describe('', () => {
},
result: {
data: {
- user: {
- affiliations: {
- edges: [
- {
- node: {
- organization: {
- acronym: 'TEST',
- affiliatedUsers: {
- pageInfo: {
- hasNextPage: true,
- hasPreviousPage: true,
- startCursor: 'string',
- endCursor: 'string',
- },
- edges: [
- {
- node: {
- id: 'NzYzMzQ1MzQ1Ng==',
- user: {
- userName: 'testuser@testemail.ca',
- displayName: 'Test User',
- tfa: true,
- affiliations: {
- edges: [
- {
- node: {
- id: 'ODAyOTY1MDMyMQ==',
- organization: {
- acronym: 'GC',
- },
- permission: 'SUPER_ADMIN',
- },
- },
- ],
- },
- },
- },
- },
- ],
- },
- },
- },
- },
- ],
+ userList: {
+ organization: 'TEST',
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: true,
},
+ edges: [
+ {
+ node: {
+ id: 'ODY0MDEzMTE1NA==',
+ userName: 'testuser@testemail.gc.ca',
+ admin: true,
+ tfa: true,
+ displayName: 'Test User',
+ },
+ },
+ ],
},
},
},
@@ -352,57 +233,28 @@ describe('', () => {
},
result: {
data: {
- user: {
- affiliations: {
- edges: [
- {
- node: {
- organization: {
- acronym: 'TEST',
- affiliatedUsers: {
- pageInfo: {
- hasNextPage: true,
- hasPreviousPage: true,
- startCursor: 'string',
- endCursor: 'string',
- },
- edges: [
- {
- node: {
- id: 'NzYzMzQ1MzQ1Ng==',
- user: {
- userName: 'testuser@testemail.ca',
- displayName: 'Test User',
- tfa: false,
- affiliations: {
- edges: [
- {
- node: {
- id: 'ODAyOTY1MDMyMQ==',
- organization: {
- acronym: 'GC',
- },
- permission: 'USER_READ',
- },
- },
- ],
- },
- },
- },
- },
- ],
- },
- },
- },
- },
- ],
+ userList: {
+ organization: 'TEST',
+ pageInfo: {
+ hasNextPage: true,
+ hasPreviousPage: true,
},
+ edges: [
+ {
+ node: {
+ id: 'ODY0MDEzMTE1NA==',
+ userName: 'testuser@testemail.gc.ca',
+ admin: false,
+ tfa: false,
+ displayName: 'Test User',
+ },
+ },
+ ],
},
},
},
},
]
-
const { container, queryAllByRole, getByText } = render(
diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js
index 3ed053db7b..7b37a6e20c 100644
--- a/frontend/src/graphql/queries.js
+++ b/frontend/src/graphql/queries.js
@@ -20,43 +20,19 @@ export const GENERATE_OTP_URL = gql`
export const QUERY_USERLIST = gql`
{
- user {
- affiliations {
- edges {
- node {
- organization {
- acronym
- affiliatedUsers {
- pageInfo {
- hasNextPage
- hasPreviousPage
- startCursor
- endCursor
- }
- edges {
- node {
- id
- user {
- userName
- displayName
- tfa
- affiliations {
- edges {
- node {
- id
- organization {
- acronym
- }
- permission
- }
- }
- }
- }
- }
- }
- }
- }
- }
+ userList(organizaion: "NS") {
+ organization
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ edges {
+ node {
+ id
+ userName
+ admin
+ tfa
+ displayName
}
}
}
From fe81ba46ead82355c532d71a3604f5c14f51d8bf Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Wed, 8 Apr 2020 10:46:00 -0300
Subject: [PATCH 16/20] Add faker values for custom types
---
frontend/schema.faker.graphql | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql
index 5d18a34210..f5cf8a25a4 100644
--- a/frontend/schema.faker.graphql
+++ b/frontend/schema.faker.graphql
@@ -1121,16 +1121,16 @@ type UserListItem implements Node {
id: ID!
""" The users email address or userName """
- userName: EmailAddress!
+ userName: EmailAddress! @fake(type: email)
""" The users display name"""
- displayName: String!
+ displayName: String! @fake(type: firstName)
""" Indicates wether or not this user has enabled two factor authentication"""
- tfa: Boolean!
+ tfa: Boolean! @examples(values: [true, false])
""" Indicates if this user is an admin of the organization specified in UserList query."""
- admin: Boolean!
+ admin: Boolean! @examples(values: [true, false])
}
"""
@@ -1138,22 +1138,22 @@ This custom gql object is used to populate a userPage component in the front end
"""
type UserPage{
""" The users email address or userName"""
- userName: EmailAddress!
+ userName: EmailAddress! @fake(type: email)
""" The users display name"""
- displayName: String!
+ displayName: String! @fake(type: firstName)
""" Indicates which organization this users data is being displayed for."""
- organization: Acronym!
+ organization: Acronym! @examples(values: ["GC", "ABC", "ASDF", "NSTIR", "BC"])
""" Indicates the preferred language of this user."""
- lang: String!
+ lang: String! @examples(values: ["English, "French])
""" Indicates wether or not this user has enabled two factor authentication"""
- tfa: Boolean!
+ tfa: Boolean! @examples(values: [true, false])
""" Indicates if this user is an admin of the organization specified."""
- admin: Boolean!
+ admin: Boolean! @examples(values: [true, false])
}
"""A Relay edge containing a `UserItem` and its cursor."""
From ef99a724a2aff35263ba43733d92773832c5b963 Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Wed, 8 Apr 2020 10:51:46 -0300
Subject: [PATCH 17/20] Use custom object query. Bind admin value to checkbox.
---
frontend/src/UserPage.js | 6 +++---
frontend/src/graphql/queries.js | 9 ++++++---
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/frontend/src/UserPage.js b/frontend/src/UserPage.js
index 14a85a7408..3f2bfea43e 100644
--- a/frontend/src/UserPage.js
+++ b/frontend/src/UserPage.js
@@ -71,8 +71,8 @@ export function UserPage(props) {
{
window.alert('coming soon!!\n' + JSON.stringify(values, null, 2))
@@ -137,7 +137,7 @@ export function UserPage(props) {
Administrative Account
Account Active
diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js
index 7b37a6e20c..9386f8fc88 100644
--- a/frontend/src/graphql/queries.js
+++ b/frontend/src/graphql/queries.js
@@ -40,11 +40,14 @@ export const QUERY_USERLIST = gql`
`
export const QUERY_USER = gql`
- query User($userName: EmailAddress!) {
- user(userName: $userName) {
+ query UserPage($userName: EmailAddress!) {
+ userPage(userName: $userName) {
userName
- displayName
+ tfa
+ organization
+ admin
lang
+ displayName
}
}
`
From 96a17d952282cb78e01d0b229a36fef2cd72c60c Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Thu, 9 Apr 2020 10:09:41 -0300
Subject: [PATCH 18/20] Add prop type string in userPage
---
frontend/src/UserPage.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/UserPage.js b/frontend/src/UserPage.js
index 3f2bfea43e..58d840c55e 100644
--- a/frontend/src/UserPage.js
+++ b/frontend/src/UserPage.js
@@ -4,7 +4,7 @@ import React from 'react'
import { Formik } from 'formik'
import { useHistory } from 'react-router-dom'
-//import { string } from 'prop-types'
+import { string } from 'prop-types'
import {
Stack,
@@ -137,7 +137,7 @@ export function UserPage(props) {
Administrative Account
Account Active
From 15ca9eb4e510bf640710128fc5d481fe2733ed2f Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Thu, 9 Apr 2020 13:41:29 -0300
Subject: [PATCH 19/20] Make additional subtype for admin and org. Use new
subtype in query. Update test and component to use new query.
---
frontend/schema.faker.graphql | 25 +++++++++++++++----------
frontend/src/UserPage.js | 2 +-
frontend/src/__tests__/UserPage.test.js | 4 ++++
frontend/src/graphql/queries.js | 6 ++++--
4 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql
index f5cf8a25a4..b2a7c9bc82 100644
--- a/frontend/schema.faker.graphql
+++ b/frontend/schema.faker.graphql
@@ -1133,6 +1133,15 @@ type UserListItem implements Node {
admin: Boolean! @examples(values: [true, false])
}
+"""A Relay edge containing a `UserItem` and its cursor."""
+type UserItemEdge {
+ """The item at the end of the edge"""
+ node: UserListItem!
+
+ """A cursor for use in pagination"""
+ cursor: String!
+}
+
"""
This custom gql object is used to populate a userPage component in the front end.
"""
@@ -1143,9 +1152,6 @@ type UserPage{
""" The users display name"""
displayName: String! @fake(type: firstName)
- """ Indicates which organization this users data is being displayed for."""
- organization: Acronym! @examples(values: ["GC", "ABC", "ASDF", "NSTIR", "BC"])
-
""" Indicates the preferred language of this user."""
lang: String! @examples(values: ["English, "French])
@@ -1153,14 +1159,13 @@ type UserPage{
tfa: Boolean! @examples(values: [true, false])
""" Indicates if this user is an admin of the organization specified."""
- admin: Boolean! @examples(values: [true, false])
+ userAffiliations: [UserPageAffiliations]!
}
-"""A Relay edge containing a `UserItem` and its cursor."""
-type UserItemEdge {
- """The item at the end of the edge"""
- node: UserListItem!
+type UserPageAffiliations{
+ """ Indicates if this user is an admin of the organization"""
+ admin: Boolean! @examples(values: [true, false])
- """A cursor for use in pagination"""
- cursor: String!
+ """ Indicates which organization this users data is being displayed for."""
+ organization: Acronym! @examples(values: ["GC", "ABC", "ASDF", "NSTIR", "BC"])
}
\ No newline at end of file
diff --git a/frontend/src/UserPage.js b/frontend/src/UserPage.js
index 58d840c55e..a83b1380ee 100644
--- a/frontend/src/UserPage.js
+++ b/frontend/src/UserPage.js
@@ -137,7 +137,7 @@ export function UserPage(props) {
Administrative Account
Account Active
diff --git a/frontend/src/__tests__/UserPage.test.js b/frontend/src/__tests__/UserPage.test.js
index 0cecfd7f02..0068242b51 100644
--- a/frontend/src/__tests__/UserPage.test.js
+++ b/frontend/src/__tests__/UserPage.test.js
@@ -30,6 +30,10 @@ describe('', () => {
userName: 'testuser@testemail.gc.ca',
displayName: 'Test User',
lang: 'English',
+ userAffiliations: {
+ admin: true,
+ organization: 'TEST',
+ },
},
},
},
diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js
index 9386f8fc88..dd18faf930 100644
--- a/frontend/src/graphql/queries.js
+++ b/frontend/src/graphql/queries.js
@@ -44,10 +44,12 @@ export const QUERY_USER = gql`
userPage(userName: $userName) {
userName
tfa
- organization
- admin
lang
displayName
+ userAffiliations{
+ admin
+ organization
+ }
}
}
`
From af4a3f8dd6345b989ed2c68b1c46eb20109c9d6d Mon Sep 17 00:00:00 2001
From: Matt Peachey
Date: Tue, 14 Apr 2020 08:04:01 -0300
Subject: [PATCH 20/20] 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
---
frontend/src/UserCard.js | 58 ++++++++++
frontend/src/UserList.js | 64 ++---------
frontend/src/__tests__/UserCard.test.js | 91 ++++++++++++++++
frontend/src/__tests__/UserList.test.js | 135 +-----------------------
4 files changed, 163 insertions(+), 185 deletions(-)
create mode 100644 frontend/src/UserCard.js
create mode 100644 frontend/src/__tests__/UserCard.test.js
diff --git a/frontend/src/UserCard.js b/frontend/src/UserCard.js
new file mode 100644
index 0000000000..5eea8f8325
--- /dev/null
+++ b/frontend/src/UserCard.js
@@ -0,0 +1,58 @@
+import React from 'react'
+
+import { Badge, Box, Text, PseudoBox } from '@chakra-ui/core'
+import { Trans } from '@lingui/macro'
+
+import { useHistory } from 'react-router-dom'
+
+import { bool, string } from 'prop-types'
+
+export function UserCard(props) {
+ const history = useHistory()
+ return (
+ {
+ history.push({
+ pathname: '/user',
+ state: { detail: props.userName },
+ })
+ }}
+ _hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
+ p="30px"
+ >
+
+
+ {props.displayName}
+
+
+
+
+ {props.userName}
+
+
+
+
+ TwoFactor
+
+
+ Admin
+
+
+
+
+ )
+}
+
+UserCard.propTypes = {
+ displayName: string.isRequired,
+ userName: string.isRequired,
+ admin: bool.isRequired,
+ tfa: bool.isRequired,
+}
diff --git a/frontend/src/UserList.js b/frontend/src/UserList.js
index a3c829322d..76a4065c46 100644
--- a/frontend/src/UserList.js
+++ b/frontend/src/UserList.js
@@ -1,28 +1,23 @@
import React from 'react'
import {
- Badge,
Stack,
SimpleGrid,
Divider,
- Box,
- Text,
Button,
Icon,
InputGroup,
InputLeftElement,
Input,
- PseudoBox,
} from '@chakra-ui/core'
import { Trans } from '@lingui/macro'
import { QUERY_USERLIST } from './graphql/queries'
import { useQuery } from '@apollo/react-hooks'
import { PaginationButtons } from './PaginationButtons'
-import { useHistory } from 'react-router-dom'
+import { UserCard } from './UserCard'
export function UserList() {
- const history = useHistory()
// This function generates the URL when the page loads
const { loading, error, data } = useQuery(QUERY_USERLIST)
if (loading) {
@@ -56,54 +51,13 @@ export function UserList() {
{data
? data.userList.edges.map((edge) => {
return (
- {
- history.push({
- pathname: '/user',
- state: { detail: edge.node.userName },
- })
- }}
- _hover={{ borderColor: 'gray.200', bg: 'gray.200' }}
- p="30px"
- >
-
-
- {edge.node.displayName}
-
-
-
-
- {edge.node.userName}
-
-
-
-
- TwoFactor
-
-
- Admin
-
-
-
-
+ userName={edge.node.userName}
+ tfa={edge.node.tfa}
+ admin={edge.node.admin}
+ displayName={edge.node.displayName}
+ />
)
})
: null}
@@ -115,7 +69,9 @@ export function UserList() {
)
}
-/*
+/* -- Source code for adding organizations, not being used. --
+
+
Orgs:
{// Populate the user-orgs list.
diff --git a/frontend/src/__tests__/UserCard.test.js b/frontend/src/__tests__/UserCard.test.js
new file mode 100644
index 0000000000..5b7ec9385e
--- /dev/null
+++ b/frontend/src/__tests__/UserCard.test.js
@@ -0,0 +1,91 @@
+import React from 'react'
+import { i18n } from '@lingui/core'
+import { render } from '@testing-library/react'
+import { MemoryRouter } from 'react-router-dom'
+import { ThemeProvider, theme } from '@chakra-ui/core'
+import { I18nProvider } from '@lingui/react'
+import { MockedProvider } from '@apollo/react-testing'
+
+import { UserCard } from '../UserCard'
+
+// If this unused import, the mocked data test fails. VERY weird.
+import App from '../App'
+
+describe('', () => {
+ it('successfully renders', async () => {
+ const { container } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+ })
+ it('badges are green when TwoFactor and Admin values are true', async () => {
+ const { container, getByText } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+
+ const tfaBadge = getByText(/TwoFactor/i)
+ const adminBadge = getByText(/Admin/i)
+
+ expect(tfaBadge).toBeDefined()
+ expect(adminBadge).toBeDefined()
+
+ expect(tfaBadge).toHaveStyle('background-color: rgb(198, 246, 213)')
+ expect(adminBadge).toHaveStyle('background-color: rgb(198, 246, 213)')
+ })
+
+ it('badges are red when TwoFactor and Admin values are false', async () => {
+ const { container, getByText } = render(
+
+
+
+
+
+
+
+
+ ,
+ )
+ expect(container).toBeDefined()
+
+ const tfaBadge = getByText(/TwoFactor/i)
+ const adminBadge = getByText(/Admin/i)
+
+ expect(tfaBadge).toBeDefined()
+ expect(adminBadge).toBeDefined()
+
+ expect(tfaBadge).toHaveStyle('background-color: rgb(254, 215, 215)')
+ expect(adminBadge).toHaveStyle('background-color: rgb(254, 215, 215)')
+ })
+})
diff --git a/frontend/src/__tests__/UserList.test.js b/frontend/src/__tests__/UserList.test.js
index de97229af0..2da4791912 100644
--- a/frontend/src/__tests__/UserList.test.js
+++ b/frontend/src/__tests__/UserList.test.js
@@ -3,7 +3,6 @@ import { UserList } from '../UserList'
import { i18n } from '@lingui/core'
import {
render,
- cleanup,
waitForElementToBeRemoved,
fireEvent,
waitFor,
@@ -20,8 +19,6 @@ import { QUERY_USERLIST } from '../graphql/queries'
import App from '../App'
describe('', () => {
- afterEach(cleanup)
-
it('successfully renders', async () => {
const { container } = render(
@@ -69,7 +66,7 @@ describe('', () => {
]
// Set the inital history item to user-list
- const { container, queryAllByRole, getByText } = render(
+ const { container, getAllByText, getByText } = render(
@@ -88,7 +85,7 @@ describe('', () => {
await waitForElementToBeRemoved(loadingElement)
// Get all of the mocked user cards, and expect there to be only one entry.
- const userCards = queryAllByRole('userCard')
+ const userCards = getAllByText('testuser@testemail.gc.ca')
expect(userCards).toHaveLength(1)
})
@@ -131,7 +128,7 @@ describe('', () => {
})
// Set the inital history item to user-list
- const { container, queryAllByRole, getByText } = render(
+ const { container, getAllByText, getByText } = render(
@@ -150,7 +147,7 @@ describe('', () => {
await waitForElementToBeRemoved(loadingElement)
// Get all of the mocked user cards, and expect there to be only one entry.
- const userCards = queryAllByRole('userCard')
+ const userCards = getAllByText('testuser@testemail.gc.ca')
expect(userCards).toHaveLength(1)
const leftClick = { button: 0 }
@@ -162,128 +159,4 @@ describe('', () => {
expect(history.location.pathname).toEqual('/user')
})
})
-
- it('badges are green when TwoFactor and Admin values are true', async () => {
- const mocks = [
- {
- request: {
- query: QUERY_USERLIST,
- },
- result: {
- data: {
- userList: {
- organization: 'TEST',
- pageInfo: {
- hasNextPage: true,
- hasPreviousPage: true,
- },
- edges: [
- {
- node: {
- id: 'ODY0MDEzMTE1NA==',
- userName: 'testuser@testemail.gc.ca',
- admin: true,
- tfa: true,
- displayName: 'Test User',
- },
- },
- ],
- },
- },
- },
- },
- ]
-
- const { container, queryAllByRole, getByText } = render(
-
-
-
-
-
-
-
-
- ,
- )
- expect(container).toBeDefined()
-
- expect(getByText('Loading...')).toBeInTheDocument()
- const loadingElement = getByText('Loading...')
-
- await waitForElementToBeRemoved(loadingElement)
-
- // Get all of the mocked user cards, and expect there to be only one entry.
- const userCards = queryAllByRole('userCard')
- expect(userCards).toHaveLength(1)
-
- const tfaBadge = getByText(/TwoFactor/i)
- const adminBadge = getByText(/Admin/i)
-
- expect(tfaBadge).toBeDefined()
- expect(adminBadge).toBeDefined()
-
- expect(tfaBadge).toHaveStyle('background-color: rgb(198, 246, 213)')
- expect(adminBadge).toHaveStyle('background-color: rgb(198, 246, 213)')
- })
- it('badges are red when TwoFactor and Admin values are false', async () => {
- const mocks = [
- {
- request: {
- query: QUERY_USERLIST,
- },
- result: {
- data: {
- userList: {
- organization: 'TEST',
- pageInfo: {
- hasNextPage: true,
- hasPreviousPage: true,
- },
- edges: [
- {
- node: {
- id: 'ODY0MDEzMTE1NA==',
- userName: 'testuser@testemail.gc.ca',
- admin: false,
- tfa: false,
- displayName: 'Test User',
- },
- },
- ],
- },
- },
- },
- },
- ]
- const { container, queryAllByRole, getByText } = render(
-
-
-
-
-
-
-
-
- ,
- )
- expect(container).toBeDefined()
-
- expect(getByText('Loading...')).toBeInTheDocument()
- const loadingElement = getByText('Loading...')
-
- await waitForElementToBeRemoved(loadingElement)
-
- // Get all of the mocked user cards, and expect there to be only one entry.
- const userCards = queryAllByRole('userCard')
- expect(userCards).toHaveLength(1)
-
- const tfaBadge = getByText(/TwoFactor/i)
- const adminBadge = getByText(/Admin/i)
-
- expect(tfaBadge).toBeDefined()
- expect(adminBadge).toBeDefined()
-
- expect(tfaBadge).toHaveStyle('background-color: rgb(254, 215, 215)')
- expect(adminBadge).toHaveStyle('background-color: rgb(254, 215, 215)')
- })
})