Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions enrich/geocode/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!-- START_INFOCARD -->
# @flatfile/plugin-enrich-geocode

The `@flatfile/plugin-enrich-geocode` plugin for geocoding addresses using the Google Maps Geocoding API. This plugin enables automatic geocoding of addresses, reverse geocoding of coordinates, and enrichment of location data within your Flatfile imports.

**Event Type:**
`listener.on('commit:created')`

<!-- END_INFOCARD -->

## Features

- Forward geocoding: Convert addresses to latitude and longitude coordinates
- Reverse geocoding: Convert latitude and longitude coordinates to formatted addresses
- Automatic geocoding based on available data
- Customizable field mapping
- Error handling for invalid inputs and API errors
- Extraction of additional location components (country, postal code)

## Parameters

The `enrichGeocode` function accepts a configuration object with the following properties:

- `sheetSlug` (string): The slug of the sheet containing address data. Default: "addresses"
- `addressField` (string): The field name for the address input. Default: "address"
- `latitudeField` (string): The field name for latitude. Default: "latitude"
- `longitudeField` (string): The field name for longitude. Default: "longitude"
- `autoGeocode` (boolean): Whether to automatically geocode records. Default: true

## Installation

To install the plugin, use npm:

```bash
npm install @flatfile/plugin-enrich-geocode
```

## Example Usage

```javascript
import type { FlatfileListener } from "@flatfile/listener";
import enrichGeocode from "@flatfile/plugin-enrich-geocode";

export default function (listener: FlatfileListener) {
listener.use(
enrichGeocode(listener, {
sheetSlug: 'customer_addresses',
addressField: 'full_address',
latitudeField: 'lat',
longitudeField: 'lng'
})
);

// ... rest of your Flatfile setup
}
```
16 changes: 16 additions & 0 deletions enrich/geocode/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
testEnvironment: 'node',

transform: {
'^.+\\.tsx?$': 'ts-jest',
},
setupFiles: ['../../test/dotenv-config.js'],
setupFilesAfterEnv: [
'../../test/betterConsoleLog.js',
'../../test/unit.cleanup.js',
],
testTimeout: 60_000,
globalSetup: '../../test/setup-global.js',
forceExit: true,
passWithNoTests: true,
}
73 changes: 73 additions & 0 deletions enrich/geocode/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"name": "@flatfile/plugin-enrich-geocode",
"version": "0.0.0",
"url": "https://github.com/FlatFilers/flatfile-plugins/tree/main/enrich/geocode",
"description": "A Flatfile plugin for geocoding addresses using the Google Maps Geocoding API",
"registryMetadata": {
"category": "transform"
},
"engines": {
"node": ">= 16"
},
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
],
"browser": {
"./dist/index.cjs": "./dist/index.browser.cjs",
"./dist/index.mjs": "./dist/index.browser.mjs"
},
"exports": {
"types": "./dist/index.d.ts",
"node": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"browser": {
"require": "./dist/index.browser.cjs",
"import": "./dist/index.browser.mjs"
},
"default": "./dist/index.mjs"
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"build": "rollup -c",
"build:watch": "rollup -c --watch",
"build:prod": "NODE_ENV=production rollup -c",
"check": "tsc ./**/*.ts --noEmit --esModuleInterop",
"test": "jest src/*.spec.ts --detectOpenHandles",
"test:unit": "jest src/*.spec.ts --testPathIgnorePatterns=.*\\.e2e\\.spec\\.ts$ --detectOpenHandles",
"test:e2e": "jest src/*.e2e.spec.ts --detectOpenHandles"
},
"keywords": [
"flatfile-plugins",
"category-enrich"
],
"author": "Flatfile, Inc.",
"repository": {
"type": "git",
"url": "https://github.com/FlatFilers/flatfile-plugins.git",
"directory": "enrich/geocode"
},
"license": "ISC",
"dependencies": {
"@flatfile/plugin-record-hook": "^1.6.1",
"cross-fetch": "^4.0.0"
},
"peerDependencies": {
"@flatfile/listener": "^1.0.5"
},
"devDependencies": {
"@flatfile/hooks": "^1.5.0",
"@flatfile/rollup-config": "^0.1.1",
"@types/node": "^22.6.1",
"typescript": "^5.6.2"
}
}
5 changes: 5 additions & 0 deletions enrich/geocode/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { buildConfig } from '@flatfile/rollup-config'

const config = buildConfig({})

export default config
138 changes: 138 additions & 0 deletions enrich/geocode/src/enrich.geocode.plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import fetch from 'cross-fetch'
import { performGeocoding } from './enrich.geocode.plugin'

jest.mock('cross-fetch')

describe('performGeocoding', () => {
const apiKey = 'test_api_key'

beforeEach(() => {
jest.clearAllMocks()
})

it('should perform forward geocoding successfully', async () => {
const mockResponse = {
status: 'OK',
results: [
{
geometry: {
location: { lat: 40.7128, lng: -74.006 },
},
formatted_address: 'New York, NY, USA',
address_components: [
{ types: ['country'], long_name: 'United States' },
{ types: ['postal_code'], long_name: '10001' },
],
},
],
}
;(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue({
json: jest.fn().mockResolvedValue(mockResponse),
} as any)

const result = await performGeocoding({ address: 'New York' }, apiKey)

expect(fetch).toHaveBeenCalledWith(
'https://maps.googleapis.com/maps/api/geocode/json?key=test_api_key&address=New+York'
)
expect(result).toEqual({
latitude: 40.7128,
longitude: -74.006,
formatted_address: 'New York, NY, USA',
country: 'United States',
postal_code: '10001',
})
})

it('should perform reverse geocoding successfully', async () => {
const mockResponse = {
status: 'OK',
results: [
{
geometry: {
location: { lat: 48.8584, lng: 2.2945 },
},
formatted_address: 'Eiffel Tower, Paris, France',
address_components: [
{ types: ['country'], long_name: 'France' },
{ types: ['postal_code'], long_name: '75007' },
],
},
],
}
;(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue({
json: jest.fn().mockResolvedValue(mockResponse),
} as any)

const result = await performGeocoding(
{ latitude: 48.8584, longitude: 2.2945 },
apiKey
)

expect(fetch).toHaveBeenCalledWith(
'https://maps.googleapis.com/maps/api/geocode/json?key=test_api_key&latlng=48.8584%2C2.2945'
)
expect(result).toEqual({
latitude: 48.8584,
longitude: 2.2945,
formatted_address: 'Eiffel Tower, Paris, France',
country: 'France',
postal_code: '75007',
})
})

it('should handle zero results', async () => {
const mockResponse = {
status: 'ZERO_RESULTS',
results: [],
}
;(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue({
json: jest.fn().mockResolvedValue(mockResponse),
} as any)

const result = await performGeocoding(
{ address: 'NonexistentPlace' },
apiKey
)

expect(result).toEqual({
message: 'No results found for the given input',
field: 'address',
})
})

it('should handle API errors', async () => {
;(fetch as jest.MockedFunction<typeof fetch>).mockRejectedValue(
new Error('API error')
)

const result = await performGeocoding({ address: 'New York' }, apiKey)

expect(result).toEqual({
message: 'API error: API error',
field: 'address',
})
})

it('should handle unexpected errors', async () => {
;(fetch as jest.MockedFunction<typeof fetch>).mockRejectedValue(
new Error('Network error')
)

const result = await performGeocoding({ address: 'New York' }, apiKey)

expect(result).toEqual({
message: 'API error: Network error',
field: 'address',
})
})

it('should return an error when neither address nor coordinates are provided', async () => {
const result = await performGeocoding({}, apiKey)

expect(result).toEqual({
message: 'Either address or both latitude and longitude are required',
field: 'input',
})
})
})
Loading