diff --git a/api/src/organization/loaders/load-organization-domain-statuses.js b/api/src/organization/loaders/load-organization-domain-statuses.js
index dda3bde6a..62d070ef4 100644
--- a/api/src/organization/loaders/load-organization-domain-statuses.js
+++ b/api/src/organization/loaders/load-organization-domain-statuses.js
@@ -1,35 +1,112 @@
import { t } from '@lingui/macro'
+import { aql } from 'arangojs'
export const loadOrganizationDomainStatuses =
- ({ query, userKey, i18n }) =>
- async ({ orgId, blocked }) => {
+ ({ query, userKey, i18n, language }) =>
+ async ({ orgId, filters }) => {
let domains
+ let domainFilters = aql`FILTER v.archived != true`
+ if (typeof filters !== 'undefined') {
+ filters.forEach(({ filterCategory, comparison, filterValue }) => {
+ if (comparison === '==') {
+ comparison = aql`==`
+ } else {
+ comparison = aql`!=`
+ }
+ if (filterCategory === 'dmarc-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.dmarc ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'dkim-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.dkim ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'https-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.https ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'spf-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.spf ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'ciphers-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.ciphers ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'curves-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.curves ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'hsts-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.hsts ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'protocols-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.protocols ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'certificates-status') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.status.certificates ${comparison} ${filterValue}
+ `
+ } else if (filterCategory === 'tags') {
+ if (filterValue === 'hidden') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER e.hidden ${comparison} true
+ `
+ } else if (filterValue === 'nxdomain') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.rcode ${comparison} "NXDOMAIN"
+ `
+ } else if (filterValue === 'blocked') {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER v.blocked ${comparison} true
+ `
+ } else {
+ domainFilters = aql`
+ ${domainFilters}
+ FILTER POSITION(claimTags, ${filterValue}) ${comparison} true
+ `
+ }
+ }
+ })
+ }
try {
- if (blocked) {
- domains = (
- await query`
- WITH claims, domains, organizations
- FOR v, e IN 1..1 OUTBOUND ${orgId} claims
- FILTER v.blocked == true
- RETURN {
- domain: v.domain,
- status: v.status
- }
- `
- ).all()
- } else {
- domains = (
- await query`
+ domains = (
+ await query`
WITH claims, domains, organizations
FOR v, e IN 1..1 OUTBOUND ${orgId} claims
+ LET claimTags = (
+ LET translatedTags = (
+ FOR tag IN e.tags || []
+ RETURN TRANSLATE(${language}, tag)
+ )
+ RETURN translatedTags
+ )[0]
+ ${domainFilters}
RETURN {
domain: v.domain,
- status: v.status
+ status: v.status,
+ tags: claimTags,
+ hidden: e.hidden,
+ rcode: v.rcode,
+ blocked: v.blocked,
}
`
- ).all()
- }
+ ).all()
} catch (err) {
console.error(`Database error occurred when user: ${userKey} running loadOrganizationDomainStatuses: ${err}`)
throw new Error(i18n._(t`Unable to load organization domain statuses. Please try again.`))
diff --git a/api/src/organization/objects/organization.js b/api/src/organization/objects/organization.js
index ab10fc726..162ca5b86 100644
--- a/api/src/organization/objects/organization.js
+++ b/api/src/organization/objects/organization.js
@@ -75,9 +75,9 @@ export const organizationType = new GraphQLObjectType({
description:
'CSV formatted output of all domains in the organization including their email and web scan statuses.',
args: {
- blocked: {
- type: GraphQLBoolean,
- description: 'Filters domains by blocked status.',
+ filters: {
+ type: new GraphQLList(domainFilter),
+ description: 'Filters used to limit domains returned.',
},
},
resolve: async (
@@ -119,13 +119,18 @@ export const organizationType = new GraphQLObjectType({
'spf',
'dkim',
'dmarc',
+ 'tags',
+ 'hidden',
+ 'rcode',
+ 'blocked',
]
let csvOutput = headers.join(',')
domains.forEach((domain) => {
let csvLine = `${domain.domain}`
- csvLine += headers.slice(1).reduce((previousValue, currentHeader) => {
+ csvLine += headers.slice(1, 10).reduce((previousValue, currentHeader) => {
return `${previousValue},${domain.status[currentHeader]}`
}, '')
+ csvLine += `,${domain.tags.join('|')},${domain.hidden},${domain.rcode},${domain.blocked}`
csvOutput += `\n${csvLine}`
})
diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js
index 3c5d781d6..7b4de63a6 100644
--- a/frontend/src/graphql/queries.js
+++ b/frontend/src/graphql/queries.js
@@ -95,9 +95,9 @@ export const LANDING_PAGE_SUMMARIES = gql`
`
export const GET_ORGANIZATION_DOMAINS_STATUSES_CSV = gql`
- query GetOrganizationDomainsStatusesCSV($orgSlug: Slug!) {
+ query GetOrganizationDomainsStatusesCSV($orgSlug: Slug!, $filters: [DomainFilter]) {
findOrganizationBySlug(orgSlug: $orgSlug) {
- toCsv
+ toCsv(filters: $filters)
}
}
`
diff --git a/frontend/src/organizationDetails/OrganizationDetails.js b/frontend/src/organizationDetails/OrganizationDetails.js
index 92d0a02c9..33e020066 100644
--- a/frontend/src/organizationDetails/OrganizationDetails.js
+++ b/frontend/src/organizationDetails/OrganizationDetails.js
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react'
-import { useLazyQuery, useQuery } from '@apollo/client'
+import { useQuery } from '@apollo/client'
import { Trans } from '@lingui/macro'
import {
Box,
@@ -27,9 +27,8 @@ import { TieredSummaries } from '../summaries/TieredSummaries'
import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage'
import { LoadingMessage } from '../components/LoadingMessage'
import { useDocumentTitle } from '../utilities/useDocumentTitle'
-import { GET_ORGANIZATION_DOMAINS_STATUSES_CSV, ORG_DETAILS_PAGE } from '../graphql/queries'
+import { ORG_DETAILS_PAGE } from '../graphql/queries'
import { RadialBarChart } from '../summaries/RadialBarChart'
-import { ExportButton } from '../components/ExportButton'
import { RequestOrgInviteModal } from '../organizations/RequestOrgInviteModal'
import { useUserVar } from '../utilities/userState'
@@ -48,13 +47,6 @@ export default function OrganizationDetails() {
// errorPolicy: 'ignore', // allow partial success
})
- const [getOrgDomainStatuses, { loading: orgDomainStatusesLoading, _error, _data }] = useLazyQuery(
- GET_ORGANIZATION_DOMAINS_STATUSES_CSV,
- {
- variables: { orgSlug: orgSlug },
- },
- )
-
useEffect(() => {
if (!activeTab) {
history.replace(`/organizations/${orgSlug}/${defaultActiveTab}`)
@@ -160,20 +152,11 @@ export default function OrganizationDetails() {
- {data?.organization?.userHasPermission && (
- {
- const result = await getOrgDomainStatuses()
- return result.data?.findOrganizationBySlug?.toCsv
- }}
- isLoading={orgDomainStatusesLoading}
- />
- )}
-
+
{!isNaN(data?.organization?.affiliations?.totalCount) && (
diff --git a/frontend/src/organizationDetails/OrganizationDomains.js b/frontend/src/organizationDetails/OrganizationDomains.js
index 054788814..87e74ee05 100644
--- a/frontend/src/organizationDetails/OrganizationDomains.js
+++ b/frontend/src/organizationDetails/OrganizationDomains.js
@@ -13,7 +13,7 @@ import {
useDisclosure,
} from '@chakra-ui/react'
import { ErrorBoundary } from 'react-error-boundary'
-import { number, string } from 'prop-types'
+import { bool, string } from 'prop-types'
import { DomainCard } from '../domains/DomainCard'
import { ListOf } from '../components/ListOf'
@@ -23,13 +23,19 @@ import { RelayPaginationControls } from '../components/RelayPaginationControls'
import { InfoBox, InfoPanel } from '../components/InfoPanel'
import { usePaginatedCollection } from '../utilities/usePaginatedCollection'
import { useDebouncedFunction } from '../utilities/useDebouncedFunction'
-import { PAGINATED_ORG_DOMAINS as FORWARD, MY_TRACKER_DOMAINS } from '../graphql/queries'
+import {
+ PAGINATED_ORG_DOMAINS as FORWARD,
+ GET_ORGANIZATION_DOMAINS_STATUSES_CSV,
+ MY_TRACKER_DOMAINS,
+} from '../graphql/queries'
import { SearchBox } from '../components/SearchBox'
import { Formik } from 'formik'
import { getRequirement, schemaToValidation } from '../utilities/fieldRequirements'
import { CheckCircleIcon, InfoIcon, WarningIcon } from '@chakra-ui/icons'
+import { useLazyQuery } from '@apollo/client'
+import { ExportButton } from '../components/ExportButton'
-export function OrganizationDomains({ orgSlug }) {
+export function OrganizationDomains({ orgSlug, orgName, userHasPermission }) {
const [orderDirection, setOrderDirection] = useState('ASC')
const [orderField, setOrderField] = useState('DOMAIN')
const [searchTerm, setSearchTerm] = useState('')
@@ -72,6 +78,13 @@ export function OrganizationDomains({ orgSlug }) {
nextFetchPolicy: 'cache-first',
})
+ const [getOrgDomainStatuses, { loading: orgDomainStatusesLoading, _error, _data }] = useLazyQuery(
+ GET_ORGANIZATION_DOMAINS_STATUSES_CSV,
+ {
+ variables: { orgSlug, filters },
+ },
+ )
+
const { isOpen, onToggle } = useDisclosure()
if (error) return
@@ -274,6 +287,19 @@ export function OrganizationDomains({ orgSlug }) {
return (
+ {userHasPermission && (
+ {
+ const result = await getOrgDomainStatuses()
+ return result.data?.findOrganizationBySlug?.toCsv
+ }}
+ isLoading={orgDomainStatusesLoading}
+ />
+ )}
{/* Web statuses */}
@@ -401,4 +427,4 @@ export function OrganizationDomains({ orgSlug }) {
)
}
-OrganizationDomains.propTypes = { domainsPerPage: number, orgSlug: string }
+OrganizationDomains.propTypes = { orgSlug: string, orgName: string, userHasPermission: bool }