From 87a1d1089724c363344214645cdd33c55df54dcb Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 5 May 2022 16:31:33 +0200 Subject: [PATCH 01/10] Implement Stats API fn --- packages/react-api/src/api/common.js | 8 +++- packages/react-api/src/api/lds.js | 9 ++-- packages/react-api/src/api/stats.js | 72 ++++++++++++++++++++++++++++ packages/react-api/src/index.d.ts | 1 + packages/react-api/src/index.js | 1 + 5 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 packages/react-api/src/api/stats.js diff --git a/packages/react-api/src/api/common.js b/packages/react-api/src/api/common.js index c9cdd55a2..61222991c 100644 --- a/packages/react-api/src/api/common.js +++ b/packages/react-api/src/api/common.js @@ -1,7 +1,7 @@ /** * Return more descriptive error from API */ -export function dealWithApiError({ credentials, response, data }) { +export function dealWithApiError({ response, data }) { switch (response.status) { case 401: throw new Error('Unauthorized access. Invalid credentials'); @@ -11,3 +11,9 @@ export function dealWithApiError({ credentials, response, data }) { throw new Error(`${JSON.stringify(data?.hint || data.error?.[0])}`); } } + +export function checkCredentials(credentials) { + if (!credentials || !credentials.apiBaseUrl || !credentials.accessToken) { + throw new Error('Missing or bad credentials provided'); + } +} diff --git a/packages/react-api/src/api/lds.js b/packages/react-api/src/api/lds.js index f79b50399..787c91288 100644 --- a/packages/react-api/src/api/lds.js +++ b/packages/react-api/src/api/lds.js @@ -1,4 +1,4 @@ -import { dealWithApiError } from './common'; +import { checkCredentials, dealWithApiError } from './common'; /** * Execute a LDS geocoding service geocode request. @@ -13,9 +13,8 @@ import { dealWithApiError } from './common'; * @param { Object= } props.opts - Additional options for the HTTP request */ export async function ldsGeocode({ credentials, address, country, limit, opts }) { - if (!credentials || !credentials.apiBaseUrl || !credentials.accessToken) { - throw new Error('ldsGeocode: Missing or bad credentials provided'); - } + checkCredentials(credentials); + if (!address) { throw new Error('ldsGeocode: No address provided'); } @@ -51,7 +50,7 @@ export async function ldsGeocode({ credentials, address, country, limit, opts }) data = data[0]; } if (!response.ok) { - dealWithApiError({ credentials, response, data }); + dealWithApiError({ response, data }); } return data.value; diff --git a/packages/react-api/src/api/stats.js b/packages/react-api/src/api/stats.js new file mode 100644 index 000000000..5e24bccd3 --- /dev/null +++ b/packages/react-api/src/api/stats.js @@ -0,0 +1,72 @@ +import { checkCredentials, dealWithApiError } from './common'; +import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; + +const mandatoryProps = ['source', 'column']; + +/** + * Execute a stats service request. + * + * @param { object } props + * @param { string } props.column - + * @param { object } props.source - + * @param { object= } props.opts - Additional options for the HTTP request + */ +export async function callStats(props) { + mandatoryProps.forEach((prop) => { + if (!props[prop]) throw new Error('callStats: Missing source'); + }); + + const { source, column, opts } = props; + + checkCredentials(source.credentials); + if (source.type === MAP_TYPES.TILESET) { + throw new Error('callStats cannot be used with static tilesets'); + } + if (source.credentials.apiVersion === API_VERSIONS.V2) { + throw new Error('callStats cannot be used with CARTO 2.'); + } + + const url = buildUrl(source, column); + + const { abortController, ...otherOptions } = opts || {}; + + let response; + let data; + try { + response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${source.credentials.accessToken}` + }, + signal: abortController?.signal, + ...otherOptions + }); + data = await response.json(); + } catch (error) { + if (error.name === 'AbortError') throw error; + + throw new Error(`Failed to connect to LDS API: ${error}`); + } + + if (!response.ok) { + dealWithApiError({ response, data }); + } + + return data; +} + +// Aux +function buildUrl(source, column) { + const isQuery = source.type === MAP_TYPES.QUERY; + + const url = new URL( + `${source.credentials.apiBaseUrl}/v3/stats/${source.connection}/${ + isQuery ? column : `${source.data}/${column}` + }` + ); + + if (source.type === MAP_TYPES.QUERY) { + url.searchParams.set('q', source.data); + } + + return url; +} diff --git a/packages/react-api/src/index.d.ts b/packages/react-api/src/index.d.ts index f6c210854..627b3f7a4 100644 --- a/packages/react-api/src/index.d.ts +++ b/packages/react-api/src/index.d.ts @@ -1,5 +1,6 @@ export { executeSQL } from './api/SQL'; export { ldsGeocode } from './api/lds'; +export { callStats } from './api/stats'; export { default as useCartoLayerProps } from './hooks/useCartoLayerProps'; diff --git a/packages/react-api/src/index.js b/packages/react-api/src/index.js index bcfa6d009..2272ce563 100644 --- a/packages/react-api/src/index.js +++ b/packages/react-api/src/index.js @@ -1,5 +1,6 @@ export { executeSQL } from './api/SQL'; export { ldsGeocode } from './api/lds'; +export { callStats } from './api/stats'; export { default as useCartoLayerProps } from './hooks/useCartoLayerProps'; From 1f6e2294bbe171d22955feb50e9e042f24920eca Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 5 May 2022 16:38:52 +0200 Subject: [PATCH 02/10] Generalise common parts in lds and stats --- packages/react-api/src/api/common.js | 25 ++++++++++++++++++++++++ packages/react-api/src/api/lds.js | 23 ++-------------------- packages/react-api/src/api/stats.js | 29 +++------------------------- 3 files changed, 30 insertions(+), 47 deletions(-) diff --git a/packages/react-api/src/api/common.js b/packages/react-api/src/api/common.js index 61222991c..3d914a7e4 100644 --- a/packages/react-api/src/api/common.js +++ b/packages/react-api/src/api/common.js @@ -17,3 +17,28 @@ export function checkCredentials(credentials) { throw new Error('Missing or bad credentials provided'); } } + +export async function makeCall({ url, credentials, opts }) { + let response; + let data; + try { + response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${credentials.accessToken}` + }, + signal: opts?.abortController?.signal, + ...opts?.otherOptions + }); + data = await response.json(); + } catch (error) { + if (error.name === 'AbortError') throw error; + + throw new Error(`Failed request: ${error}`); + } + + if (!response.ok) { + dealWithApiError({ response, data }); + } + + return data; +} diff --git a/packages/react-api/src/api/lds.js b/packages/react-api/src/api/lds.js index 787c91288..364d2e98b 100644 --- a/packages/react-api/src/api/lds.js +++ b/packages/react-api/src/api/lds.js @@ -1,4 +1,4 @@ -import { checkCredentials, dealWithApiError } from './common'; +import { checkCredentials, dealWithApiError, makeCall } from './common'; /** * Execute a LDS geocoding service geocode request. @@ -28,30 +28,11 @@ export async function ldsGeocode({ credentials, address, country, limit, opts }) url.searchParams.set('limit', String(limit)); } - const { abortController, ...otherOptions } = opts || {}; + let data = await makeCall({ url, credentials, opts }); - let response; - let data; - try { - response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${credentials.accessToken}` - }, - signal: abortController?.signal, - ...otherOptions - }); - data = await response.json(); - } catch (error) { - if (error.name === 'AbortError') throw error; - - throw new Error(`Failed to connect to LDS API: ${error}`); - } if (Array.isArray(data)) { data = data[0]; } - if (!response.ok) { - dealWithApiError({ response, data }); - } return data.value; } diff --git a/packages/react-api/src/api/stats.js b/packages/react-api/src/api/stats.js index 5e24bccd3..6ace5220e 100644 --- a/packages/react-api/src/api/stats.js +++ b/packages/react-api/src/api/stats.js @@ -1,4 +1,4 @@ -import { checkCredentials, dealWithApiError } from './common'; +import { checkCredentials, dealWithApiError, makeCall } from './common'; import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; const mandatoryProps = ['source', 'column']; @@ -28,30 +28,7 @@ export async function callStats(props) { const url = buildUrl(source, column); - const { abortController, ...otherOptions } = opts || {}; - - let response; - let data; - try { - response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${source.credentials.accessToken}` - }, - signal: abortController?.signal, - ...otherOptions - }); - data = await response.json(); - } catch (error) { - if (error.name === 'AbortError') throw error; - - throw new Error(`Failed to connect to LDS API: ${error}`); - } - - if (!response.ok) { - dealWithApiError({ response, data }); - } - - return data; + return makeCall({ url, credentials: source.credentials, opts }); } // Aux @@ -64,7 +41,7 @@ function buildUrl(source, column) { }` ); - if (source.type === MAP_TYPES.QUERY) { + if (isQuery) { url.searchParams.set('q', source.data); } From 2788086b0ea07eb17b34ebc51591a16d0ed3ee6d Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 5 May 2022 16:39:24 +0200 Subject: [PATCH 03/10] Remove unused imports --- packages/react-api/src/api/lds.js | 2 +- packages/react-api/src/api/stats.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-api/src/api/lds.js b/packages/react-api/src/api/lds.js index 364d2e98b..c58e91a90 100644 --- a/packages/react-api/src/api/lds.js +++ b/packages/react-api/src/api/lds.js @@ -1,4 +1,4 @@ -import { checkCredentials, dealWithApiError, makeCall } from './common'; +import { checkCredentials, makeCall } from './common'; /** * Execute a LDS geocoding service geocode request. diff --git a/packages/react-api/src/api/stats.js b/packages/react-api/src/api/stats.js index 6ace5220e..26b017e8f 100644 --- a/packages/react-api/src/api/stats.js +++ b/packages/react-api/src/api/stats.js @@ -1,4 +1,4 @@ -import { checkCredentials, dealWithApiError, makeCall } from './common'; +import { checkCredentials, makeCall } from './common'; import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; const mandatoryProps = ['source', 'column']; From 8ada59bc5138935dedb5180a88e3524f4172321f Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Wed, 11 May 2022 16:01:49 +0200 Subject: [PATCH 04/10] Use assert --- packages/react-api/src/api/common.js | 6 ++++++ packages/react-api/src/api/stats.js | 17 ++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/react-api/src/api/common.js b/packages/react-api/src/api/common.js index 3d914a7e4..c07c24624 100644 --- a/packages/react-api/src/api/common.js +++ b/packages/react-api/src/api/common.js @@ -12,6 +12,12 @@ export function dealWithApiError({ response, data }) { } } +export function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + export function checkCredentials(credentials) { if (!credentials || !credentials.apiBaseUrl || !credentials.accessToken) { throw new Error('Missing or bad credentials provided'); diff --git a/packages/react-api/src/api/stats.js b/packages/react-api/src/api/stats.js index 26b017e8f..0095e3b7b 100644 --- a/packages/react-api/src/api/stats.js +++ b/packages/react-api/src/api/stats.js @@ -1,8 +1,6 @@ -import { checkCredentials, makeCall } from './common'; +import { assert, checkCredentials, makeCall } from './common'; import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; -const mandatoryProps = ['source', 'column']; - /** * Execute a stats service request. * @@ -12,19 +10,20 @@ const mandatoryProps = ['source', 'column']; * @param { object= } props.opts - Additional options for the HTTP request */ export async function callStats(props) { - mandatoryProps.forEach((prop) => { - if (!props[prop]) throw new Error('callStats: Missing source'); - }); + assert(props.source, 'callStats: missing source'); + assert(props.column, 'callStats: missing column'); const { source, column, opts } = props; checkCredentials(source.credentials); + if (source.type === MAP_TYPES.TILESET) { throw new Error('callStats cannot be used with static tilesets'); } - if (source.credentials.apiVersion === API_VERSIONS.V2) { - throw new Error('callStats cannot be used with CARTO 2.'); - } + assert( + source.credentials.apiVersion === API_VERSIONS.V3, + 'Stats API is a feature only available in CARTO 3.' + ); const url = buildUrl(source, column); From 55cfc6c342a3d4d60bc30567820bb00cf6a10c63 Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 12 May 2022 09:15:32 +0200 Subject: [PATCH 05/10] Implement getTileJson --- packages/react-api/src/api/stats.js | 8 +++--- packages/react-api/src/api/tilejson.js | 36 ++++++++++++++++++++++++++ packages/react-api/src/index.d.ts | 3 ++- packages/react-api/src/index.js | 3 ++- 4 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 packages/react-api/src/api/tilejson.js diff --git a/packages/react-api/src/api/stats.js b/packages/react-api/src/api/stats.js index 0095e3b7b..3c059af39 100644 --- a/packages/react-api/src/api/stats.js +++ b/packages/react-api/src/api/stats.js @@ -9,16 +9,16 @@ import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; * @param { object } props.source - * @param { object= } props.opts - Additional options for the HTTP request */ -export async function callStats(props) { - assert(props.source, 'callStats: missing source'); - assert(props.column, 'callStats: missing column'); +export async function getStats(props) { + assert(props.source, 'getStats: missing source'); + assert(props.column, 'getStats: missing column'); const { source, column, opts } = props; checkCredentials(source.credentials); if (source.type === MAP_TYPES.TILESET) { - throw new Error('callStats cannot be used with static tilesets'); + throw new Error('getStats cannot be used with static tilesets'); } assert( source.credentials.apiVersion === API_VERSIONS.V3, diff --git a/packages/react-api/src/api/tilejson.js b/packages/react-api/src/api/tilejson.js new file mode 100644 index 000000000..a37ace7a6 --- /dev/null +++ b/packages/react-api/src/api/tilejson.js @@ -0,0 +1,36 @@ +import { assert, checkCredentials } from './common'; +import { MAP_TYPES, API_VERSIONS, fetchLayerData, FORMATS } from '@deck.gl/carto'; + +/** + * Get the TileJson for static tilesets + * + * @param { object } props + * @param { object } props.source - A static tileset C4R source + */ +export async function getTileJson(props) { + const { source } = props; + + assert(source, 'getTileJson: missing source'); + assert( + source.type === MAP_TYPES.TILESET, + 'getTileJson: source must be a static tileset' + ); + checkCredentials(source.credentials); + assert( + source.credentials.apiVersion === API_VERSIONS.V3, + 'TileJson is a feature only available in CARTO 3.' + ); + + const { data, format } = await fetchLayerData({ + type: source.type, + source: source.data, + connection: source.connection, + credentials: source.credentials, + format: FORMATS.TILEJSON, + clientId: 'carto-for-react' + }); + + assert(format === FORMATS.TILEJSON, 'getTileJson: data is not a tilejson'); + + return data; +} diff --git a/packages/react-api/src/index.d.ts b/packages/react-api/src/index.d.ts index 627b3f7a4..cf244fa13 100644 --- a/packages/react-api/src/index.d.ts +++ b/packages/react-api/src/index.d.ts @@ -1,6 +1,7 @@ export { executeSQL } from './api/SQL'; export { ldsGeocode } from './api/lds'; -export { callStats } from './api/stats'; +export { getStats } from './api/stats'; +export { getTileJson } from './api/tilejson'; export { default as useCartoLayerProps } from './hooks/useCartoLayerProps'; diff --git a/packages/react-api/src/index.js b/packages/react-api/src/index.js index 2272ce563..c891d8cb6 100644 --- a/packages/react-api/src/index.js +++ b/packages/react-api/src/index.js @@ -1,6 +1,7 @@ export { executeSQL } from './api/SQL'; export { ldsGeocode } from './api/lds'; -export { callStats } from './api/stats'; +export { getStats } from './api/stats'; +export { getTileJson } from './api/tilejson'; export { default as useCartoLayerProps } from './hooks/useCartoLayerProps'; From dd60f8fff4bd29de97b7da41a948b394b3d62df9 Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 12 May 2022 09:25:38 +0200 Subject: [PATCH 06/10] Support tileset stats in getStats fn --- packages/react-api/src/api/stats.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/react-api/src/api/stats.js b/packages/react-api/src/api/stats.js index 3c059af39..67423a30b 100644 --- a/packages/react-api/src/api/stats.js +++ b/packages/react-api/src/api/stats.js @@ -1,5 +1,6 @@ import { assert, checkCredentials, makeCall } from './common'; import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; +import { getTileJson } from './tilejson'; /** * Execute a stats service request. @@ -17,17 +18,22 @@ export async function getStats(props) { checkCredentials(source.credentials); - if (source.type === MAP_TYPES.TILESET) { - throw new Error('getStats cannot be used with static tilesets'); - } assert( source.credentials.apiVersion === API_VERSIONS.V3, 'Stats API is a feature only available in CARTO 3.' ); - const url = buildUrl(source, column); - - return makeCall({ url, credentials: source.credentials, opts }); + if (source.type === MAP_TYPES.TILESET) { + const tileJson = await getTileJson({ source }); + const tileStatsAttributes = tileJson.tilestats.layers[0].attributes; + const columnStats = tileStatsAttributes.find(({ attribute }) => attribute === column); + assert(columnStats, 'getStats: column not found in tileset attributes'); + return columnStats; + } else { + const url = buildUrl(source, column); + + return makeCall({ url, credentials: source.credentials, opts }); + } } // Aux From d4ee540b17ec12030c54500f8223e29fe55c253c Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 19 May 2022 09:16:54 +0200 Subject: [PATCH 07/10] Add tilejson test --- .../react-api/__tests__/api/tilejson.test.js | 53 +++++++++++++++++++ packages/react-api/src/api/tilejson.js | 2 + 2 files changed, 55 insertions(+) create mode 100644 packages/react-api/__tests__/api/tilejson.test.js diff --git a/packages/react-api/__tests__/api/tilejson.test.js b/packages/react-api/__tests__/api/tilejson.test.js new file mode 100644 index 000000000..eab743f4f --- /dev/null +++ b/packages/react-api/__tests__/api/tilejson.test.js @@ -0,0 +1,53 @@ +import { getTileJson } from '../../src/api/tilejson'; +import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; + +const mockedFetchLayerData = jest.fn(); + +jest.mock('@deck.gl/carto', () => ({ + ...jest.requireActual('@deck.gl/carto'), + fetchLayerData: (props) => { + mockedFetchLayerData(props); + return Promise.resolve({ + data: {}, + format: 'tilejson' + }); + } +})); + +const TEST_CONNECTION = '__test_connection__'; +const TEST_TILESET = '__test_tileset__'; +const TEST_API_KEY = '__test_api_key__'; + +describe('tilejson', () => { + describe('getTileJson', () => { + test('should return a tilejson', async () => { + const source = { + type: MAP_TYPES.TILESET, + data: TEST_TILESET, + connection: TEST_CONNECTION, + credentials: { + accessToken: TEST_API_KEY, + apiVersion: API_VERSIONS.V3, + apiBaseUrl: 'https://gcp-us-east1.api.carto.com' + } + }; + + const tilejson = await getTileJson({ source }); + + expect(mockedFetchLayerData).toBeCalledWith({ + clientId: 'carto-for-react', + connection: '__test_connection__', + credentials: { + accessToken: '__test_api_key__', + apiBaseUrl: 'https://gcp-us-east1.api.carto.com', + apiVersion: 'v3' + }, + format: 'tilejson', + source: '__test_tileset__', + type: 'tileset' + }); + + expect(tilejson).toBeDefined(); + }); + }); +}); diff --git a/packages/react-api/src/api/tilejson.js b/packages/react-api/src/api/tilejson.js index a37ace7a6..83532c889 100644 --- a/packages/react-api/src/api/tilejson.js +++ b/packages/react-api/src/api/tilejson.js @@ -11,6 +11,8 @@ export async function getTileJson(props) { const { source } = props; assert(source, 'getTileJson: missing source'); + assert(source.connection, 'getTileJson: missing connection'); + assert(source.data, 'getTileJson: missing data'); assert( source.type === MAP_TYPES.TILESET, 'getTileJson: source must be a static tileset' From 5bea0c0766d118185a00f2b1ebbc25a990de2812 Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 19 May 2022 12:14:03 +0200 Subject: [PATCH 08/10] Add stats tests --- .../react-api/__tests__/api/stats.test.js | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 packages/react-api/__tests__/api/stats.test.js diff --git a/packages/react-api/__tests__/api/stats.test.js b/packages/react-api/__tests__/api/stats.test.js new file mode 100644 index 000000000..dcd94617b --- /dev/null +++ b/packages/react-api/__tests__/api/stats.test.js @@ -0,0 +1,179 @@ +import { getStats } from '../../src/api/stats'; + +const TABLE_AND_QUERY_STATS = { + attribute: 'injuries', + type: 'Number', + avg: 0.44776268477826375, + sum: 4483, + min: 0, + max: 7, + quantiles: { + 3: [0, 7], + 4: [0, 0, 7], + 5: [0, 0, 1, 7], + 6: [0, 0, 0, 1, 7], + 7: [0, 0, 0, 0, 1, 7], + 8: [0, 0, 0, 0, 1, 1, 7], + 9: [0, 0, 0, 0, 0, 1, 1, 7], + 10: [0, 0, 0, 0, 0, 0, 1, 1, 7], + 11: [0, 0, 0, 0, 0, 0, 1, 1, 1, 7], + 12: [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 7], + 13: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 7], + 14: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 7], + 15: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 7], + 16: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 7], + 17: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 7], + 18: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 7], + 19: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 7], + 20: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 7] + } +}; + +const mockedGetTileJson = jest.fn(); + +jest.mock('../../src/api/tilejson', () => ({ + getTileJson: (props) => { + mockedGetTileJson(props); + return Promise.resolve({ tilestats: mockTilestats }); + } +})); + +const mockTilestats = { + layerCount: 1, + layers: [ + { + layer: 'default', + count: 504931, + geometry: 'Point', + attributeCount: 5, + attributes: [ + { + attribute: 'name', + type: 'String', + categories: [ + { category: null, frequency: 236730 }, + { category: 'Banco Santander', frequency: 2250 }, + { category: 'Caixabank', frequency: 2022 } + ] + }, + { + attribute: 'group_name', + type: 'String', + categories: [ + { category: 'Others', frequency: 147789 }, + { category: 'Sustenance', frequency: 94220 }, + { category: 'Transportation', frequency: 92761 } + ] + } + ] + } + ] +}; + +describe('stats', () => { + describe('getStats', () => { + test('table source - should return stats', async () => { + const TABLE_TEST = { + input: { + source: { + type: 'table', + data: 'cartobq.public_account.seattle_collisions', + connection: 'carto-ps-bq-developers', + credentials: { + accessToken: '__test_api_key__', + apiBaseUrl: 'https://gcp-us-east1.api.carto.com', + apiVersion: 'v3' + } + }, + column: 'injuries' + }, + url: `https://gcp-us-east1.api.carto.com/v3/stats/carto-ps-bq-developers/cartobq.public_account.seattle_collisions/injuries`, + output: TABLE_AND_QUERY_STATS + }; + + const fetchMock = (global.fetch = jest.fn().mockImplementation(async () => { + return { + ok: true, + json: async () => TABLE_TEST.output + }; + })); + + const abortController = new AbortController(); + + const res = await getStats({ ...TABLE_TEST.input, opts: { abortController } }); + + expect(res).toEqual(TABLE_TEST.output); + + expect(fetchMock).toBeCalledWith(TABLE_TEST.url, { + headers: { + Authorization: `Bearer ${TABLE_TEST.input.source.credentials.accessToken}` + }, + signal: abortController.signal + }); + }); + + test('query source - should return stats', async () => { + const QUERY_TEST = { + input: { + source: { + type: 'query', + data: 'SELECT * FROM `cartobq.public_account.seattle_collisions`', + connection: 'carto-ps-bq-developers', + credentials: { + accessToken: '__test_api_key__', + apiBaseUrl: 'https://gcp-us-east1.api.carto.com', + apiVersion: 'v3' + } + }, + column: 'injuries' + }, + url: `https://gcp-us-east1.api.carto.com/v3/stats/carto-ps-bq-developers/injuries?q=SELECT+*+FROM+%60cartobq.public_account.seattle_collisions%60`, + output: TABLE_AND_QUERY_STATS + }; + + const fetchMock = (global.fetch = jest.fn().mockImplementation(async () => { + return { + ok: true, + json: async () => QUERY_TEST.output + }; + })); + + const abortController = new AbortController(); + + const res = await getStats({ ...QUERY_TEST.input, opts: { abortController } }); + + expect(res).toEqual(QUERY_TEST.output); + + expect(fetchMock).toBeCalledWith(QUERY_TEST.url, { + headers: { + Authorization: `Bearer ${QUERY_TEST.input.source.credentials.accessToken}` + }, + signal: abortController.signal + }); + }); + + test('tileset source - should return stats', async () => { + const TILESET_TEST = { + input: { + source: { + type: 'tileset', + data: 'cartobq.public_account.pois', + connection: 'carto-ps-bq-developers', + credentials: { + accessToken: '__test_api_key__', + apiBaseUrl: 'https://gcp-us-east1.api.carto.com', + apiVersion: 'v3' + } + }, + column: 'name' + }, + output: mockTilestats.layers[0].attributes[0] + }; + + const res = await getStats(TILESET_TEST.input); + + expect(res).toEqual(TILESET_TEST.output); + expect(mockedGetTileJson).toBeCalledWith({ source: TILESET_TEST.input.source }); + }); + }); +}); From 19177b12b51fdef5a34d55073f1d77c252c3c463 Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 19 May 2022 16:30:58 +0200 Subject: [PATCH 09/10] Export as private --- packages/react-api/src/api/stats.js | 4 ++-- packages/react-api/src/index.d.ts | 4 ++-- packages/react-api/src/index.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-api/src/api/stats.js b/packages/react-api/src/api/stats.js index 67423a30b..0298414f2 100644 --- a/packages/react-api/src/api/stats.js +++ b/packages/react-api/src/api/stats.js @@ -6,8 +6,8 @@ import { getTileJson } from './tilejson'; * Execute a stats service request. * * @param { object } props - * @param { string } props.column - - * @param { object } props.source - + * @param { string } props.column - column to get stats for + * @param { object } props.source - source that owns the column * @param { object= } props.opts - Additional options for the HTTP request */ export async function getStats(props) { diff --git a/packages/react-api/src/index.d.ts b/packages/react-api/src/index.d.ts index cf244fa13..181f4848e 100644 --- a/packages/react-api/src/index.d.ts +++ b/packages/react-api/src/index.d.ts @@ -1,7 +1,7 @@ export { executeSQL } from './api/SQL'; export { ldsGeocode } from './api/lds'; -export { getStats } from './api/stats'; -export { getTileJson } from './api/tilejson'; +export { getStats as _getStats } from './api/stats'; +export { getTileJson as _getTileJson } from './api/tilejson'; export { default as useCartoLayerProps } from './hooks/useCartoLayerProps'; diff --git a/packages/react-api/src/index.js b/packages/react-api/src/index.js index c891d8cb6..a914c4995 100644 --- a/packages/react-api/src/index.js +++ b/packages/react-api/src/index.js @@ -1,7 +1,7 @@ export { executeSQL } from './api/SQL'; export { ldsGeocode } from './api/lds'; -export { getStats } from './api/stats'; -export { getTileJson } from './api/tilejson'; +export { getStats as _getStats } from './api/stats'; +export { getTileJson as _getTileJson } from './api/tilejson'; export { default as useCartoLayerProps } from './hooks/useCartoLayerProps'; From 3a05d48ef5246139136a4141b207aeecd5919ec2 Mon Sep 17 00:00:00 2001 From: Sergio Clebal Date: Thu, 19 May 2022 16:31:53 +0200 Subject: [PATCH 10/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa8aba87..a89ed6fff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ ## Not released +- Implement Stats API fn [#404](https://github.com/CartoDB/carto-react/pull/404) - Fix map filtering with CategoryWidget using boolean values [#411](https://github.com/CartoDB/carto-react/pull/411) - ## 1.3 ### 1.3.0-alpha.10 (2022-05-12)