From 9a6af326856a4a6907038f7709b1d959d35c5099 Mon Sep 17 00:00:00 2001 From: Lawrence Owen Date: Tue, 14 Nov 2023 12:39:18 +1000 Subject: [PATCH] - Handle errors - log to analytics - handle empty result - add unit test --- CHANGES.md | 2 +- lib/Core/AnalyticEvents/analyticEvents.ts | 3 +- .../CesiumIonSearchProvider.ts | 70 +++++++++++++------ .../CesiumIonSearchProviderSpec.ts | 59 ++++++++++++++++ 4 files changed, 110 insertions(+), 24 deletions(-) create mode 100644 test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts diff --git a/CHANGES.md b/CHANGES.md index f350fab13ea..196b54828b5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,7 +15,7 @@ - See [ASGS 2021](https://www.abs.gov.au/statistics/standards/australian-statistical-geography-standard-asgs-edition-3/jul2021-jun2026/access-and-downloads/digital-boundary-files) - Added [Melbourne CLUE blocks](https://data.melbourne.vic.gov.au/pages/clue/) to region mapping. - Fix WMS `GetMap`/`GetFeatureInfo` requests not having `styles` parameter (will use empty string instead of `undefined`) -- [The next improvement] +- Add CesiumIon geocoder #### 8.3.7 - 2023-10-26 diff --git a/lib/Core/AnalyticEvents/analyticEvents.ts b/lib/Core/AnalyticEvents/analyticEvents.ts index daecf95c482..39b4cbc4abe 100644 --- a/lib/Core/AnalyticEvents/analyticEvents.ts +++ b/lib/Core/AnalyticEvents/analyticEvents.ts @@ -16,7 +16,8 @@ export enum SearchAction { bing = "Bing", catalog = "Catalog", gazetteer = "Gazetteer", - nominatim = "nominatim" + nominatim = "nominatim", + cesium = "Cesium" } export enum LaunchAction { diff --git a/lib/Models/SearchProviders/CesiumIonSearchProvider.ts b/lib/Models/SearchProviders/CesiumIonSearchProvider.ts index 032b6348a7f..56715c09d3b 100644 --- a/lib/Models/SearchProviders/CesiumIonSearchProvider.ts +++ b/lib/Models/SearchProviders/CesiumIonSearchProvider.ts @@ -1,18 +1,21 @@ import SearchProvider from "./SearchProvider"; import { observable, makeObservable } from "mobx"; -import Terria from "../Terria"; +import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; +import i18next from "i18next"; +import Terria from "../Terria"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; -import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import loadJson from "../../Core/loadJson"; +import { + Category, + SearchAction +} from "../../Core/AnalyticEvents/analyticEvents"; interface CesiumIonSearchProviderOptions { terria: Terria; url?: string; - key?: string; + key: string; flightDurationSeconds?: number; - primaryCountry?: string; - culture?: string; } interface CesiumIonGeocodeResultFeature { @@ -36,6 +39,7 @@ export default class CesiumIonSearchProvider extends SearchProvider { makeObservable(this); this.terria = options.terria; + this.name = i18next.t("viewModels.searchLocations"); this.url = defaultValue( options.url, "https://api.cesium.com/v1/geocode/search" @@ -45,39 +49,61 @@ export default class CesiumIonSearchProvider extends SearchProvider { options.flightDurationSeconds, 1.5 ); + + if (!this.key) { + console.warn( + "The " + + this.name + + " geocoder will always return no results because a CesiumIon key has not been provided. Please get a CesiumIon key from ion.cesium.com, ensure it has geocoding permission and add it to parameters.cesiumIonAccessToken in config.json." + ); + } } protected async doSearch( searchText: string, - results: SearchProviderResults + searchResults: SearchProviderResults ): Promise { if (searchText === undefined || /^\s*$/.test(searchText)) { return Promise.resolve(); } - const response = await loadJson( - `${this.url}?text=${searchText}&access_token=${this.key}` + this.terria.analytics?.logEvent( + Category.search, + SearchAction.cesium, + searchText ); + let response; + try { + response = await loadJson( + `${this.url}?text=${searchText}&access_token=${this.key}` + ); + } catch (e) { + searchResults.message = i18next.t("viewModels.searchErrorOccurred"); + return; + } if (!response.features) { + searchResults.message = i18next.t("viewModels.searchNoLocations"); return; } - results.results.push( - ...response.features.map((feature) => { - const [w, s, e, n] = feature.bbox; - const rectangle = Rectangle.fromDegrees(w, s, e, n); + if (response.features.length === 0) { + searchResults.message = i18next.t("viewModels.searchNoLocations"); + } - return new SearchResult({ - name: feature.properties.label, - clickAction: createZoomToFunction(this, rectangle), - location: { - latitude: (s + n) / 2, - longitude: (e + w) / 2 - } - }); - }) - ); + searchResults.results = response.features.map((feature) => { + const [w, s, e, n] = feature.bbox; + const rectangle = Rectangle.fromDegrees(w, s, e, n); + + return new SearchResult({ + name: feature.properties.label, + clickAction: createZoomToFunction(this, rectangle), + location: { + latitude: (s + n) / 2, + longitude: (e + w) / 2 + } + }); + }); } } diff --git a/test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts b/test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts new file mode 100644 index 00000000000..f000088d6f9 --- /dev/null +++ b/test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts @@ -0,0 +1,59 @@ +import CesiumIonSearchProvider from "../../../lib/Models/SearchProviders/CesiumIonSearchProvider"; +import Terria from "../../../lib/Models//Terria"; +import * as loadJson from "../../../lib/Core/loadJson"; + +const fixture = { + features: [ + { + properties: { + label: "West End, Australia" + }, + bbox: [ + 152.99620056152344, -27.490509033203125, 153.0145721435547, + -27.474090576171875 + ] + } + ] +}; + +describe("CesiumIonSearchProvider", () => { + const searchProvider = new CesiumIonSearchProvider({ + key: "testkey", + url: "api.test.com", + terria: { + currentViewer: { + zoomTo: () => {} + } + } as unknown as Terria + }); + it("Handles valid results", async () => { + spyOn(loadJson, "default").and.returnValue( + new Promise((resolve) => resolve(fixture)) + ); + + const result = await searchProvider.search("test"); + expect(loadJson.default).toHaveBeenCalledWith( + "api.test.com?text=test&access_token=testkey" + ); + expect(result.results.length).toBe(1); + expect(result.results[0].name).toBe("West End, Australia"); + expect(result.results[0].location?.latitude).toBe(-27.4822998046875); + }); + + it("Handles empty result", async () => { + spyOn(loadJson, "default").and.returnValue( + new Promise((resolve) => resolve([])) + ); + const result = await searchProvider.search("test"); + console.log(result); + expect(result.results.length).toBe(0); + expect(result.message).toBe("viewModels.searchNoLocations"); + }); + + it("Handles error", async () => { + spyOn(loadJson, "default").and.throwError("error"); + const result = await searchProvider.search("test"); + expect(result.results.length).toBe(0); + expect(result.message).toBe("viewModels.searchErrorOccurred"); + }); +});