From fdf56cd53b79cf3c3710d270114297f932cc5e9b Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 19 Feb 2021 08:36:39 -0400 Subject: [PATCH 1/4] create verified domain ordering enum --- api-js/src/enums/index.js | 1 + .../src/enums/verified-domain-order-field.js | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 api-js/src/enums/verified-domain-order-field.js diff --git a/api-js/src/enums/index.js b/api-js/src/enums/index.js index 091b605af0..4ad9fabd8b 100644 --- a/api-js/src/enums/index.js +++ b/api-js/src/enums/index.js @@ -13,3 +13,4 @@ export * from './scan-types' export * from './ssl-order-field' export * from './status' export * from './tfa-send-method' +export * from './verified-domain-order-field' diff --git a/api-js/src/enums/verified-domain-order-field.js b/api-js/src/enums/verified-domain-order-field.js new file mode 100644 index 0000000000..4d00139762 --- /dev/null +++ b/api-js/src/enums/verified-domain-order-field.js @@ -0,0 +1,37 @@ +import { GraphQLEnumType } from 'graphql' + +export const VerifiedDomainOrderField = new GraphQLEnumType({ + name: 'VerifiedDomainOrderField', + description: + 'Properties by which verified domain connections can be ordered.', + values: { + DOMAIN: { + value: 'domain', + description: 'Order verified domain edges by domain.', + }, + LAST_RAN: { + value: 'last-ran', + description: 'Order verified domain edges by last ran.', + }, + DKIM_STATUS: { + value: 'dkim-status', + description: 'Order verified domain edges by dkim status.', + }, + DMARC_STATUS: { + value: 'dmarc-status', + description: 'Order verified domain edges by dmarc status.', + }, + HTTPS_STATUS: { + value: 'https-status', + description: 'Order verified domain edges by https status.', + }, + SPF_STATUS: { + value: 'spf-status', + description: 'Order verified domain edges by spf status.', + }, + SSL_STATUS: { + value: 'ssl-status', + description: 'Order verified domain edges by ssl status.', + }, + }, +}) From 9e0466b4dbd514c564a4f456ef44987ca144c1f4 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 19 Feb 2021 08:36:54 -0400 Subject: [PATCH 2/4] create verified domain orderBy input object --- .../__tests__/verified-domain-order.test.js | 25 +++++++++++++++++++ api-js/src/verified-domains/inputs/index.js | 1 + .../inputs/verified-domain-order.js | 18 +++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 api-js/src/verified-domains/inputs/__tests__/verified-domain-order.test.js create mode 100644 api-js/src/verified-domains/inputs/index.js create mode 100644 api-js/src/verified-domains/inputs/verified-domain-order.js diff --git a/api-js/src/verified-domains/inputs/__tests__/verified-domain-order.test.js b/api-js/src/verified-domains/inputs/__tests__/verified-domain-order.test.js new file mode 100644 index 0000000000..b507d7fcf9 --- /dev/null +++ b/api-js/src/verified-domains/inputs/__tests__/verified-domain-order.test.js @@ -0,0 +1,25 @@ +import { GraphQLNonNull } from 'graphql' + +import { verifiedDomainOrder } from '../verified-domain-order' +import { OrderDirection, VerifiedDomainOrderField } from '../../../enums' + +describe('given the verifiedDomainOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = verifiedDomainOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject( + GraphQLNonNull(OrderDirection), + ) + }) + it('has a field field', () => { + const demoType = verifiedDomainOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject( + GraphQLNonNull(VerifiedDomainOrderField), + ) + }) + }) +}) diff --git a/api-js/src/verified-domains/inputs/index.js b/api-js/src/verified-domains/inputs/index.js new file mode 100644 index 0000000000..9ca41a647e --- /dev/null +++ b/api-js/src/verified-domains/inputs/index.js @@ -0,0 +1 @@ +export * from './verified-domain-order' \ No newline at end of file diff --git a/api-js/src/verified-domains/inputs/verified-domain-order.js b/api-js/src/verified-domains/inputs/verified-domain-order.js new file mode 100644 index 0000000000..f963c57bda --- /dev/null +++ b/api-js/src/verified-domains/inputs/verified-domain-order.js @@ -0,0 +1,18 @@ +import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' + +import { OrderDirection, VerifiedDomainOrderField } from '../../enums' + +export const verifiedDomainOrder = new GraphQLInputObjectType({ + name: 'VerifiedDomainOrder', + description: 'Ordering options for verified domain connections.', + fields: () => ({ + field: { + type: GraphQLNonNull(VerifiedDomainOrderField), + description: 'The field to order verified domains by.', + }, + direction: { + type: GraphQLNonNull(OrderDirection), + description: 'The ordering direction.', + }, + }), +}) From 288a837b931671e8503812551ae18bd172abb9eb Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 19 Feb 2021 08:37:09 -0400 Subject: [PATCH 3/4] add ordering functionality to verified domain connection loaders --- ...oad-verified-domain-conn-by-org-id.test.js | 711 +++++++++++++++++- .../load-verified-domain-connections.test.js | 703 ++++++++++++++++- ...d-domain-connections-by-organization-id.js | 187 ++++- .../load-verified-domain-connections.js | 187 ++++- 4 files changed, 1759 insertions(+), 29 deletions(-) diff --git a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js b/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js index 874b78deda..d527be9606 100644 --- a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js +++ b/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-conn-by-org-id.test.js @@ -7,7 +7,10 @@ import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' import { makeMigrations } from '../../../../migrations' import { cleanseInput } from '../../../validators' -import { verifiedDomainLoaderConnectionsByOrgId, verifiedDomainLoaderByKey } from '../../loaders' +import { + verifiedDomainLoaderConnectionsByOrgId, + verifiedDomainLoaderByKey, +} from '../../loaders' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -328,6 +331,712 @@ describe('given the verifiedDomainLoaderConnectionsByOrgId function', () => { expect(domains).toEqual(expectedStructure) }) }) + describe('using the orderBy field', () => { + let domainOne, domainTwo, domainThree + beforeEach(async () => { + await truncate() + org = await collections.organizations.save({ + verified: true, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domainOne = await collections.domains.save({ + domain: 'test.domain.gc.a.ca', + status: { + dkim: 'fail', + dmarc: 'fail', + https: 'fail', + spf: 'fail', + ssl: 'fail', + }, + lastRan: '2021-01-01 12:12:12.000000', + }) + domainTwo = await collections.domains.save({ + domain: 'test.domain.gc.b.ca', + status: { + dkim: 'info', + dmarc: 'info', + https: 'info', + spf: 'info', + ssl: 'info', + }, + lastRan: '2021-01-02 12:12:12.000000', + }) + domainThree = await collections.domains.save({ + domain: 'test.domain.gc.c.ca', + status: { + dkim: 'pass', + dmarc: 'pass', + https: 'pass', + spf: 'pass', + ssl: 'pass', + }, + lastRan: '2021-01-03 12:12:12.000000', + }) + await collections.claims.save({ + _from: org._id, + _to: domainOne._id, + }) + await collections.claims.save({ + _from: org._id, + _to: domainTwo._id, + }) + await collections.claims.save({ + _from: org._id, + _to: domainThree._id, + }) + }) + describe('ordering on DOMAIN', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'domain', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'domain', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on LAST_RAN', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'last-ran', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'last-ran', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on DKIM_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'dkim-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'dkim-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on DMARC_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'dmarc-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'dmarc-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on HTTPS_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'https-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'https-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on SPF_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'spf-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'spf-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on SSL_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'ssl-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnectionsByOrgId( + query, + cleanseInput, + ) + + const connectionArgs = { + orgId: org._id, + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'ssl-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + }) describe('no organizations are found', () => { it('returns an empty structure', async () => { await truncate() diff --git a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js b/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js index 01e46a770b..61aa50fb86 100644 --- a/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js +++ b/api-js/src/verified-domains/loaders/__tests__/load-verified-domain-connections.test.js @@ -7,7 +7,10 @@ import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' import { makeMigrations } from '../../../../migrations' import { cleanseInput } from '../../../validators' -import { verifiedDomainLoaderConnections, verifiedDomainLoaderByKey } from '../../loaders' +import { + verifiedDomainLoaderConnections, + verifiedDomainLoaderByKey, +} from '../../loaders' const { DB_PASS: rootPass, DB_URL: url } = process.env @@ -36,7 +39,6 @@ describe('given the load domain connection using org id function', () => { }) beforeEach(async () => { - await truncate() consoleOutput = [] user = await collections.users.save({ userName: 'test.account@istio.actually.exists', @@ -91,9 +93,14 @@ describe('given the load domain connection using org id function', () => { }) }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { await drop() }) + describe('given a successful load', () => { describe('using no cursor', () => { it('returns multiple domains', async () => { @@ -323,6 +330,698 @@ describe('given the load domain connection using org id function', () => { expect(domains).toEqual(expectedStructure) }) }) + describe('using the orderBy field', () => { + let domainOne, domainTwo, domainThree + beforeEach(async () => { + await truncate() + org = await collections.organizations.save({ + verified: true, + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domainOne = await collections.domains.save({ + domain: 'test.domain.gc.a.ca', + status: { + dkim: 'fail', + dmarc: 'fail', + https: 'fail', + spf: 'fail', + ssl: 'fail', + }, + lastRan: '2021-01-01 12:12:12.000000', + }) + domainTwo = await collections.domains.save({ + domain: 'test.domain.gc.b.ca', + status: { + dkim: 'info', + dmarc: 'info', + https: 'info', + spf: 'info', + ssl: 'info', + }, + lastRan: '2021-01-02 12:12:12.000000', + }) + domainThree = await collections.domains.save({ + domain: 'test.domain.gc.c.ca', + status: { + dkim: 'pass', + dmarc: 'pass', + https: 'pass', + spf: 'pass', + ssl: 'pass', + }, + lastRan: '2021-01-03 12:12:12.000000', + }) + await collections.claims.save({ + _from: org._id, + _to: domainOne._id, + }) + await collections.claims.save({ + _from: org._id, + _to: domainTwo._id, + }) + await collections.claims.save({ + _from: org._id, + _to: domainThree._id, + }) + }) + describe('ordering on DOMAIN', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'domain', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'domain', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on LAST_RAN', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'last-ran', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'last-ran', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on DKIM_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'dkim-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'dkim-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on DMARC_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'dmarc-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'dmarc-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on HTTPS_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'https-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'https-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on SPF_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'spf-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'spf-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on SSL_STATUS', () => { + describe('direction is ASC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainOne._key), + before: toGlobalId('verifiedDomains', domainThree._key), + orderBy: { + field: 'ssl-status', + direction: 'ASC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + describe('direction is DESC', () => { + it('returns domains in order', async () => { + const domainLoader = verifiedDomainLoaderByKey(query) + const expectedDomain = await domainLoader.load(domainTwo._key) + + const connectionLoader = verifiedDomainLoaderConnections( + query, + cleanseInput, + ) + + const connectionArgs = { + first: 5, + after: toGlobalId('verifiedDomains', domainThree._key), + before: toGlobalId('verifiedDomains', domainOne._key), + orderBy: { + field: 'ssl-status', + direction: 'DESC', + }, + } + + const domains = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('verifiedDomains', domainTwo._key), + node: { + ...expectedDomain, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('verifiedDomains', domainTwo._key), + endCursor: toGlobalId('verifiedDomains', domainTwo._key), + }, + } + + expect(domains).toEqual(expectedStructure) + }) + }) + }) + }) describe('no organizations are found', () => { it('returns an empty structure', async () => { await truncate() diff --git a/api-js/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js b/api-js/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js index 2dfae6057c..d4aca928d5 100644 --- a/api-js/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js +++ b/api-js/src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js @@ -6,20 +6,100 @@ export const verifiedDomainLoaderConnectionsByOrgId = ( query, cleanseInput, i18n, -) => async ({ orgId, after, before, first, last }) => { +) => async ({ orgId, after, before, first, last, orderBy }) => { let afterTemplate = aql`` let beforeTemplate = aql`` - let afterId if (typeof after !== 'undefined') { - afterId = fromGlobalId(cleanseInput(after)).id - afterTemplate = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(${afterId})` + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + let documentField = aql`` + let domainField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + documentField = aql`DOCUMENT(domains, ${afterId}).domain` + domainField = aql`domain.domain` + } else if (orderBy.field === 'last-ran') { + documentField = aql`DOCUMENT(domains, ${afterId}).lastRan` + domainField = aql`domain.lastRan` + } else if (orderBy.field === 'dkim-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.dkim` + domainField = aql`domain.status.dkim` + } else if (orderBy.field === 'dmarc-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.dmarc` + domainField = aql`domain.status.dmarc` + } else if (orderBy.field === 'https-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.https` + domainField = aql`domain.status.https` + } else if (orderBy.field === 'spf-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.spf` + domainField = aql`domain.status.spf` + } else if (orderBy.field === 'ssl-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.ssl` + domainField = aql`domain.status.ssl` + } + + afterTemplate = aql` + FILTER ${domainField} ${afterTemplateDirection} ${documentField} + OR (${domainField} == ${documentField} + AND TO_NUMBER(domain._key) > TO_NUMBER(${afterId})) + ` + } } - let beforeId if (typeof before !== 'undefined') { - beforeId = fromGlobalId(cleanseInput(before)).id - beforeTemplate = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})` + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + let documentField = aql`` + let domainField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + documentField = aql`DOCUMENT(domains, ${beforeId}).domain` + domainField = aql`domain.domain` + } else if (orderBy.field === 'last-ran') { + documentField = aql`DOCUMENT(domains, ${beforeId}).lastRan` + domainField = aql`domain.lastRan` + } else if (orderBy.field === 'dkim-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.dkim` + domainField = aql`domain.status.dkim` + } else if (orderBy.field === 'dmarc-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.dmarc` + domainField = aql`domain.status.dmarc` + } else if (orderBy.field === 'https-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.https` + domainField = aql`domain.status.https` + } else if (orderBy.field === 'spf-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.spf` + domainField = aql`domain.status.spf` + } else if (orderBy.field === 'ssl-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.ssl` + domainField = aql`domain.status.ssl` + } + + beforeTemplate = aql` + FILTER ${domainField} ${beforeTemplateDirection} ${documentField} + OR (${domainField} == ${documentField} + AND TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})) + ` + } } let limitTemplate = aql`` @@ -65,9 +145,9 @@ export const verifiedDomainLoaderConnectionsByOrgId = ( ), ) } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`SORT domain._key ASC LIMIT TO_NUMBER(${first})` + limitTemplate = aql`domain._key ASC LIMIT TO_NUMBER(${first})` } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`SORT domain._key DESC LIMIT TO_NUMBER(${last})` + limitTemplate = aql`domain._key DESC LIMIT TO_NUMBER(${last})` } } else { const argSet = typeof first !== 'undefined' ? 'first' : 'last' @@ -80,6 +160,85 @@ export const verifiedDomainLoaderConnectionsByOrgId = ( ) } + let hasNextPageFilter = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let domainField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + domainField = aql`domain.domain` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).domain` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).domain` + } else if (orderBy.field === 'last-ran') { + domainField = aql`domain.lastRan` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).lastRan` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).lastRan` + } else if (orderBy.field === 'dkim-status') { + domainField = aql`domain.status.dkim` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.dkim` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.dkim` + } else if (orderBy.field === 'dmarc-status') { + domainField = aql`domain.status.dmarc` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.dmarc` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.dmarc` + } else if (orderBy.field === 'https-status') { + domainField = aql`domain.status.https` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.https` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.https` + } else if (orderBy.field === 'spf-status') { + domainField = aql`domain.status.spf` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.spf` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.spf` + } else if (orderBy.field === 'ssl-status') { + domainField = aql`domain.status.ssl` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.ssl` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.ssl` + } + + hasNextPageFilter = aql` + FILTER ${domainField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${domainField} == ${hasNextPageDocumentField} + AND TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${domainField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${domainField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + sortByField = aql`domain.domain ${orderBy.direction},` + } else if (orderBy.field === 'last-ran') { + sortByField = aql`domain.lastRan ${orderBy.direction},` + } else if (orderBy.field === 'dkim-status') { + sortByField = aql`domain.status.dkim ${orderBy.direction},` + } else if (orderBy.field === 'dmarc-status') { + sortByField = aql`domain.status.dmarc ${orderBy.direction},` + } else if (orderBy.field === 'https-status') { + sortByField = aql`domain.status.https ${orderBy.direction},` + } else if (orderBy.field === 'spf-status') { + sortByField = aql`domain.status.spf ${orderBy.direction},` + } else if (orderBy.field === 'ssl-status') { + sortByField = aql`domain.status.ssl ${orderBy.direction},` + } + } + let sortString = aql`` if (typeof last !== 'undefined') { sortString = aql`DESC` @@ -99,6 +258,8 @@ export const verifiedDomainLoaderConnectionsByOrgId = ( FILTER domain._key IN domainIds ${afterTemplate} ${beforeTemplate} + SORT + ${sortByField} ${limitTemplate} RETURN MERGE(domain, { id: domain._key, _type: "verifiedDomain" }) ) @@ -106,16 +267,16 @@ export const verifiedDomainLoaderConnectionsByOrgId = ( LET hasNextPage = (LENGTH( FOR domain IN domains FILTER domain._key IN domainIds - FILTER TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key) - SORT domain._key ${sortString} LIMIT 1 + ${hasNextPageFilter} + SORT ${sortByField} domain._key ${sortString} LIMIT 1 RETURN domain ) > 0 ? true : false) LET hasPreviousPage = (LENGTH( FOR domain IN domains FILTER domain._key IN domainIds - FILTER TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key) - SORT domain._key ${sortString} LIMIT 1 + ${hasPreviousPageFilter} + SORT ${sortByField} domain._key ${sortString} LIMIT 1 RETURN domain ) > 0 ? true : false) diff --git a/api-js/src/verified-domains/loaders/load-verified-domain-connections.js b/api-js/src/verified-domains/loaders/load-verified-domain-connections.js index 2e69fd1203..037bb4e93a 100644 --- a/api-js/src/verified-domains/loaders/load-verified-domain-connections.js +++ b/api-js/src/verified-domains/loaders/load-verified-domain-connections.js @@ -6,20 +6,100 @@ export const verifiedDomainLoaderConnections = ( query, cleanseInput, i18n, -) => async ({ after, before, first, last }) => { +) => async ({ after, before, first, last, orderBy }) => { let afterTemplate = aql`` let beforeTemplate = aql`` - let afterId if (typeof after !== 'undefined') { - afterId = fromGlobalId(cleanseInput(after)).id - afterTemplate = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(${afterId})` + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + let documentField = aql`` + let domainField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + documentField = aql`DOCUMENT(domains, ${afterId}).domain` + domainField = aql`domain.domain` + } else if (orderBy.field === 'last-ran') { + documentField = aql`DOCUMENT(domains, ${afterId}).lastRan` + domainField = aql`domain.lastRan` + } else if (orderBy.field === 'dkim-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.dkim` + domainField = aql`domain.status.dkim` + } else if (orderBy.field === 'dmarc-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.dmarc` + domainField = aql`domain.status.dmarc` + } else if (orderBy.field === 'https-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.https` + domainField = aql`domain.status.https` + } else if (orderBy.field === 'spf-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.spf` + domainField = aql`domain.status.spf` + } else if (orderBy.field === 'ssl-status') { + documentField = aql`DOCUMENT(domains, ${afterId}).status.ssl` + domainField = aql`domain.status.ssl` + } + + afterTemplate = aql` + FILTER ${domainField} ${afterTemplateDirection} ${documentField} + OR (${domainField} == ${documentField} + AND TO_NUMBER(domain._key) > TO_NUMBER(${afterId})) + ` + } } - let beforeId if (typeof before !== 'undefined') { - beforeId = fromGlobalId(cleanseInput(before)).id - beforeTemplate = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})` + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + let documentField = aql`` + let domainField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + documentField = aql`DOCUMENT(domains, ${beforeId}).domain` + domainField = aql`domain.domain` + } else if (orderBy.field === 'last-ran') { + documentField = aql`DOCUMENT(domains, ${beforeId}).lastRan` + domainField = aql`domain.lastRan` + } else if (orderBy.field === 'dkim-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.dkim` + domainField = aql`domain.status.dkim` + } else if (orderBy.field === 'dmarc-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.dmarc` + domainField = aql`domain.status.dmarc` + } else if (orderBy.field === 'https-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.https` + domainField = aql`domain.status.https` + } else if (orderBy.field === 'spf-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.spf` + domainField = aql`domain.status.spf` + } else if (orderBy.field === 'ssl-status') { + documentField = aql`DOCUMENT(domains, ${beforeId}).status.ssl` + domainField = aql`domain.status.ssl` + } + + beforeTemplate = aql` + FILTER ${domainField} ${beforeTemplateDirection} ${documentField} + OR (${domainField} == ${documentField} + AND TO_NUMBER(domain._key) < TO_NUMBER(${beforeId})) + ` + } } let limitTemplate = aql`` @@ -65,9 +145,9 @@ export const verifiedDomainLoaderConnections = ( ), ) } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`SORT domain._key ASC LIMIT TO_NUMBER(${first})` + limitTemplate = aql`domain._key ASC LIMIT TO_NUMBER(${first})` } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`SORT domain._key DESC LIMIT TO_NUMBER(${last})` + limitTemplate = aql`domain._key DESC LIMIT TO_NUMBER(${last})` } } else { const argSet = typeof first !== 'undefined' ? 'first' : 'last' @@ -80,6 +160,85 @@ export const verifiedDomainLoaderConnections = ( ) } + let hasNextPageFilter = aql`FILTER TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let domainField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + domainField = aql`domain.domain` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).domain` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).domain` + } else if (orderBy.field === 'last-ran') { + domainField = aql`domain.lastRan` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).lastRan` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).lastRan` + } else if (orderBy.field === 'dkim-status') { + domainField = aql`domain.status.dkim` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.dkim` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.dkim` + } else if (orderBy.field === 'dmarc-status') { + domainField = aql`domain.status.dmarc` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.dmarc` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.dmarc` + } else if (orderBy.field === 'https-status') { + domainField = aql`domain.status.https` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.https` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.https` + } else if (orderBy.field === 'spf-status') { + domainField = aql`domain.status.spf` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.spf` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.spf` + } else if (orderBy.field === 'ssl-status') { + domainField = aql`domain.status.ssl` + hasNextPageDocumentField = aql`DOCUMENT(domains, LAST(retrievedDomains)._key).status.ssl` + hasPreviousPageDocumentField = aql`DOCUMENT(domains, FIRST(retrievedDomains)._key).status.ssl` + } + + hasNextPageFilter = aql` + FILTER ${domainField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${domainField} == ${hasNextPageDocumentField} + AND TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${domainField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${domainField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'domain') { + sortByField = aql`domain.domain ${orderBy.direction},` + } else if (orderBy.field === 'last-ran') { + sortByField = aql`domain.lastRan ${orderBy.direction},` + } else if (orderBy.field === 'dkim-status') { + sortByField = aql`domain.status.dkim ${orderBy.direction},` + } else if (orderBy.field === 'dmarc-status') { + sortByField = aql`domain.status.dmarc ${orderBy.direction},` + } else if (orderBy.field === 'https-status') { + sortByField = aql`domain.status.https ${orderBy.direction},` + } else if (orderBy.field === 'spf-status') { + sortByField = aql`domain.status.spf ${orderBy.direction},` + } else if (orderBy.field === 'ssl-status') { + sortByField = aql`domain.status.ssl ${orderBy.direction},` + } + } + let sortString if (typeof last !== 'undefined') { sortString = aql`DESC` @@ -105,6 +264,8 @@ export const verifiedDomainLoaderConnections = ( FILTER domain._key IN domainIds ${afterTemplate} ${beforeTemplate} + SORT + ${sortByField} ${limitTemplate} RETURN MERGE(domain, { id: domain._key, _type: "verifiedDomain" }) ) @@ -112,16 +273,16 @@ export const verifiedDomainLoaderConnections = ( LET hasNextPage = (LENGTH( FOR domain IN domains FILTER domain._key IN domainIds - FILTER TO_NUMBER(domain._key) > TO_NUMBER(LAST(retrievedDomains)._key) - SORT domain._key ${sortString} LIMIT 1 + ${hasNextPageFilter} + SORT ${sortByField} domain._key ${sortString} LIMIT 1 RETURN domain ) > 0 ? true : false) LET hasPreviousPage = (LENGTH( FOR domain IN domains FILTER domain._key IN domainIds - FILTER TO_NUMBER(domain._key) < TO_NUMBER(FIRST(retrievedDomains)._key) - SORT domain._key ${sortString} LIMIT 1 + ${hasPreviousPageFilter} + SORT ${sortByField} domain._key ${sortString} LIMIT 1 RETURN domain ) > 0 ? true : false) From c20b0e5c6527c9c52676da7a34340737036c4794 Mon Sep 17 00:00:00 2001 From: nsdeschenes Date: Fri, 19 Feb 2021 08:37:57 -0400 Subject: [PATCH 4/4] add ordering arguments to queries and objects --- .../verified-domains/queries/find-verified-domains.js | 8 +++++++- .../objects/verified-organization.js | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/api-js/src/verified-domains/queries/find-verified-domains.js b/api-js/src/verified-domains/queries/find-verified-domains.js index 5e88d07867..dc24a08019 100644 --- a/api-js/src/verified-domains/queries/find-verified-domains.js +++ b/api-js/src/verified-domains/queries/find-verified-domains.js @@ -1,10 +1,16 @@ import { connectionArgs } from 'graphql-relay' + +import { verifiedDomainOrder } from '../inputs' import { verifiedDomainConnection } from '../objects' export const findVerifiedDomains = { type: verifiedDomainConnection.connectionType, description: 'Select verified check domains', args: { + orderBy: { + type: verifiedDomainOrder, + description: 'Ordering options for verified domain connections.', + }, ...connectionArgs, }, resolve: async ( @@ -12,7 +18,7 @@ export const findVerifiedDomains = { args, { loaders: { verifiedDomainLoaderConnections } }, ) => { - const domainConnections = await verifiedDomainLoaderConnections({ ...args }) + const domainConnections = await verifiedDomainLoaderConnections(args) return domainConnections }, } diff --git a/api-js/src/verified-organizations/objects/verified-organization.js b/api-js/src/verified-organizations/objects/verified-organization.js index bd6f19d245..a916428514 100644 --- a/api-js/src/verified-organizations/objects/verified-organization.js +++ b/api-js/src/verified-organizations/objects/verified-organization.js @@ -10,6 +10,7 @@ import { globalIdField, } from 'graphql-relay' +import { verifiedDomainOrder } from '../../verified-domains/inputs' import { verifiedDomainConnection } from '../../verified-domains/objects' import { organizationSummaryType } from '../../organization/objects' import { Acronym, Slug } from '../../scalars' @@ -78,7 +79,13 @@ export const verifiedOrganizationType = new GraphQLObjectType({ domains: { type: verifiedDomainConnection.connectionType, description: 'The domains which are associated with this organization.', - args: connectionArgs, + args: { + orderBy: { + type: verifiedDomainOrder, + description: 'Ordering options for verified domain connections.', + }, + ...connectionArgs, + }, resolve: async ( { _id }, args,