diff --git a/api-js/src/email-scan/inputs/__tests__/spf-order.test.js b/api-js/src/email-scan/inputs/__tests__/spf-order.test.js new file mode 100644 index 0000000000..2b53822ef1 --- /dev/null +++ b/api-js/src/email-scan/inputs/__tests__/spf-order.test.js @@ -0,0 +1,23 @@ +import { GraphQLNonNull } from 'graphql' + +import { spfOrder } from '../spf-order' +import { OrderDirection, SpfOrderField } from '../../../enums' + +describe('given the spfOrder input object', () => { + describe('testing fields', () => { + it('has a direction field', () => { + const demoType = spfOrder.getFields() + + expect(demoType).toHaveProperty('direction') + expect(demoType.direction.type).toMatchObject( + GraphQLNonNull(OrderDirection), + ) + }) + it('has a field field', () => { + const demoType = spfOrder.getFields() + + expect(demoType).toHaveProperty('field') + expect(demoType.field.type).toMatchObject(GraphQLNonNull(SpfOrderField)) + }) + }) +}) diff --git a/api-js/src/email-scan/inputs/index.js b/api-js/src/email-scan/inputs/index.js index 81e906666f..a8cb1f599e 100644 --- a/api-js/src/email-scan/inputs/index.js +++ b/api-js/src/email-scan/inputs/index.js @@ -1,3 +1,4 @@ export * from './dkim-order' export * from './dkim-result-order' -export * from './dmarc-order' \ No newline at end of file +export * from './dmarc-order' +export * from './spf-order' diff --git a/api-js/src/email-scan/inputs/spf-order.js b/api-js/src/email-scan/inputs/spf-order.js new file mode 100644 index 0000000000..c0b2b1a12b --- /dev/null +++ b/api-js/src/email-scan/inputs/spf-order.js @@ -0,0 +1,18 @@ +import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql' + +import { OrderDirection, SpfOrderField } from '../../enums' + +export const spfOrder = new GraphQLInputObjectType({ + name: 'SPFOrder', + description: 'Ordering options for SPF connections.', + fields: () => ({ + field: { + type: GraphQLNonNull(SpfOrderField), + description: 'The field to order SPF scans by.', + }, + direction: { + type: GraphQLNonNull(OrderDirection), + description: 'The ordering direction.', + }, + }), +}) diff --git a/api-js/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js b/api-js/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js index 12ecb42dfc..a9aabc6f95 100644 --- a/api-js/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js +++ b/api-js/src/email-scan/loaders/__tests__/load-spf-connections-by-domain-id.test.js @@ -42,7 +42,6 @@ describe('when given the load spf connection function', () => { }) beforeEach(async () => { - await truncate() consoleWarnOutput.length = 0 consoleErrorOutput.length = 0 @@ -59,6 +58,10 @@ describe('when given the load spf connection function', () => { }) }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { await drop() }) @@ -519,6 +522,429 @@ describe('when given the load spf connection function', () => { }) }) }) + describe('using orderBy field', () => { + let spfOne, spfTwo, spfThree + beforeEach(async () => { + await truncate() + domain = await collections.domains.save({ + domain: 'test.domain.gc.ca', + }) + spfOne = await collections.spf.save({ + lookups: 1, + record: 'a', + spfDefault: 'a', + timestamp: '2021-01-26 23:29:21.219962', + }) + spfTwo = await collections.spf.save({ + lookups: 2, + record: 'b', + spfDefault: 'b', + timestamp: '2021-01-27 23:29:21.219962', + }) + spfThree = await collections.spf.save({ + lookups: 3, + record: 'c', + spfDefault: 'c', + timestamp: '2021-01-28 23:29:21.219962', + }) + await collections.domainsSPF.save({ + _to: spfOne._id, + _from: domain._id, + }) + await collections.domainsSPF.save({ + _to: spfTwo._id, + _from: domain._id, + }) + await collections.domainsSPF.save({ + _to: spfThree._id, + _from: domain._id, + }) + }) + describe('ordering on TIMESTAMP', () => { + describe('direction is set to ASC', () => { + it('returns spf scan', async () => { + const loader = spfLoaderByKey(query, user._key, i18n) + const expectedSpfScan = await loader.load(spfTwo._key) + + const connectionLoader = spfLoaderConnectionsByDomainId( + query, + user._key, + cleanseInput, + i18n, + ) + + const connectionArgs = { + domainId: domain._id, + first: 5, + after: toGlobalId('spf', spfOne._key), + before: toGlobalId('spf', spfThree._key), + orderBy: { + field: 'timestamp', + direction: 'ASC', + }, + } + + const spfScans = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('spf', expectedSpfScan._key), + node: { + domainId: domain._id, + ...expectedSpfScan, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('spf', expectedSpfScan._key), + endCursor: toGlobalId('spf', expectedSpfScan._key), + }, + } + + expect(spfScans).toEqual(expectedStructure) + }) + }) + describe('direction is set to DESC', () => { + it('returns spf scan', async () => { + const loader = spfLoaderByKey(query, user._key, i18n) + const expectedSpfScan = await loader.load(spfTwo._key) + + const connectionLoader = spfLoaderConnectionsByDomainId( + query, + user._key, + cleanseInput, + i18n, + ) + + const connectionArgs = { + domainId: domain._id, + first: 5, + after: toGlobalId('spf', spfThree._key), + before: toGlobalId('spf', spfOne._key), + orderBy: { + field: 'timestamp', + direction: 'DESC', + }, + } + + const spfScans = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('spf', expectedSpfScan._key), + node: { + domainId: domain._id, + ...expectedSpfScan, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('spf', expectedSpfScan._key), + endCursor: toGlobalId('spf', expectedSpfScan._key), + }, + } + + expect(spfScans).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on LOOKUPS', () => { + describe('direction is set to ASC', () => { + it('returns spf scan', async () => { + const loader = spfLoaderByKey(query, user._key, i18n) + const expectedSpfScan = await loader.load(spfTwo._key) + + const connectionLoader = spfLoaderConnectionsByDomainId( + query, + user._key, + cleanseInput, + i18n, + ) + + const connectionArgs = { + domainId: domain._id, + first: 5, + after: toGlobalId('spf', spfOne._key), + before: toGlobalId('spf', spfThree._key), + orderBy: { + field: 'lookups', + direction: 'ASC', + }, + } + + const spfScans = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('spf', expectedSpfScan._key), + node: { + domainId: domain._id, + ...expectedSpfScan, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('spf', expectedSpfScan._key), + endCursor: toGlobalId('spf', expectedSpfScan._key), + }, + } + + expect(spfScans).toEqual(expectedStructure) + }) + }) + describe('direction is set to DESC', () => { + it('returns spf scan', async () => { + const loader = spfLoaderByKey(query, user._key, i18n) + const expectedSpfScan = await loader.load(spfTwo._key) + + const connectionLoader = spfLoaderConnectionsByDomainId( + query, + user._key, + cleanseInput, + i18n, + ) + + const connectionArgs = { + domainId: domain._id, + first: 5, + after: toGlobalId('spf', spfThree._key), + before: toGlobalId('spf', spfOne._key), + orderBy: { + field: 'lookups', + direction: 'DESC', + }, + } + + const spfScans = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('spf', expectedSpfScan._key), + node: { + domainId: domain._id, + ...expectedSpfScan, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('spf', expectedSpfScan._key), + endCursor: toGlobalId('spf', expectedSpfScan._key), + }, + } + + expect(spfScans).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on RECORD', () => { + describe('direction is set to ASC', () => { + it('returns spf scan', async () => { + const loader = spfLoaderByKey(query, user._key, i18n) + const expectedSpfScan = await loader.load(spfTwo._key) + + const connectionLoader = spfLoaderConnectionsByDomainId( + query, + user._key, + cleanseInput, + i18n, + ) + + const connectionArgs = { + domainId: domain._id, + first: 5, + after: toGlobalId('spf', spfOne._key), + before: toGlobalId('spf', spfThree._key), + orderBy: { + field: 'record', + direction: 'ASC', + }, + } + + const spfScans = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('spf', expectedSpfScan._key), + node: { + domainId: domain._id, + ...expectedSpfScan, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('spf', expectedSpfScan._key), + endCursor: toGlobalId('spf', expectedSpfScan._key), + }, + } + + expect(spfScans).toEqual(expectedStructure) + }) + }) + describe('direction is set to DESC', () => { + it('returns spf scan', async () => { + const loader = spfLoaderByKey(query, user._key, i18n) + const expectedSpfScan = await loader.load(spfTwo._key) + + const connectionLoader = spfLoaderConnectionsByDomainId( + query, + user._key, + cleanseInput, + i18n, + ) + + const connectionArgs = { + domainId: domain._id, + first: 5, + after: toGlobalId('spf', spfThree._key), + before: toGlobalId('spf', spfOne._key), + orderBy: { + field: 'record', + direction: 'DESC', + }, + } + + const spfScans = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('spf', expectedSpfScan._key), + node: { + domainId: domain._id, + ...expectedSpfScan, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('spf', expectedSpfScan._key), + endCursor: toGlobalId('spf', expectedSpfScan._key), + }, + } + + expect(spfScans).toEqual(expectedStructure) + }) + }) + }) + describe('ordering on SPF_DEFAULT', () => { + describe('direction is set to ASC', () => { + it('returns spf scan', async () => { + const loader = spfLoaderByKey(query, user._key, i18n) + const expectedSpfScan = await loader.load(spfTwo._key) + + const connectionLoader = spfLoaderConnectionsByDomainId( + query, + user._key, + cleanseInput, + i18n, + ) + + const connectionArgs = { + domainId: domain._id, + first: 5, + after: toGlobalId('spf', spfOne._key), + before: toGlobalId('spf', spfThree._key), + orderBy: { + field: 'spf-default', + direction: 'ASC', + }, + } + + const spfScans = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('spf', expectedSpfScan._key), + node: { + domainId: domain._id, + ...expectedSpfScan, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('spf', expectedSpfScan._key), + endCursor: toGlobalId('spf', expectedSpfScan._key), + }, + } + + expect(spfScans).toEqual(expectedStructure) + }) + }) + describe('direction is set to DESC', () => { + it('returns spf scan', async () => { + const loader = spfLoaderByKey(query, user._key, i18n) + const expectedSpfScan = await loader.load(spfTwo._key) + + const connectionLoader = spfLoaderConnectionsByDomainId( + query, + user._key, + cleanseInput, + i18n, + ) + + const connectionArgs = { + domainId: domain._id, + first: 5, + after: toGlobalId('spf', spfThree._key), + before: toGlobalId('spf', spfOne._key), + orderBy: { + field: 'spf-default', + direction: 'DESC', + }, + } + + const spfScans = await connectionLoader(connectionArgs) + + const expectedStructure = { + edges: [ + { + cursor: toGlobalId('spf', expectedSpfScan._key), + node: { + domainId: domain._id, + ...expectedSpfScan, + }, + }, + ], + totalCount: 3, + pageInfo: { + hasNextPage: true, + hasPreviousPage: true, + startCursor: toGlobalId('spf', expectedSpfScan._key), + endCursor: toGlobalId('spf', expectedSpfScan._key), + }, + } + + expect(spfScans).toEqual(expectedStructure) + }) + }) + }) + }) describe('no spf scans are found', () => { it('returns an empty structure', async () => { await truncate() diff --git a/api-js/src/email-scan/loaders/load-spf-connections-by-domain-id.js b/api-js/src/email-scan/loaders/load-spf-connections-by-domain-id.js index 3ab2def275..29d25261de 100644 --- a/api-js/src/email-scan/loaders/load-spf-connections-by-domain-id.js +++ b/api-js/src/email-scan/loaders/load-spf-connections-by-domain-id.js @@ -7,27 +7,116 @@ export const spfLoaderConnectionsByDomainId = ( userKey, cleanseInput, i18n, -) => async ({ domainId, startDate, endDate, after, before, first, last }) => { +) => async ({ + domainId, + startDate, + endDate, + after, + before, + first, + last, + orderBy, +}) => { let afterTemplate = aql`` if (typeof after !== 'undefined') { const { id: afterId } = fromGlobalId(cleanseInput(after)) - afterTemplate = aql`FILTER TO_NUMBER(spfScan._key) > TO_NUMBER(${afterId})` + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(spfScan._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } + + let spfField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'timestamp') { + spfField = aql`spfScan.timestamp` + documentField = aql`DOCUMENT(spf, ${afterId}).timestamp` + } else if (orderBy.field === 'lookups') { + spfField = aql`spfScan.lookups` + documentField = aql`DOCUMENT(spf, ${afterId}).lookups` + } else if (orderBy.field === 'record') { + spfField = aql`spfScan.record` + documentField = aql`DOCUMENT(spf, ${afterId}).record` + } else if (orderBy.field === 'spf-default') { + spfField = aql`spfScan.spfDefault` + documentField = aql`DOCUMENT(spf, ${afterId}).spfDefault` + } + + afterTemplate = aql` + FILTER ${spfField} ${afterTemplateDirection} ${documentField} + OR (${spfField} == ${documentField} + AND TO_NUMBER(spfScan._key) > TO_NUMBER(${afterId})) + ` + } } let beforeTemplate = aql`` if (typeof before !== 'undefined') { const { id: beforeId } = fromGlobalId(cleanseInput(before)) - beforeTemplate = aql`FILTER TO_NUMBER(spfScan._key) < TO_NUMBER(${beforeId})` + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(spfScan._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } + + let spfField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'timestamp') { + spfField = aql`spfScan.timestamp` + documentField = aql`DOCUMENT(spf, ${beforeId}).timestamp` + } else if (orderBy.field === 'lookups') { + spfField = aql`spfScan.lookups` + documentField = aql`DOCUMENT(spf, ${beforeId}).lookups` + } else if (orderBy.field === 'record') { + spfField = aql`spfScan.record` + documentField = aql`DOCUMENT(spf, ${beforeId}).record` + } else if (orderBy.field === 'spf-default') { + spfField = aql`spfScan.spfDefault` + documentField = aql`DOCUMENT(spf, ${beforeId}).spfDefault` + } + + beforeTemplate = aql` + FILTER ${spfField} ${beforeTemplateDirection} ${documentField} + OR (${spfField} == ${documentField} + AND TO_NUMBER(spfScan._key) < TO_NUMBER(${beforeId})) + ` + } } let startDateTemplate = aql`` if (typeof startDate !== 'undefined') { - startDateTemplate = aql`FILTER spfScan.timestamp >= ${startDate}` + startDateTemplate = aql` + FILTER DATE_FORMAT( + DATE_TIMESTAMP(spfScan.timestamp), + "%y-%m-%d" + ) >= + DATE_FORMAT( + DATE_TIMESTAMP(${startDate}), + "%y-%m-%d" + ) + ` } let endDateTemplate = aql`` if (typeof endDate !== 'undefined') { - endDateTemplate = aql`FILTER spfScan.timestamp <= ${endDate}` + endDateTemplate = aql` + FILTER DATE_FORMAT( + DATE_TIMESTAMP(spfScan.timestamp), + "%y-%m-%d" + ) <= + DATE_FORMAT( + DATE_TIMESTAMP(${endDate}), + "%y-%m-%d" + ) + ` } let limitTemplate = aql`` @@ -73,9 +162,9 @@ export const spfLoaderConnectionsByDomainId = ( ), ) } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`SORT spfScan._key ASC LIMIT TO_NUMBER(${first})` + limitTemplate = aql`spfScan._key ASC LIMIT TO_NUMBER(${first})` } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`SORT spfScan._key DESC LIMIT TO_NUMBER(${last})` + limitTemplate = aql`spfScan._key DESC LIMIT TO_NUMBER(${last})` } } else { const argSet = typeof first !== 'undefined' ? 'first' : 'last' @@ -88,6 +177,66 @@ export const spfLoaderConnectionsByDomainId = ( ) } + let hasNextPageFilter = aql`FILTER TO_NUMBER(spfScan._key) > TO_NUMBER(LAST(retrievedSpfScans)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(spfScan._key) < TO_NUMBER(FIRST(retrievedSpfScans)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection + let hasPreviousPageDirection + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let spfField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'timestamp') { + spfField = aql`spfScan.timestamp` + hasNextPageDocument = aql`DOCUMENT(spf, LAST(retrievedSpfScans)._key).timestamp` + hasPreviousPageDocument = aql`DOCUMENT(spf, FIRST(retrievedSpfScans)._key).timestamp` + } else if (orderBy.field === 'lookups') { + spfField = aql`spfScan.lookups` + hasNextPageDocument = aql`DOCUMENT(spf, LAST(retrievedSpfScans)._key).lookups` + hasPreviousPageDocument = aql`DOCUMENT(spf, FIRST(retrievedSpfScans)._key).lookups` + } else if (orderBy.field === 'record') { + spfField = aql`spfScan.record` + hasNextPageDocument = aql`DOCUMENT(spf, LAST(retrievedSpfScans)._key).record` + hasPreviousPageDocument = aql`DOCUMENT(spf, FIRST(retrievedSpfScans)._key).record` + } else if (orderBy.field === 'spf-default') { + spfField = aql`spfScan.spfDefault` + hasNextPageDocument = aql`DOCUMENT(spf, LAST(retrievedSpfScans)._key).spfDefault` + hasPreviousPageDocument = aql`DOCUMENT(spf, FIRST(retrievedSpfScans)._key).spfDefault` + } + + hasNextPageFilter = aql` + FILTER ${spfField} ${hasNextPageDirection} ${hasNextPageDocument} + OR (${spfField} == ${hasNextPageDocument} + AND TO_NUMBER(spfScan._key) > TO_NUMBER(LAST(retrievedSpfScans)._key)) + ` + + hasPreviousPageFilter = aql` + FILTER ${spfField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} + OR (${spfField} == ${hasPreviousPageDocument} + AND TO_NUMBER(spfScan._key) < TO_NUMBER(FIRST(retrievedSpfScans)._key)) + ` + } + + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'timestamp') { + sortByField = aql`spfScan.timestamp ${orderBy.direction},` + } else if (orderBy.field === 'lookups') { + sortByField = aql`spfScan.lookups ${orderBy.direction},` + } else if (orderBy.field === 'record') { + sortByField = aql`spfScan.record ${orderBy.direction},` + } else if (orderBy.field === 'spf-default') { + sortByField = aql`spfScan.spfDefault ${orderBy.direction},` + } + } + let sortString if (typeof last !== 'undefined') { sortString = aql`DESC` @@ -107,6 +256,8 @@ export const spfLoaderConnectionsByDomainId = ( ${beforeTemplate} ${startDateTemplate} ${endDateTemplate} + SORT + ${sortByField} ${limitTemplate} RETURN MERGE({ id: spfScan._key, _type: "spf" }, spfScan) ) @@ -114,16 +265,16 @@ export const spfLoaderConnectionsByDomainId = ( LET hasNextPage = (LENGTH( FOR spfScan IN spf FILTER spfScan._key IN spfKeys - FILTER TO_NUMBER(spfScan._key) > TO_NUMBER(LAST(retrievedSpfScans)._key) - SORT spfScan._key ${sortString} LIMIT 1 + ${hasNextPageFilter} + SORT ${sortByField} spfScan._key ${sortString} LIMIT 1 RETURN spfScan ) > 0 ? true : false) LET hasPreviousPage = (LENGTH( FOR spfScan IN spf FILTER spfScan._key IN spfKeys - FILTER TO_NUMBER(spfScan._key) < TO_NUMBER(FIRST(retrievedSpfScans)._key) - SORT spfScan._key ${sortString} LIMIT 1 + ${hasPreviousPageFilter} + SORT ${sortByField} spfScan._key ${sortString} LIMIT 1 RETURN spfScan ) > 0 ? true : false) diff --git a/api-js/src/email-scan/objects/email-scan.js b/api-js/src/email-scan/objects/email-scan.js index bea42999a4..80be2c67ab 100644 --- a/api-js/src/email-scan/objects/email-scan.js +++ b/api-js/src/email-scan/objects/email-scan.js @@ -2,7 +2,7 @@ import { GraphQLObjectType } from 'graphql' import { connectionArgs } from 'graphql-relay' import { GraphQLDate, GraphQLDateTime } from 'graphql-scalars' -import { dkimOrder, dmarcOrder } from '../inputs' +import { dkimOrder, dmarcOrder, spfOrder } from '../inputs' import { dkimConnection } from './dkim' import { dmarcConnection } from './dmarc' import { spfConnection } from './spf' @@ -91,6 +91,10 @@ export const emailScanType = new GraphQLObjectType({ type: GraphQLDateTime, description: 'End date for date filter.', }, + orderBy: { + type: spfOrder, + description: 'Ordering options for spf connections.', + }, ...connectionArgs, }, description: `Sender Policy Framework (SPF) scan results.`, diff --git a/api-js/src/enums/index.js b/api-js/src/enums/index.js index 4ad9fabd8b..52015ed2ef 100644 --- a/api-js/src/enums/index.js +++ b/api-js/src/enums/index.js @@ -10,6 +10,7 @@ export * from './organization-order-field' export * from './period' export * from './roles' export * from './scan-types' +export * from './spf-order-field' export * from './ssl-order-field' export * from './status' export * from './tfa-send-method' diff --git a/api-js/src/enums/spf-order-field.js b/api-js/src/enums/spf-order-field.js new file mode 100644 index 0000000000..2a66069158 --- /dev/null +++ b/api-js/src/enums/spf-order-field.js @@ -0,0 +1,24 @@ +import { GraphQLEnumType } from 'graphql' + +export const SpfOrderField = new GraphQLEnumType({ + name: 'SPFOrderField', + description: 'Properties by which SPF connections can be ordered.', + values: { + TIMESTAMP: { + value: 'timestamp', + description: 'Order SPF edges by timestamp.', + }, + LOOKUPS: { + value: 'lookups', + description: 'Order SPF edges by lookups.', + }, + RECORD: { + value: 'record', + description: 'Order SPF edges by record.', + }, + SPF_DEFAULT: { + value: 'spf-default', + description: 'Order SPF edges by spf-default.', + }, + }, +})