From 87da966a3037a7d3406ddf4846b064c463f88fbb Mon Sep 17 00:00:00 2001 From: lcampbell2 Date: Tue, 7 Jul 2020 10:30:43 -0300 Subject: [PATCH 1/3] added summary tables to orgDetails --- frontend/schema.faker.graphql | 8 -- frontend/src/App.js | 3 - frontend/src/LandingPage.js | 3 +- frontend/src/Organization.js | 10 +- frontend/src/OrganizationDetails.js | 96 ++++++++++---- frontend/src/Organizations.js | 17 ++- frontend/src/SummaryTable.js | 121 ++++++++++-------- .../src/__tests__/OrganizationDetails.test.js | 10 +- frontend/src/__tests__/SummaryTable.test.js | 8 ++ frontend/src/locales/en.po | 20 +++ frontend/src/locales/fr.po | 20 +++ frontend/src/makeSummaryTableData.js | 80 +++++++++--- 12 files changed, 284 insertions(+), 112 deletions(-) diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql index 8a67f94eb..35e59a259 100755 --- a/frontend/schema.faker.graphql +++ b/frontend/schema.faker.graphql @@ -1276,12 +1276,6 @@ type PolicyPublished { fo: Int @examples(values: [0]) } -type SummaryCategory { - name: String @examples(values: ["strong", "moderate", "weak"]) - count: Int @examples(values: [33, 33, 33]) - percentage: Int @examples(values: [33, 33, 33]) -} - type CategorizedSummary { categories: [SummaryCategory] total: Int @examples(values: [100]) @@ -1291,8 +1285,6 @@ type CategorizedSummary { The central gathering point for all of the GraphQL queries. """ type Query { - webSummary: CategorizedSummary - emailSummary: CategorizedSummary """ A query object used to grab the data to create dmarc report bar graph. """ diff --git a/frontend/src/App.js b/frontend/src/App.js index 2071d8cb0..6ad96d8fa 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -64,9 +64,6 @@ export default function App() { Sign In )} - {/* - User List - */} Report diff --git a/frontend/src/LandingPage.js b/frontend/src/LandingPage.js index d1de7e1d8..389121d92 100644 --- a/frontend/src/LandingPage.js +++ b/frontend/src/LandingPage.js @@ -1,7 +1,7 @@ import React from 'react' import { Trans } from '@lingui/macro' import { Layout } from './Layout' -import { Heading, Text, Stack } from '@chakra-ui/core' +import { Heading, Text, Stack, Divider } from '@chakra-ui/core' import { SummaryGroup } from './SummaryGroup' export function LandingPage() { @@ -27,6 +27,7 @@ export function LandingPage() { + *All data represented is mocked for demonstration purposes diff --git a/frontend/src/Organization.js b/frontend/src/Organization.js index ef4e613f1..7d0868c59 100644 --- a/frontend/src/Organization.js +++ b/frontend/src/Organization.js @@ -8,9 +8,13 @@ export function Organization({ name, slug, domainCount, ...rest }) { return ( - - {name} - + + + + {name} + + + Internet facing services: {domainCount} diff --git a/frontend/src/OrganizationDetails.js b/frontend/src/OrganizationDetails.js index 0e8cf71de..ed2912a90 100644 --- a/frontend/src/OrganizationDetails.js +++ b/frontend/src/OrganizationDetails.js @@ -2,13 +2,21 @@ import React from 'react' import { useQuery } from '@apollo/react-hooks' import { t, Trans } from '@lingui/macro' import { Layout } from './Layout' -import { ListOf } from './ListOf' -import { Domain } from './Domain' -import { Link, Icon, Heading, Stack, useToast } from '@chakra-ui/core' +import { + Link, + Icon, + Heading, + Stack, + useToast, + Divider, + Text, +} from '@chakra-ui/core' import { ORGANIZATION_BY_SLUG } from './graphql/queries' import { useLingui } from '@lingui/react' import { useUserState } from './UserState' import { Link as ReactRouterLink, useParams } from 'react-router-dom' +import SummaryTable from './SummaryTable' +import makeSummaryTableData from './makeSummaryTableData' export default function OrganizationDetails() { const { i18n } = useLingui() @@ -34,9 +42,9 @@ export default function OrganizationDetails() { }, }) - let domains = [] + let domainName = '' if (data && data.organization.domains.edges) { - domains = data.organization.domains.edges.map((e) => e.node) + domainName = data.organization.name } if (loading) { @@ -46,10 +54,55 @@ export default function OrganizationDetails() {

) } + + const columns = [ + { + Header: i18n._(t`Domain`), + accessor: 'host_domain', + }, + { + Header: 'HTTPS', + accessor: 'https_result', + }, + { + Header: 'HSTS', + accessor: 'hsts_result', + }, + { + Header: i18n._(t`HSTS Preloaded`), + accessor: 'preloaded_result', + }, + { + Header: 'SSL', + accessor: 'ssl_result', + }, + { + Header: i18n._(t`Protocols & Ciphers`), + accessor: 'protocol_cipher_result', + }, + { + Header: i18n._(t`Certificate Use`), + accessor: 'cert_use_result', + }, + { + Header: 'SPF', + accessor: 'spf_result', + }, + { + Header: 'DKIM', + accessor: 'dkim_result', + }, + { + Header: 'DMARC', + accessor: 'dmarc_result', + }, + ] + + const tableEntries = Math.floor(Math.random() * 20) return ( - + - {data.organization.name} + {domainName} - - - No domains yet.} - > - {({ url, lastRan }, index) => ( - - )} - - + + {tableEntries > 0 ? ( + + ) : ( + + No domains yet. + + )} + + + *All data represented is mocked for demonstration purposes ) } diff --git a/frontend/src/Organizations.js b/frontend/src/Organizations.js index 83e7c7b35..dbb8a3a2d 100644 --- a/frontend/src/Organizations.js +++ b/frontend/src/Organizations.js @@ -3,7 +3,7 @@ import { useQuery } from '@apollo/react-hooks' import { Trans } from '@lingui/macro' import { Layout } from './Layout' import { ListOf } from './ListOf' -import { Heading, Stack, useToast } from '@chakra-ui/core' +import { Heading, Stack, useToast, Box, Divider } from '@chakra-ui/core' import { ORGANIZATIONS } from './graphql/queries' import { useUserState } from './UserState' import { Organization } from './Organization' @@ -55,12 +55,15 @@ export default function Organisations() { ifEmpty={() => No Organizations} > {({ name, slug, domainCount }, index) => ( - + + + + )}
diff --git a/frontend/src/SummaryTable.js b/frontend/src/SummaryTable.js index b6fe36dd0..920364182 100644 --- a/frontend/src/SummaryTable.js +++ b/frontend/src/SummaryTable.js @@ -2,11 +2,10 @@ import React from 'react' import styled from '@emotion/styled' import { useTable, usePagination } from 'react-table' import { array } from 'prop-types' -import { Box, Text, Button, Stack, Select, Input } from '@chakra-ui/core' - +import { Trans } from '@lingui/macro' +import { Box, Text, Stack, Select, Input, IconButton } from '@chakra-ui/core' import WithPseudoBox from './withPseudoBox' -// TODO: replace with values from the theme const Table = styled.table` & { width: 100%; @@ -24,6 +23,10 @@ const Table = styled.table` text-align: center; } + .pagination { + padding: 0.5rem; + } + .title { text-align: center; } @@ -75,6 +78,10 @@ const Table = styled.table` function SummaryTable({ ...props }) { const { data, columns } = props + const defaultPageSize = window.matchMedia('screen and (max-width: 760px)') + .matches + ? 5 + : 10 const { getTableProps, @@ -96,18 +103,14 @@ function SummaryTable({ ...props }) { { columns, data, - initialState: { pageIndex: 0 }, + initialState: { pageIndex: 0, pageSize: defaultPageSize }, }, usePagination, ) return ( - - - - Domain Summary Table - -
+ + {headerGroups.map((headerGroup, idx) => ( @@ -142,46 +145,64 @@ function SummaryTable({ ...props }) {
- - {' '} - {' '} - {' '} - {' '} - - Page {pageIndex + 1} of {pageOptions.length}{' '} - - | Go to page: - { - const page = e.target.value ? Number(e.target.value) - 1 : 0 - gotoPage(page) - }} - />{' '} - - -
+ + + + gotoPage(0)} + disabled={!canPreviousPage} + /> + previousPage()} + disabled={!canPreviousPage} + /> + nextPage()} + disabled={!canNextPage} + /> + gotoPage(pageCount - 1)} + disabled={!canNextPage} + /> + + Page {pageIndex + 1} of{' '} + {pageOptions.length}{' '} + + + + + Go to page: + + { + const page = e.target.value ? Number(e.target.value) - 1 : 0 + gotoPage(page) + }} + />{' '} + + + + + ) } diff --git a/frontend/src/__tests__/OrganizationDetails.test.js b/frontend/src/__tests__/OrganizationDetails.test.js index a6a2712e0..bb1a3b898 100644 --- a/frontend/src/__tests__/OrganizationDetails.test.js +++ b/frontend/src/__tests__/OrganizationDetails.test.js @@ -42,6 +42,15 @@ describe('', () => { }, }, ] + + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + })), + }) + const { getByText } = render( @@ -67,7 +76,6 @@ describe('', () => { await waitFor(() => { expect(getByText(name)).toBeInTheDocument() - expect(getByText(/2020-06-18T00:42:12.414Z/)).toBeInTheDocument() }) }) }) diff --git a/frontend/src/__tests__/SummaryTable.test.js b/frontend/src/__tests__/SummaryTable.test.js index 8c3e10cd9..ac506f2ab 100644 --- a/frontend/src/__tests__/SummaryTable.test.js +++ b/frontend/src/__tests__/SummaryTable.test.js @@ -5,6 +5,14 @@ import { I18nProvider } from '@lingui/react' import { setupI18n } from '@lingui/core' import SummaryTable from '../SummaryTable' +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + })), +}) + describe('', () => { it('renders correctly', async () => { const columns = [{ Header: 'header', accessor: 'accessor' }] diff --git a/frontend/src/locales/en.po b/frontend/src/locales/en.po index 5e65b5a5b..2e3f6ebc1 100644 --- a/frontend/src/locales/en.po +++ b/frontend/src/locales/en.po @@ -642,3 +642,23 @@ msgstr "back to organizations" #: src/OrganizationDetails.js:62 msgid "{0}" msgstr "{0}" + +#: src/OrganizationDetails.js:60 +msgid "Domain" +msgstr "Domain" + +#: src/OrganizationDetails.js:60 +msgid "HSTS Preloaded" +msgstr "HSTS Preloaded" + +#: src/OrganizationDetails.js:60 +msgid "Protocols & Ciphers" +msgstr "Protocols & Ciphers" + +#: src/OrganizationDetails.js:60 +msgid "Certificate Use" +msgstr "Certificate Use" + +#: src/SummaryTable.js:174 +msgid "of" +msgstr "of" diff --git a/frontend/src/locales/fr.po b/frontend/src/locales/fr.po index 827509d00..9b12875af 100644 --- a/frontend/src/locales/fr.po +++ b/frontend/src/locales/fr.po @@ -642,3 +642,23 @@ msgstr "retour aux organisations" #: src/OrganizationDetails.js:62 msgid "{0}" msgstr "" + +#: src.OrganizationDetails.js:60 +msgid "Domain" +msgstr "Domaine" + +#: src.OrganizationDetails.js:60 +msgid "HSTS Preloaded" +msgstr "HSTS Préchargé" + +#: src.OrganizationDetails.js:60 +msgid "Protocols & Ciphers" +msgstr "Protocoles et Codes" + +#: src.OrganizationDetails.js:60 +msgid "Certificate Use" +msgstr "Utilisation des Certificats" + +#: src/SummaryTable.js:174 +msgid "of" +msgstr "de" diff --git a/frontend/src/makeSummaryTableData.js b/frontend/src/makeSummaryTableData.js index c8f5459e7..16b1265a6 100644 --- a/frontend/src/makeSummaryTableData.js +++ b/frontend/src/makeSummaryTableData.js @@ -38,22 +38,10 @@ const generateEmailStatusIcon = () => { return statusIcon } -const domainNames = [ - 'cyber.gc.ca', - 'tbs-sct.gc.ca', - 'canada.ca', - 'cra-arc.gc.ca', - 'pm.gc.ca', - 'cse-cst.gc.ca', - 'forces.gc.ca', - 'faker.gc.ca', - 'rcmp-grc.gc.ca', -] - -const newDomain = () => { - const ind = Math.floor(Math.random() * 9) +const newDomain = (names) => { + const ind = Math.floor(Math.random() * 13) return { - host_domain: domainNames[ind], + host_domain: names[ind], https_result: generateWebStatusIcon(), hsts_result: generateWebStatusIcon(), preloaded_result: generateWebStatusIcon(), @@ -66,12 +54,70 @@ const newDomain = () => { } } -export default function makeSummaryTableData(...lens) { +export default function MakeSummaryTableData(...lens) { + // const domainNames = [ + // + // cyber.gc.ca + // , + // + // tbs-sct.gc.ca + // , + // + // canada.ca + // , + // + // cra-arc.gc.ca + // , + // + // pm.gc.ca + // , + // + // cse-cst.gc.ca + // , + // + // forces.gc.ca + // , + // + // faker.gc.ca + // , + // + // rcmp-grc.gc.ca + // , + // + // hc-sc.gc.ca + // , + // + // dfait-maeci.gc.ca + // , + // + // ec.gc.ca + // , + // + // dfo-mpo.gc.ca + // , + // ] + + const domainNames = [ + 'cyber.gc.ca', + 'tbs-sct.gc.ca', + 'canada.ca', + 'cra-arc.gc.ca', + 'pm.gc.ca', + 'cse-cst.gc.ca', + 'forces.gc.ca', + 'faker.gc.ca', + 'rcmp-grc.gc.ca', + 'hc-sc.gc.ca', + 'dfait-maeci.gc.ca', + 'ec.gc.ca', + 'dfo-mpo.gc.ca', + ] + const makeDataLevel = (depth = 0) => { const len = lens[depth] return range(len).map(() => { return { - ...newDomain(), + ...newDomain(domainNames), subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined, } }) From aa3f7441304b342015a50121e7d9c90a36cca333 Mon Sep 17 00:00:00 2001 From: lcampbell2 Date: Wed, 8 Jul 2020 10:09:36 -0300 Subject: [PATCH 2/3] updated faker --- frontend/schema.faker.graphql | 122 ++++++++++++++++------------------ 1 file changed, 58 insertions(+), 64 deletions(-) diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql index 35e59a259..1cd3afc6e 100755 --- a/frontend/schema.faker.graphql +++ b/frontend/schema.faker.graphql @@ -411,27 +411,6 @@ type DmarcReportDetailTables { detailTables: DetailTables } -""" -Input object containing fields which map to the required arguments for -dmarcReportDetailTablesInput -""" -input DmarcReportDetailTablesInput { - """ - The slugified version of the domain you wish to retrieve data for. - """ - domainSlug: Slug! - - """ - The period in which the returned data is relevant to. - """ - period: PeriodEnums! - - """ - The year in which the returned data is relevant to. - """ - year: Year! -} - """ A query object used to grab the data to create dmarc report doughnuts """ @@ -452,21 +431,6 @@ type DmarcReportSummary { categoryTotals: CategoryTotals } -""" -This object is used to define the various arguments used in the dmarc report -summary query -""" -input DmarcReportSummaryInput { - """The slugified version of the domain you wish to retrieve data for.""" - domainSlug: Slug! - - """The period in which the returned data is relevant to.""" - period: PeriodEnums! - - """The year in which the returned data is relevant to.""" - year: Year! -} - """ A query object used to grab the data to create dmarc report bar graph """ @@ -487,15 +451,6 @@ type DmarcReportSummaryList { categoryTotals: CategoryTotals } -""" -This object is used to define the various arguments used in the dmarc report -summary list query -""" -input DmarcReportSummaryListInput { - """The slugified version of the domain you wish to retrieve data for.""" - domainSlug: Slug! -} - type Domain implements Node { organization: Organizations @@ -1276,11 +1231,6 @@ type PolicyPublished { fo: Int @examples(values: [0]) } -type CategorizedSummary { - categories: [SummaryCategory] - total: Int @examples(values: [100]) -} - """ The central gathering point for all of the GraphQL queries. """ @@ -1290,9 +1240,9 @@ type Query { """ dmarcReportSummaryList( """ - Input argument with various input fields required for the dmarc report summary list query + The slugified version of the domain you wish to retrieve data for. """ - input: DmarcReportSummaryListInput! + domainSlug: Slug! ): [DmarcReportSummaryList] @listLength(min: 13, max: 13) """ @@ -1300,9 +1250,9 @@ type Query { """ demoDmarcReportSummaryList( """ - Input argument with various input fields required for the dmarc report summary list query + The slugified version of the domain you wish to retrieve data for. """ - input: DmarcReportSummaryListInput! + domainSlug: Slug! ): [DmarcReportSummaryList] @listLength(min: 13, max: 13) """ @@ -1310,9 +1260,19 @@ type Query { """ dmarcReportDetailTables( """ - Input object containing fields which map to the required arguments for dmarcReportDetailTablesInput + The slugified version of the domain you wish to retrieve data for. + """ + domainSlug: Slug! + """ - input: DmarcReportDetailTablesInput! + The period in which the returned data is relevant to. + """ + period: PeriodEnums! + + """ + The year in which the returned data is relevant to. + """ + year: Year! ): DmarcReportDetailTables """ @@ -1320,25 +1280,59 @@ type Query { """ demoDmarcReportDetailTables( """ - Input object containing fields which map to the required arguments for dmarcReportDetailTablesInput + The slugified version of the domain you wish to retrieve data for. + """ + domainSlug: Slug! + + """ + The period in which the returned data is relevant to. """ - input: DmarcReportDetailTablesInput! + period: PeriodEnums! + + """ + The year in which the returned data is relevant to. + """ + year: Year! ): DmarcReportDetailTables - """A query object used to grab the data to create dmarc report doughnuts""" + """ + A query object used to grab the data to create dmarc report doughnuts + """ dmarcReportSummary( """ - Input argument with various input fields required for the dmarc report summary query + The slugified version of the domain you wish to retrieve data for. + """ + domainSlug: Slug! + + """ + The period in which the returned data is relevant to. + """ + period: PeriodEnums! + """ - input: DmarcReportSummaryInput! + The year in which the returned data is relevant to. + """ + year: Year! ): DmarcReportSummary - """A query object used to grab the data to create dmarc report doughnuts""" + """ + A query object used to grab the data to create dmarc report doughnuts + """ demoDmarcReportSummary( """ - Input argument with various input fields required for the dmarc report summary query + The slugified version of the domain you wish to retrieve data for. + """ + domainSlug: Slug! + + """ + The period in which the returned data is relevant to. + """ + period: PeriodEnums! + + """ + The year in which the returned data is relevant to. """ - input: DmarcReportSummaryInput! + year: Year! ): DmarcReportSummary """ From aa00b8119d005588d06838cf0cd5942884987c82 Mon Sep 17 00:00:00 2001 From: lcampbell2 Date: Thu, 9 Jul 2020 08:12:59 -0300 Subject: [PATCH 3/3] updated faker --- frontend/schema.faker.graphql | 65 +++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/frontend/schema.faker.graphql b/frontend/schema.faker.graphql index 1cd3afc6e..02a9070b6 100755 --- a/frontend/schema.faker.graphql +++ b/frontend/schema.faker.graphql @@ -729,11 +729,6 @@ enum LanguageEnums { The central gathering point for all of the GraphQL mutations. """ type Mutation { - updatePassword( - confirmPassword: String! - password: String! - userName: EmailAddress! - ): UpdateUserPassword authenticateTwoFactor( otpCode: String! userName: EmailAddress! @@ -873,6 +868,26 @@ type Mutation { """ userName: EmailAddress! ): SendEmailVerification + + """ + Allows users to update their password, using a token sent to them via email. + """ + updatePassword( + """ + An input object containing all fields for this mutation. + """ + input: UpdateUserPasswordInput! + ): UpdateUserPassword + + """ + Allows the user to request an email to reset their account password. + """ + sendPasswordResetLink( + """ + User name for the account you would like to receive a password reset link for. + """ + userName: EmailAddress! + ): SendPasswordResetLink } """ @@ -1560,6 +1575,17 @@ type SendEmailVerification { status: Boolean } +""" +This mutation allows a user to provide their username and request that a +password reset email be sent to their account with a reset token in a url. +""" +type SendPasswordResetLink { + """ + Status of a successful password reset email. + """ + status: String +} + """ This method allows for new users to sign up for our sites services. """ @@ -1828,8 +1854,35 @@ input UpdateOrganizationInput { city: String } +""" +This mutation allows users to use a token sent to them by email to update +their account password. +""" type UpdateUserPassword { - user: User + """ + Informs the user if the password update was successful. + """ + status: String +} + +""" +Input object containing all fields for the UpdateUserPassword mutation +""" +input UpdateUserPasswordInput { + """ + Reset token found in the url, which was sent to the users inbox, by the sendPasswordReset mutation. + """ + resetToken: String! + + """ + The new password the user wishes to change to. + """ + password: String! + + """ + A confirmation of the password field to ensure the user has entered the new password correctly. + """ + confirmPassword: String! } type UpdateUserRole {