Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(geo): add listGeofences api #9310

Merged
merged 1 commit into from
Dec 6, 2021
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
49 changes: 49 additions & 0 deletions packages/geo/__tests__/Geo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
mockBatchPutGeofenceCommand,
geofencesWithInvalidId,
mockGetGeofenceCommand,
mockListGeofencesCommand,
} from './data';

LocationClient.prototype.send = jest.fn(async command => {
Expand Down Expand Up @@ -500,4 +501,52 @@ describe('Geo', () => {
);
});
});

describe('listGeofences', () => {
test('listGeofences gets the first 100 geofences when no arguments are given', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementationOnce(mockListGeofencesCommand);

const geo = new GeoClass();
geo.configure(awsConfig);

// Check that results are what's expected
const results = await geo.listGeofences();
expect(results.entries.length).toEqual(100);
});

test('listGeofences gets the second 100 geofences when nextToken is passed', async () => {
jest.spyOn(Credentials, 'get').mockImplementation(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementation(mockListGeofencesCommand);

const geo = new GeoClass();
geo.configure(awsConfig);

// Check that results are what's expected

const first100Geofences = await geo.listGeofences();

const second100Geofences = await geo.listGeofences({
nextToken: first100Geofences.nextToken,
});

expect(second100Geofences.entries.length).toEqual(100);
expect(second100Geofences.entries[0].geofenceId).toEqual(
'validGeofenceId100'
);
expect(second100Geofences.entries[99].geofenceId).toEqual(
'validGeofenceId199'
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
mockBatchPutGeofenceCommand,
validGeometry,
mockGetGeofenceCommand,
mockListGeofencesCommand,
} from '../data';
import {
SearchByTextOptions,
Expand Down Expand Up @@ -535,4 +536,63 @@ describe('AmazonLocationServiceProvider', () => {
);
});
});

describe('listGeofences', () => {
test('listGeofences gets the first 100 geofences when no arguments are given', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementation(mockListGeofencesCommand);

const locationProvider = new AmazonLocationServiceProvider();
locationProvider.configure(awsConfig.geo.amazon_location_service);

const geofences = await locationProvider.listGeofences();

expect(geofences.entries.length).toEqual(100);
});

test('listGeofences gets the second 100 geofences when nextToken is passed', async () => {
jest.spyOn(Credentials, 'get').mockImplementation(() => {
return Promise.resolve(credentials);
});

LocationClient.prototype.send = jest
.fn()
.mockImplementation(mockListGeofencesCommand);

const locationProvider = new AmazonLocationServiceProvider();
locationProvider.configure(awsConfig.geo.amazon_location_service);

const first100Geofences = await locationProvider.listGeofences();

const second100Geofences = await locationProvider.listGeofences({
nextToken: first100Geofences.nextToken,
});

expect(second100Geofences.entries.length).toEqual(100);
expect(second100Geofences.entries[0].geofenceId).toEqual(
'validGeofenceId100'
);
expect(second100Geofences.entries[99].geofenceId).toEqual(
'validGeofenceId199'
);
});

test('should error if there are no geofenceCollections in config', async () => {
jest.spyOn(Credentials, 'get').mockImplementationOnce(() => {
return Promise.resolve(credentials);
});

const locationProvider = new AmazonLocationServiceProvider();
locationProvider.configure({});

await expect(locationProvider.listGeofences()).rejects.toThrow(
'No Geofence Collections found, please run `amplify add geo` to create one and run `amplify push` after.'
);
});
});
});
33 changes: 33 additions & 0 deletions packages/geo/__tests__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import {
BatchPutGeofenceCommand,
GetGeofenceCommand,
ListGeofencesCommand,
} from '@aws-sdk/client-location';
import camelcaseKeys from 'camelcase-keys';

Expand Down Expand Up @@ -233,6 +234,22 @@ export function createGeofenceInputArray(numberOfGeofences) {
return geofences;
}

export function createGeofenceOutputArray(numberOfGeofences) {
const geofences = [];
for (let i = 0; i < numberOfGeofences; i++) {
geofences.push({
GeofenceId: `validGeofenceId${i}`,
Geometry: {
Polygon: validPolygon,
},
Status: 'ACTIVE',
CreateTime: '2020-04-01T21:00:00.000Z',
UpdateTime: '2020-04-01T21:00:00.000Z',
});
}
return geofences;
}

export function mockBatchPutGeofenceCommand(command) {
if (command instanceof BatchPutGeofenceCommand) {
return {
Expand Down Expand Up @@ -263,3 +280,19 @@ export function mockGetGeofenceCommand(command) {
return geofence;
}
}

export function mockListGeofencesCommand(command) {
if (command instanceof ListGeofencesCommand) {
const geofences = createGeofenceOutputArray(200);
if (command.input.NextToken === 'THIS IS YOUR TOKEN') {
return {
Entries: geofences.slice(100, 200),
NextToken: 'THIS IS YOUR SECOND TOKEN',
};
}
return {
Entries: geofences.slice(0, 100),
NextToken: 'THIS IS YOUR TOKEN',
};
}
}
23 changes: 23 additions & 0 deletions packages/geo/src/Geo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
GeofenceOptions,
CreateUpdateGeofenceResults,
Geofence,
ListGeofenceOptions,
ListGeofenceResults,
} from './types';

const logger = new Logger('Geo');
Expand Down Expand Up @@ -240,6 +242,27 @@ export class GeoClass {
throw error;
}
}

/**
* List geofences from a geofence collection
* @param options?: ListGeofenceOptions
* @returns {Promise<ListGeofencesResults>} - Promise that resolves to an object with:
* entries: list of geofences - 100 geofences are listed per page
* nextToken: token for next page of geofences
*/
public async listGeofences(
options?: ListGeofenceOptions
): Promise<ListGeofenceResults> {
const { providerName = DEFAULT_PROVIDER } = options || {};
const prov = this.getPluggable(providerName);

try {
return await prov.listGeofences(options);
} catch (error) {
logger.debug(error);
throw error;
}
}
}

export const Geo = new GeoClass();
Expand Down
85 changes: 85 additions & 0 deletions packages/geo/src/Providers/AmazonLocationServiceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import {
GetGeofenceCommand,
GetGeofenceCommandInput,
GetGeofenceCommandOutput,
ListGeofencesCommand,
ListGeofencesCommandInput,
ListGeofencesCommandOutput,
} from '@aws-sdk/client-location';

import {
Expand All @@ -42,6 +45,8 @@ import {
Coordinates,
GeofenceInput,
AmazonLocationServiceGeofenceOptions,
AmazonLocationServiceListGeofenceOptions,
ListGeofenceResults,
AmazonLocationServiceGeofenceStatus,
CreateUpdateGeofenceResults,
AmazonLocationServiceGeofence,
Expand Down Expand Up @@ -425,6 +430,86 @@ export class AmazonLocationServiceProvider implements GeoProvider {
return geofence;
}

/**
* List geofences from a geofence collection
* @param options?: ListGeofenceOptions
* @returns {Promise<ListGeofencesResults>} - Promise that resolves to an object with:
* entries: list of geofences - 100 geofences are listed per page
* nextToken: token for next page of geofences
*/
public async listGeofences(
options?: AmazonLocationServiceListGeofenceOptions
): Promise<ListGeofenceResults> {
const credentialsOK = await this._ensureCredentials();
if (!credentialsOK) {
throw new Error('No credentials');
}

// Verify geofence collection exists in aws-config.js
try {
this._verifyGeofenceCollections(options?.collectionName);
} catch (error) {
logger.debug(error);
throw error;
}

// Create Amazon Location Service Client
const client = new LocationClient({
credentials: this._config.credentials,
region: this._config.region,
customUserAgent: getAmplifyUserAgent(),
});

// Create Amazon Location Service input
const listGeofencesInput: ListGeofencesCommandInput = {
NextToken: options?.nextToken,
CollectionName:
options?.collectionName || this._config.geofenceCollections.default,
};

// Create Amazon Location Service command
const command: ListGeofencesCommand = new ListGeofencesCommand(
listGeofencesInput
);

// Make API call
let response: ListGeofencesCommandOutput;
try {
response = await client.send(command);
} catch (error) {
logger.debug(error);
throw error;
}

// Convert response to camelCase for return
const { NextToken, Entries } = response;

const results: ListGeofenceResults = {
entries: Entries.map(
({
GeofenceId,
CreateTime,
UpdateTime,
Status,
Geometry: { Polygon },
}) => {
return {
geofenceId: GeofenceId,
createTime: CreateTime,
updateTime: UpdateTime,
status: Status,
geometry: {
polygon: Polygon as GeofencePolygon,
},
};
}
),
nextToken: NextToken,
};

return results;
}

/**
* @private
*/
Expand Down
11 changes: 10 additions & 1 deletion packages/geo/src/types/AmazonLocationServiceProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { MapStyle, GeofenceOptions, Geofence } from './Geo';
import {
MapStyle,
GeofenceOptions,
ListGeofenceOptions,
Geofence,
} from './Geo';

// Maps
export interface AmazonLocationServiceMapStyle extends MapStyle {
Expand All @@ -21,3 +26,7 @@ export type AmazonLocationServiceGeofenceStatus =
export type AmazonLocationServiceGeofence = Geofence & {
status: AmazonLocationServiceGeofenceStatus;
};

export type AmazonLocationServiceListGeofenceOptions = ListGeofenceOptions & {
collectionName?: string;
};
15 changes: 13 additions & 2 deletions packages/geo/src/types/Geo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,24 @@ type GeofenceBase = {
updateTime?: Date;
};

// Output object for getGeofence
// Results object for getGeofence
export type Geofence = GeofenceBase & {
geometry: PolygonGeometry;
};

// Output object for createGeofence and updateGeofence
// Results object for createGeofence and updateGeofence
export type CreateUpdateGeofenceResults = {
successes: GeofenceBase[];
errors: GeofenceError[];
};

// Options object for listGeofence
export type ListGeofenceOptions = GeofenceOptions & {
nextToken?: string;
};

// Results options for listGeofence
export type ListGeofenceResults = {
entries: Geofence[];
nextToken: string;
};
10 changes: 9 additions & 1 deletion packages/geo/src/types/Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
Geofence,
GeofenceInput,
GeofenceOptions,
ListGeofenceOptions,
ListGeofenceResults,
CreateUpdateGeofenceResults,
} from './Geo';

Expand Down Expand Up @@ -54,5 +56,11 @@ export interface GeoProvider {
): Promise<CreateUpdateGeofenceResults>;

// get a single geofence
getGeofence(geofenceId: string, options?: GeofenceOptions): Promise<Geofence>;
getGeofence(
geofenceId: string,
options?: ListGeofenceOptions
): Promise<Geofence>;

// list all geofences
listGeofences(options?: ListGeofenceOptions): Promise<ListGeofenceResults>;
}