diff --git a/assets/css/_autocomplete-theme.scss b/assets/css/_autocomplete-theme.scss
index 34e4d72205..c624d72584 100644
--- a/assets/css/_autocomplete-theme.scss
+++ b/assets/css/_autocomplete-theme.scss
@@ -91,7 +91,8 @@
.aa-Item {
border-bottom: $border;
border-radius: 0;
- font-weight: normal;
+ display: flex;
+ padding: calc(#{$base-spacing} / 2) $base-spacing;
&:hover {
background-color: $brand-primary-lightest;
@@ -102,25 +103,17 @@
font-weight: bold;
}
- > a,
- > button {
- color: currentColor;
- display: flex;
- font-weight: inherit;
- gap: .25rem;
- min-width: 0;
- padding: calc(#{$base-spacing} / 2) $base-spacing;
- }
-
a:hover {
text-decoration: none;
}
[class*='c-svg__icon'] {
- width: 1em;
+ height: auto;
+ top: .15em;
}
}
+ .aa-ItemLink,
.aa-ItemContent,
.aa-ItemContentBody {
display: unset;
@@ -141,6 +134,26 @@
}
// stylelint-enable declaration-no-important
}
+
+ // more specific layout
+ [data-autocomplete-source-id='geolocation'] {
+ .aa-ItemContentTitle {
+ color: $brand-primary;
+ }
+ }
+ [data-autocomplete-source-id='algolia'],
+ [data-autocomplete-source-id='locations'],
+ [data-autocomplete-source-id='popular'] {
+ .fa-map-marker {
+ color: $brand-primary;
+ }
+ .aa-ItemContent {
+ align-items: first baseline;
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ }
+ }
}
#header-desktop {
diff --git a/assets/js/algolia-result.js b/assets/js/algolia-result.js
index 373e30960b..0a13b756ed 100644
--- a/assets/js/algolia-result.js
+++ b/assets/js/algolia-result.js
@@ -223,7 +223,7 @@ function _iconFromRoute(route) {
export function getPopularIcon(icon) {
switch (icon) {
case "airplane":
- return TEMPLATES.fontAwesomeIcon.render({ icon: "fa-plane" });
+ return TEMPLATES.fontAwesomeIcon.render({ icon: "fa-plane-departure" });
default:
return Icons.getFeatureIcon(icon);
}
@@ -309,7 +309,9 @@ export function getUrl(hit, index) {
return _contentUrl(hit);
case "locations":
- return `transit-near-me?address=${encodeURIComponent(hit.address)}`;
+ return `transit-near-me?address=${encodeURIComponent(
+ hit.street_address
+ )}`;
case "usemylocation":
return "#";
@@ -343,7 +345,7 @@ export function getTitle(hit, type) {
switch (type) {
case "locations":
// eslint-disable-next-line no-case-declarations
- const { address: text, highlighted_spans: spans } = hit;
+ const { street_address: text, highlighted_spans: spans } = hit;
return highlightText(text, spans);
case "stops":
@@ -451,14 +453,21 @@ function _getCommuterRailZone(hit) {
if (hit.zone) {
return [`Zone ${hit.zone}`];
}
+ if (hit.icon === "station") {
+ // the north/south station popular result
+ return [`Zone 1A`];
+ }
return [];
}
function _stopIcons(hit, type) {
+ const isBusStop = type === "stops" && !hit.stop["station?"];
+ const featuresToFilter = isBusStop
+ ? ["access", "parking_lot", "bus"]
+ : ["access", "parking_lot"];
const filteredFeatures = hit.features.filter(
- feature => feature !== "access" && feature !== "parking_lot"
+ feature => !featuresToFilter.includes(feature)
);
-
const alertFeature = _getAlertIcon(hit, type);
const allFeatures = alertFeature.concat(filteredFeatures);
const allFeaturesSorted = _sortFeatures(allFeatures);
@@ -466,6 +475,21 @@ function _stopIcons(hit, type) {
const zoneIcon = _getCommuterRailZone(hit);
+ if (isBusStop) {
+ return hit.routes
+ .filter(route => route.type === 3)
+ .map(route => {
+ return route.display_name
+ .replace("Bus: ", "")
+ .split(", ")
+ .map(
+ num =>
+ `${num}`
+ )
+ .join("");
+ });
+ }
+
return allIcons.concat(zoneIcon);
}
diff --git a/assets/js/algolia-results.js b/assets/js/algolia-results.js
index deba40d828..9085433b94 100644
--- a/assets/js/algolia-results.js
+++ b/assets/js/algolia-results.js
@@ -73,7 +73,10 @@ export class AlgoliaResults {
results.locations.hits.forEach((hit, idx) => {
const elem = document.getElementById(`hit-location-${idx}`);
if (elem) {
- elem.addEventListener("click", this._locationSearch(hit.address));
+ elem.addEventListener(
+ "click",
+ this._locationSearch(hit.street_address)
+ );
}
});
const useLocation = document.getElementById(
diff --git a/assets/js/test/algolia-result_test.js b/assets/js/test/algolia-result_test.js
index b2d9dfa9b4..0783c1c46a 100644
--- a/assets/js/test/algolia-result_test.js
+++ b/assets/js/test/algolia-result_test.js
@@ -166,7 +166,7 @@ describe("AlgoliaResult", () => {
};
const locationHits = {
bostonCommon: {
- address: "Boston Common, Tremont Street, Boston, MA, USA",
+ street_address: "Boston Common, Tremont Street, Boston, MA, USA",
highlighted_spans: [
{ length: 2, offset: 3 },
{ length: 5, offset: 9 }
diff --git a/assets/js/test/algolia-results_test.js b/assets/js/test/algolia-results_test.js
index a00c0ae66c..d81c2fad1a 100644
--- a/assets/js/test/algolia-results_test.js
+++ b/assets/js/test/algolia-results_test.js
@@ -137,7 +137,8 @@ describe("AlgoliaResults", () => {
const hit = {
stop: {
id: "stop-id",
- name: "pre_stop-name"
+ name: "pre_stop-name",
+ "station?": true
},
_highlightResult: {
stop: {
@@ -164,7 +165,6 @@ describe("AlgoliaResults", () => {
expect(result).to.be.a("string");
expect(result).to.have.string("/stops/" + hit.stop.id);
expect(result).to.have.string(hit._highlightResult.stop.name.value);
- expect(result).to.have.string("stop-icon");
expect(result).to.have.string("green-line-b-icon");
expect(result).to.have.string("bus-icon");
expect(result).to.have.string("Zone 8");
@@ -290,7 +290,8 @@ describe("AlgoliaResults", () => {
hitTitle: "title1",
stop: {
id: "id1",
- name: "name1"
+ name: "name1",
+ "station?": true
},
_highlightResult: {
stop: {
@@ -308,7 +309,8 @@ describe("AlgoliaResults", () => {
hitTitle: "title2",
stop: {
id: "id2",
- name: "name2"
+ name: "name2",
+ "station?": true
},
_highlightResult: {
stop: {
diff --git a/assets/js/trip-planner-location-controls.js b/assets/js/trip-planner-location-controls.js
index 1e94d641b3..4818402731 100644
--- a/assets/js/trip-planner-location-controls.js
+++ b/assets/js/trip-planner-location-controls.js
@@ -320,12 +320,13 @@ export class TripPlannerLocControls {
);
break;
case "locations":
- MapsHelpers.lookupPlace(hit.address).then(res => {
+ MapsHelpers.lookupPlace(hit.street_address).then(res => {
+ // this doesnt work
const { latitude, longitude } = res;
this.setStopValue(ac, hit);
this.setAutocompleteValue(
ac,
- hit.address,
+ hit.street_address,
lat,
lng,
latitude,
diff --git a/assets/ts/ui/autocomplete/__autocomplete.d.ts b/assets/ts/ui/autocomplete/__autocomplete.d.ts
index 2d9611a010..158db83cff 100644
--- a/assets/ts/ui/autocomplete/__autocomplete.d.ts
+++ b/assets/ts/ui/autocomplete/__autocomplete.d.ts
@@ -2,7 +2,6 @@ import { Hit } from "@algolia/client-search";
import { BaseItem } from "@algolia/autocomplete-core";
import { Route, RouteType, Stop } from "../../__v3api";
import { HighlightedSpan } from "../../helpers/text";
-import { TripPlannerLocControls } from "../../../js/trip-planner-location-controls";
type AlgoliaItem = Hit<{ index: string }> & BaseItem;
@@ -35,12 +34,24 @@ type SearchResultItem = {
export type LocationItem = {
longitude: number;
latitude: number;
- address: string;
+ formatted: string;
highlighted_spans: HighlightedSpan[];
+ street_address: string;
+ municipality: string;
+ state: string;
url: string;
};
-export type PopularItem = typeof TripPlannerLocControls.POPULAR[number];
+export type PopularItem = {
+ icon: "airplane" | "station";
+ name: string;
+ features: string[];
+ latitude: number;
+ longitude: number;
+ url: string;
+ state: string;
+ municipality: string;
+};
export type AutocompleteItem = RouteItem | StopItem | ContentItem;
export type Item = AutocompleteItem | LocationItem | PopularItem;
diff --git a/assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap b/assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap
index 3113c63dff..bcee7c7c75 100644
--- a/assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap
+++ b/assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap
@@ -7,22 +7,28 @@ exports[`Algolia template handles route item 1`] = `
href="/schedules/111"
>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -37,15 +43,24 @@ exports[`Location template renders 1`] = `
-
-
+
+
+ 123 Main St
+
+
+
- South Station, Boston, MA
-
+ Town, MA
+
@@ -60,25 +75,49 @@ exports[`Popular template renders 1`] = `
-
-
-
-
-
-
- South Station
-
-
+
+
+ South Station
+
+
+
+
+
+
+
+
+ Zone 1A
+
+
+
+
+
+ Town, MA
+
diff --git a/assets/ts/ui/autocomplete/__tests__/__snapshots__/templates-test.tsx.snap b/assets/ts/ui/autocomplete/__tests__/__snapshots__/templates-test.tsx.snap
index 3113c63dff..bcee7c7c75 100644
--- a/assets/ts/ui/autocomplete/__tests__/__snapshots__/templates-test.tsx.snap
+++ b/assets/ts/ui/autocomplete/__tests__/__snapshots__/templates-test.tsx.snap
@@ -7,22 +7,28 @@ exports[`Algolia template handles route item 1`] = `
href="/schedules/111"
>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -37,15 +43,24 @@ exports[`Location template renders 1`] = `
-
-
+
+
+ 123 Main St
+
+
+
- South Station, Boston, MA
-
+ Town, MA
+
@@ -60,25 +75,49 @@ exports[`Popular template renders 1`] = `
-
-
-
-
-
-
- South Station
-
-
+
+
+ South Station
+
+
+
+
+
+
+
+
+ Zone 1A
+
+
+
+
+
+ Town, MA
+
diff --git a/assets/ts/ui/autocomplete/__tests__/helpers-test.ts b/assets/ts/ui/autocomplete/__tests__/helpers-test.ts
index 823200a959..0769613360 100644
--- a/assets/ts/ui/autocomplete/__tests__/helpers-test.ts
+++ b/assets/ts/ui/autocomplete/__tests__/helpers-test.ts
@@ -47,6 +47,9 @@ test("itemWithUrl gets a requested URL", () => {
features: [],
latitude: 42.365396,
longitude: -71.017547,
+ municipality: "Town",
+ state: "MA",
+ url: "",
urls: {
"transit-near-me": "/transit-near-me/logan",
"retail-sales-locations": "/retail-locations-somewhere",
diff --git a/assets/ts/ui/autocomplete/__tests__/sources-test.ts b/assets/ts/ui/autocomplete/__tests__/sources-test.ts
index f72b6ac6e2..3fb49cc30d 100644
--- a/assets/ts/ui/autocomplete/__tests__/sources-test.ts
+++ b/assets/ts/ui/autocomplete/__tests__/sources-test.ts
@@ -84,24 +84,31 @@ describe("popularLocationSource", () => {
describe("algoliaSource", () => {
test("defines a template", () => {
- expect(algoliaSource("query").templates.item).toBeTruthy();
+ expect(algoliaSource("query", {}).templates.item).toBeTruthy();
});
test("defines a getItems function", () => {
const query = "new project";
- const indexes = ["drupal_test"];
+ const indexes = { drupal_test: {} };
expect(
algoliaSource(query, indexes).getItems({ query } as OnInputParams<
AutocompleteItem
>)
).toBeTruthy();
expect(global.fetch).toHaveBeenCalledWith("/search/query", {
- body: JSON.stringify({ algoliaQuery: query, algoliaIndexes: indexes }),
+ body: JSON.stringify({
+ algoliaQuery: query,
+ algoliaIndexesWithParams: indexes
+ }),
headers: { "Content-Type": "application/json" },
method: "POST"
});
});
test("has optional getItemUrl", () => {
- expect(algoliaSource("question", [], false)).not.toContainKey("getItemUrl");
- expect(algoliaSource("question")).toContainKey("getItemUrl");
+ expect(
+ algoliaSource("question", { stops: { hitsPerPage: 7 } }, false)
+ ).not.toContainKey("getItemUrl");
+ expect(
+ algoliaSource("question", { stops: { hitsPerPage: 7 } })
+ ).toContainKey("getItemUrl");
});
});
diff --git a/assets/ts/ui/autocomplete/__tests__/templates-test.tsx b/assets/ts/ui/autocomplete/__tests__/templates-test.tsx
index e389866eac..4dc6e01317 100644
--- a/assets/ts/ui/autocomplete/__tests__/templates-test.tsx
+++ b/assets/ts/ui/autocomplete/__tests__/templates-test.tsx
@@ -203,9 +203,11 @@ test("Popular template renders", () => {
longitude: -71.0,
icon: "station",
features: ["red_line", "bus", "commuter_rail", "access"],
+ municipality: "Town",
+ state: "MA",
url:
"/transit-near-me?latitude=42.352271&longitude=-71.055242&name=South+Station"
- };
+ } as PopularItem;
const { asFragment } = renderMockTemplate(
templateWithLink(PopularItemTemplate)(
mockTemplateParam(popularItem, "South Station")
@@ -221,6 +223,10 @@ test("Location template renders", () => {
latitude: 42.4,
longitude: -71.0,
highlighted_spans: [],
+ formatted: "123 Main St, Town, MA, USA",
+ street_address: "123 Main St",
+ municipality: "Town",
+ state: "MA",
url:
"/transit-near-me?latitude=42.352271&longitude=-71.055242&address=South+Station,+Boston,+MA"
};
diff --git a/assets/ts/ui/autocomplete/config.ts b/assets/ts/ui/autocomplete/config.ts
index e5d70c233b..aec61227c8 100644
--- a/assets/ts/ui/autocomplete/config.ts
+++ b/assets/ts/ui/autocomplete/config.ts
@@ -75,7 +75,28 @@ const BASIC: Partial> = {
getSources({ query, setIsOpen }): AutocompleteSource[] {
if (!query) return [geolocationSource(setIsOpen, "transit-near-me")];
return debounced([
- algoliaSource(query),
+ algoliaSource(query, {
+ routes: {
+ hitsPerPage: 5
+ },
+ stops: {
+ hitsPerPage: 2
+ },
+ drupal: {
+ hitsPerPage: 2,
+ facetFilters: [
+ [
+ "_content_type:page",
+ "_content_type:search_result",
+ "_content_type:diversion",
+ "_content_type:landing_page",
+ "_content_type:person",
+ "_content_type:project",
+ "_content_type:project_update"
+ ]
+ ]
+ }
+ }),
locationSource(query, 2, "transit-near-me")
]);
}
@@ -156,7 +177,7 @@ const TRIP_PLANNER = ({
const name: string =
(item.route as Route)?.name ||
(item.stop as Stop)?.name ||
- ((item as unknown) as LocationItem).address ||
+ ((item as unknown) as LocationItem).formatted ||
(item as ContentItem).content_title ||
((item as unknown) as PopularItem).name ||
"";
@@ -193,7 +214,10 @@ const TRIP_PLANNER = ({
}
]);
return debounced([
- { ...algoliaSource(query, ["stops"], false), onSelect },
+ {
+ ...algoliaSource(query, { stops: { hitsPerPage: 5 } }, false),
+ onSelect
+ },
{ ...locationSource(query, 5), onSelect }
]);
}
diff --git a/assets/ts/ui/autocomplete/sources.ts b/assets/ts/ui/autocomplete/sources.ts
index c1b7c5ce14..43e6dbd4c9 100644
--- a/assets/ts/ui/autocomplete/sources.ts
+++ b/assets/ts/ui/autocomplete/sources.ts
@@ -89,7 +89,7 @@ export const popularLocationSource = (
*/
export const algoliaSource = (
query: string,
- indexes: string[] = ["routes", "stops", "drupal"],
+ indexesWithParams: Record>,
withLink: boolean = true
): AutocompleteSource => ({
sourceId: "algolia",
@@ -104,7 +104,7 @@ export const algoliaSource = (
},
body: JSON.stringify({
algoliaQuery: query,
- algoliaIndexes: indexes
+ algoliaIndexesWithParams: indexesWithParams
})
})
.then(res => res.json())
diff --git a/assets/ts/ui/autocomplete/templates/algolia.tsx b/assets/ts/ui/autocomplete/templates/algolia.tsx
index a9e48ab7b3..168a1d796f 100644
--- a/assets/ts/ui/autocomplete/templates/algolia.tsx
+++ b/assets/ts/ui/autocomplete/templates/algolia.tsx
@@ -16,6 +16,10 @@ import {
getIcon
} from "../../../../js/algolia-result";
+// parse this from a stop's address until we can get it as a stop field
+const stateAbbr = (address: string): string =>
+ (address.split(",").pop() || "").substring(1, 3);
+
export function LinkForItem(props: {
item: AutocompleteItem;
query: string;
@@ -74,41 +78,60 @@ const AlgoliaItemTemplate: SourceTemplates["item"] = ({
? contentIcon(item)
: getIcon(item, indexName);
return (
-
-
- {!isContentItem(item) &&
- featureIcons.map((feature: string) => (
+
+
+
- ))}
-
-
-
- {components.Highlight({
- hit: item,
- attribute
- })}
-
-
- {isRouteItem(item) && item.route.type === 3 && (
-
+
+
{components.Highlight({
hit: item,
- attribute: ["route", "long_name"]
+ attribute
})}
- )}
-
-
+ {isStopItem(item) && !item.stop["station?"] && (
+
+ #{item.stop.id}
+
+ )}
+
+ {isRouteItem(item) && item.route.type === 3 && (
+
+ {components.Highlight({
+ hit: item,
+ attribute: ["route", "long_name"]
+ })}
+
+ )}
+
+
+ {!isContentItem(item) && featureIcons.length > 0 && (
+
+ {featureIcons.map((feature: string) => (
+
+ ))}
+
+ )}
+
+
+ {isStopItem(item) && (
+
+ {`${item.stop.municipality}, ${
+ item.stop.address ? stateAbbr(item.stop.address) : "MA"
+ }`}
+
+ )}
);
};
diff --git a/assets/ts/ui/autocomplete/templates/geolocation.tsx b/assets/ts/ui/autocomplete/templates/geolocation.tsx
index 2a559f797d..be01a5095a 100644
--- a/assets/ts/ui/autocomplete/templates/geolocation.tsx
+++ b/assets/ts/ui/autocomplete/templates/geolocation.tsx
@@ -68,8 +68,14 @@ export function GeolocationComponent(props: {
if (loading) {
return (
-
- {loading}
+
);
}
@@ -89,8 +95,8 @@ export function GeolocationComponent(props: {
}}
>
-
diff --git a/assets/ts/ui/autocomplete/templates/location.tsx b/assets/ts/ui/autocomplete/templates/location.tsx
index 3172eb6c64..056902df1a 100644
--- a/assets/ts/ui/autocomplete/templates/location.tsx
+++ b/assets/ts/ui/autocomplete/templates/location.tsx
@@ -6,17 +6,23 @@ import { highlightText } from "../../../helpers/text";
const LocationItemTemplate: SourceTemplates
["item"] = ({
item
}) => {
- const { address, highlighted_spans } = item;
+ const { street_address, highlighted_spans } = item;
return (
-
-
+
+
+
+
+
{`${item.municipality}, ${item.state}`}
);
};
diff --git a/assets/ts/ui/autocomplete/templates/popular.tsx b/assets/ts/ui/autocomplete/templates/popular.tsx
index 33c0da45fd..62434240a8 100644
--- a/assets/ts/ui/autocomplete/templates/popular.tsx
+++ b/assets/ts/ui/autocomplete/templates/popular.tsx
@@ -11,23 +11,31 @@ const PopularItemTemplate: SourceTemplates["item"] = ({
const featureIcons = getFeatureIcons(item, "popular");
return (
-
- {featureIcons.map((feature: string) => (
-
- ))}
-
- {item.name}
-
+
+
+
+ {featureIcons.map((feature: string) => (
+
+ ))}
+
+
+
{`${item.municipality}, ${item.state}`}
);
};
diff --git a/config/runtime.exs b/config/runtime.exs
index ae1f02a61a..80d7449bd3 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -120,8 +120,6 @@ config :dotcom, :telemetry_metrics_splunk,
token: System.get_env("TELEMETRY_METRICS_SPLUNK_TOKEN"),
url: "https://http-inputs-mbta.splunkcloud.com/services/collector"
-config :dotcom, aws_index_prefix: System.get_env("AWS_PLACE_INDEX_PREFIX") || "dotcom-dev"
-
if config_env() != :test do
config :dotcom, :algolia_config,
app_id: System.get_env("ALGOLIA_APP_ID"),
@@ -182,7 +180,7 @@ if config_env() == :prod do
end
config :dotcom, LocationService,
- aws_index_prefix: System.get_env("AWS_PLACE_INDEX_PREFIX", "dotcom-prod")
+ aws_index: System.get_env("AWS_PLACE_INDEX_NAME", "dotcom-dev-esri")
config :dotcom, DotcomWeb.ViewHelpers,
google_tag_manager_id: System.get_env("GOOGLE_TAG_MANAGER_ID"),
diff --git a/config/test.exs b/config/test.exs
index aca4c0420b..fb422136af 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -1,5 +1,10 @@
import Config
+# Change aws_credentials so it does not affect testing
+config :aws_credentials,
+ credential_providers: [],
+ fail_if_unavailable: false
+
config :dotcom, :aws_client, AwsClient.Mock
config :dotcom, :cache, Dotcom.Cache.TestCache
diff --git a/cypress/e2e/smoke.cy.js b/cypress/e2e/smoke.cy.js
index a61253f308..fda8cd9125 100644
--- a/cypress/e2e/smoke.cy.js
+++ b/cypress/e2e/smoke.cy.js
@@ -105,9 +105,7 @@ describe("passes smoke test", () => {
.click();
cy.contains("a.m-detailed-stop", "Hingham").click();
cy.url().should("contain", "Boat-Hingham");
- cy.contains("main", "Station Information");
- cy.contains("View daily rates and facility information").click();
- cy.contains("Parking Rates");
+ cy.contains("main", "Stop Information");
});
it("schedules & maps page (all links)", () => {
diff --git a/lib/algolia/query.ex b/lib/algolia/query.ex
index df6bd0dc0e..159d02bd59 100644
--- a/lib/algolia/query.ex
+++ b/lib/algolia/query.ex
@@ -19,16 +19,19 @@ defmodule Algolia.Query do
|> Poison.encode!()
end
- def build(%{"algoliaQuery" => query, "algoliaIndexes" => indexes}) do
+ def build(%{"algoliaQuery" => query, "algoliaIndexesWithParams" => indexes_with_params}) do
requests =
- Enum.map(indexes, fn idx ->
- idx
- |> Request.new(query)
+ indexes_with_params
+ |> Map.to_list()
+ |> Enum.reverse()
+ |> Enum.map(fn {index_name, index_params} ->
+ index_name
+ |> Request.new(query, index_params)
|> Request.encode()
end)
%{"requests" => requests}
- |> Poison.encode!()
+ |> Jason.encode!()
end
@spec build_query(map) :: map
diff --git a/lib/algolia/request.ex b/lib/algolia/request.ex
index caa99a093c..b1db672d38 100644
--- a/lib/algolia/request.ex
+++ b/lib/algolia/request.ex
@@ -26,7 +26,7 @@ defmodule Algolia.Query.Request do
attributesToHighlight: String.t() | [String.t()]
}
@spec new(String.t(), String.t()) :: t()
- def new(index_name, query) when index_name in @supported_index_keys do
+ def new(index_name, query, params \\ %{}) when index_name in @supported_index_keys do
algolia_index = Keyword.fetch!(@supported_indexes, String.to_atom(index_name))
%__MODULE__{
@@ -34,48 +34,13 @@ defmodule Algolia.Query.Request do
query: query,
attributesToHighlight: highlight(index_name)
}
- |> with_hit_size(index_name)
- |> with_facet_filters(index_name)
+ |> Map.update!(:params, &Map.merge(&1, params))
end
defp highlight("routes"), do: ["route.name", "route.long_name"]
defp highlight("stops"), do: "stop.name"
defp highlight("drupal"), do: "content_title"
- defp with_hit_size(request, indexName) do
- %__MODULE__{
- request
- | params: %{request.params | "hitsPerPage" => hit_size(indexName)}
- }
- end
-
- defp hit_size("routes"), do: 5
- defp hit_size("stops"), do: 2
- defp hit_size("drupal"), do: 2
-
- @spec with_facet_filters(t(), String.t()) :: t()
- defp with_facet_filters(request, "drupal") do
- %__MODULE__{
- request
- | params: %{
- request.params
- | "facetFilters" => [
- [
- "_content_type:page",
- "_content_type:search_result",
- "_content_type:diversion",
- "_content_type:landing_page",
- "_content_type:person",
- "_content_type:project",
- "_content_type:project_update"
- ]
- ]
- }
- }
- end
-
- defp with_facet_filters(request, _), do: request
-
def encode(%__MODULE__{} = request) do
request
|> Map.from_struct()
diff --git a/lib/aws_client/behaviour.ex b/lib/aws_client/behaviour.ex
index 098d94a1d2..69d66bec8c 100644
--- a/lib/aws_client/behaviour.ex
+++ b/lib/aws_client/behaviour.ex
@@ -27,6 +27,11 @@ defmodule AwsClient.Behaviour do
| {:error, {:unexpected_response, any()}}
| {:error, AWS.Location.search_place_index_for_text_errors()}
+ @callback get_place(String.t(), String.t()) ::
+ {:ok, AWS.Location.get_place_response(), any()}
+ | {:error, {:unexpected_response, any()}}
+ | {:error, AWS.Location.get_place_errors()}
+
@type bucket_name :: String.t()
@type bucket_prefix :: String.t()
@type bucket_object_key :: String.t()
@@ -64,6 +69,11 @@ defmodule AwsClient.Behaviour do
AWS.Location.search_place_index_for_text(client(), index_name, input)
end
+ @impl __MODULE__
+ def get_place(index_name, place_id) do
+ AWS.Location.get_place(client(), index_name, place_id)
+ end
+
@impl __MODULE__
def list_objects(bucket, prefix),
do: AWS.S3.list_objects(client(), bucket, nil, nil, nil, nil, prefix, nil, nil, nil)
diff --git a/lib/dotcom_web/controllers/places_controller.ex b/lib/dotcom_web/controllers/places_controller.ex
index b7cfa18055..674d1f356b 100644
--- a/lib/dotcom_web/controllers/places_controller.ex
+++ b/lib/dotcom_web/controllers/places_controller.ex
@@ -19,9 +19,6 @@ defmodule DotcomWeb.PlacesController do
{:error, :internal_error} ->
ControllerHelpers.return_internal_error(conn)
- {:error, :zero_results} ->
- ControllerHelpers.return_zero_results_error(conn)
-
_ ->
ControllerHelpers.return_invalid_arguments_error(conn)
end
@@ -35,9 +32,6 @@ defmodule DotcomWeb.PlacesController do
{:error, :internal_error} ->
ControllerHelpers.return_internal_error(conn)
-
- {:error, :zero_results} ->
- ControllerHelpers.return_zero_results_error(conn)
end
end
@@ -52,9 +46,6 @@ defmodule DotcomWeb.PlacesController do
{:error, :internal_error} ->
ControllerHelpers.return_internal_error(conn)
-
- {:error, :zero_results} ->
- ControllerHelpers.return_zero_results_error(conn)
end
end
@@ -116,23 +107,20 @@ defmodule DotcomWeb.PlacesController do
{:ok, suggestions} ->
json(conn, %{result: with_coordinates(suggestions)})
- {:error, error} ->
- case error do
- :zero_results ->
- json(conn, %{result: []})
-
- _ ->
- ControllerHelpers.return_internal_error(conn)
- end
+ {:error, _} ->
+ ControllerHelpers.return_internal_error(conn)
end
end
- @spec with_coordinates([Suggestion.t()]) :: [
+ @spec with_coordinates([Address.t()]) :: [
%{
required(:latitude) => number,
required(:longitude) => number,
- required(:address) => String.t(),
- required(:highlighted_spans) => [LocationService.Utils.HighlightedSpan.t()] | nil,
+ required(:formatted) => String.t(),
+ required(:street_address) => String.t(),
+ required(:municipality) => String.t(),
+ required(:state) => String.t(),
+ required(:highlighted_spans) => [map()] | nil,
required(:url) => nil,
required(:urls) => %{
required(:"transit-near-me") => String.t(),
@@ -141,14 +129,12 @@ defmodule DotcomWeb.PlacesController do
}
}
]
- defp with_coordinates(suggestions) do
- suggestions
- |> Enum.map(&{&1, @location_service.geocode(&1.address)})
- |> Enum.filter(&match?({_suggestion, {:ok, [%Address{} | _]}}, &1))
- |> Enum.map(fn {suggestion, {:ok, [address | _]}} ->
+ defp with_coordinates(addresses) do
+ addresses
+ |> Enum.map(fn address ->
address
|> Map.take([:latitude, :longitude])
- |> Map.merge(Map.from_struct(suggestion))
+ |> Map.merge(Map.from_struct(address))
|> add_urls()
end)
end
@@ -156,9 +142,17 @@ defmodule DotcomWeb.PlacesController do
defp add_urls(map) do
params =
case map do
- %{address: _} -> Map.take(map, [:address, :latitude, :longitude])
- %{name: _} -> Map.take(map, [:name, :latitude, :longitude])
- %{latitude: _} -> Map.take(map, [:latitude, :longitude])
+ %{street_address: street_address} ->
+ Map.take(map, [:latitude, :longitude]) |> Map.put_new(:address, street_address)
+
+ %{address: _} ->
+ Map.take(map, [:address, :latitude, :longitude])
+
+ %{name: _} ->
+ Map.take(map, [:name, :latitude, :longitude])
+
+ %{latitude: _} ->
+ Map.take(map, [:latitude, :longitude])
end
map
@@ -190,21 +184,27 @@ defmodule DotcomWeb.PlacesController do
[
%{
icon: "airplane",
+ municipality: "East Boston",
+ state: "MA",
name: "Boston Logan Airport",
- features: [],
+ features: ["blue_line", "silver_line", "ferry", "bus"],
latitude: 42.365396,
longitude: -71.017547
},
%{
icon: "station",
+ municipality: "Boston",
+ state: "MA",
name: "South Station",
- features: ["red_line", "bus", "commuter_rail", "access"],
+ features: ["red_line", "silver_line", "bus", "commuter_rail", "access"],
latitude: 42.352271,
longitude: -71.055242,
stop_id: "place-sstat"
},
%{
icon: "station",
+ municipality: "Boston",
+ state: "MA",
name: "North Station",
features: [
"orange_line",
diff --git a/lib/location_service/address.ex b/lib/location_service/address.ex
index 9f3ce0dce6..4fae4cebbf 100644
--- a/lib/location_service/address.ex
+++ b/lib/location_service/address.ex
@@ -1,19 +1,146 @@
defmodule LocationService.Address do
@moduledoc """
- An address provided by a Geocode lookup.
+ An address provided by a Geocode or Place lookup.
"""
- @derive Jason.Encoder
+ @type highlighted_span :: %{
+ offset: integer(),
+ length: non_neg_integer()
+ }
@type t :: %__MODULE__{
formatted: String.t(),
+ highlighted_spans: [highlighted_span()],
latitude: float,
- longitude: float
+ longitude: float,
+ street_address: String.t(),
+ municipality: String.t(),
+ state: String.t()
}
- defstruct formatted: "",
+ @derive Jason.Encoder
+ defstruct formatted: nil,
+ highlighted_spans: [],
latitude: 0.0,
- longitude: 0.0
+ longitude: 0.0,
+ street_address: nil,
+ municipality: nil,
+ state: nil
+
+ @spec new(map()) :: %__MODULE__{}
+ @spec new(map(), String.t()) :: %__MODULE__{}
+ def new(
+ %{"Label" => label, "Geometry" => %{"Point" => [lon, lat]}},
+ queried_text \\ nil
+ ) do
+ address =
+ label
+ |> replace_common_street_suffix()
+ |> AddressUS.Parser.parse_address()
+
+ street_address =
+ case address.street do
+ %AddressUS.Street{
+ name: street_name,
+ primary_number: street_number,
+ suffix: street_suffix
+ } ->
+ "#{street_number} #{street_name} #{street_suffix}" |> String.trim()
+
+ _ ->
+ nil
+ end
+
+ %LocationService.Address{
+ formatted: label,
+ highlighted_spans:
+ if(queried_text, do: get_highlighted_spans(queried_text, label), else: []),
+ latitude: lat,
+ longitude: lon,
+ street_address: street_address |> with_place_name(label),
+ municipality: address.city,
+ state: address.state
+ }
+ end
+
+ # Get the text before the street address
+ defp with_place_name(street_address, label) do
+ if String.contains?(label, street_address) do
+ label
+ |> String.split(street_address)
+ |> List.first()
+ |> String.trim_trailing(",")
+ |> then(&(&1 <> street_address))
+ else
+ # Likely a simpler place, e.g. "Prudential Tunnel, Boston, MA, 02199, USA" - just take the first part
+ label
+ |> String.split(",")
+ |> List.first()
+ end
+ end
defimpl Util.Position do
def latitude(address), do: address.latitude
def longitude(address), do: address.longitude
end
+
+ @doc """
+ Gets indices of spans of text that should be highlighted in the
+ autocomplete dropdown.
+
+ Essentially, `search` is split on whitespace, and then we search `text`
+ for words that start with any of the `search` terms. The spans are
+ non-overlapping, and sorted by `offset`. There are examples in the tests
+ that should further clarify the behavior.
+ """
+ @spec get_highlighted_spans(String.t(), String.t()) :: [highlighted_span()]
+ def get_highlighted_spans(search, text) do
+ parts = String.split(search)
+
+ Enum.flat_map(parts, fn p ->
+ # (^|\\W) -- Match start of string or non-word character
+ # (? -- Begin a capture group named `t`
+ # p -- Match the current part
+ # \\w* -- Match any number of word characters
+ # ) -- Close `t`
+ src = "(^|\\W)(?" <> Regex.escape(p) <> "\\w*)"
+ {:ok, re} = Regex.compile(src, "i")
+
+ Regex.scan(re, text, return: :index, capture: :all_names)
+ |> Enum.map(fn
+ [{offset, length}] -> %{offset: offset, length: length}
+ nil -> nil
+ end)
+ |> Enum.filter(& &1)
+ end)
+ |> Enum.uniq()
+ |> Enum.sort(&(&1.offset <= &2.offset))
+ end
+
+ def replace_common_street_suffix(text) when is_binary(text) do
+ text
+ |> String.replace(" Av,", " Avenue,")
+ |> String.replace(" Ave,", " Avenue,")
+ |> String.replace(" Bv,", " Boulevard,")
+ |> String.replace(" Blvd,", " Boulevard,")
+ |> String.replace(" Ci,", " Circle,")
+ |> String.replace(" Cir,", " Circle,")
+ |> String.replace(" Cl,", " Circle,")
+ |> String.replace(" Ct,", " Court,")
+ |> String.replace(" Ctr,", " Center,")
+ |> String.replace(" Dr,", " Drive,")
+ |> String.replace(" La,", " Lane,")
+ |> String.replace(" Ln,", " Lane,")
+ |> String.replace(" Pw,", " Parkway,")
+ |> String.replace(" Pkwy,", " Parkway,")
+ |> String.replace(" Pi,", " Pike,")
+ |> String.replace(" Pk,", " Pike,")
+ |> String.replace(" Pl,", " Place,")
+ |> String.replace(" Pt,", " Point,")
+ |> String.replace(" Rd,", " Road,")
+ |> String.replace(" Sq,", " Square,")
+ |> String.replace(" St,", " Street,")
+ |> String.replace(" Str,", " Street,")
+ |> String.replace(" Te,", " Terrace,")
+ |> String.replace(" Wy,", " Way,")
+ end
+
+ def replace_common_street_suffix(other), do: other
end
diff --git a/lib/location_service/location_service.ex b/lib/location_service/location_service.ex
index f8e0de6f06..f2672cd4fa 100644
--- a/lib/location_service/location_service.ex
+++ b/lib/location_service/location_service.ex
@@ -3,8 +3,6 @@ defmodule LocationService do
Interacts with Amazon's Location Service, specifically its Places service, to perform geocoding, reverse geocoding and place lookups.
"""
- require Logger
-
use Nebulex.Caching.Decorators
@aws_client Application.compile_env!(:dotcom, :aws_client)
@@ -27,7 +25,7 @@ defmodule LocationService do
@behaviour LocationService.Behaviour
@impl LocationService.Behaviour
- @decorate cacheable(cache: @cache, on_error: :nothing, opts: [ttl: @ttl])
+ # @decorate cacheable(cache: @cache, on_error: :nothing, opts: [ttl: @ttl])
def autocomplete(text, limit, options \\ @bias_options) do
options
|> Map.merge(%{"Text" => text, "MaxResults" => limit})
@@ -35,15 +33,7 @@ defmodule LocationService do
|> handle_response()
end
- defp filter_results(results) do
- results
- |> Enum.filter(fn
- %LocationService.Address{formatted: address} -> Regex.match?(@filter, address)
- %LocationService.Suggestion{address: address} -> Regex.match?(@filter, address)
- end)
- end
-
- @decorate cacheable(cache: @cache, on_error: :nothing, opts: [ttl: @ttl])
+ # @decorate cacheable(cache: @cache, on_error: :nothing, opts: [ttl: @ttl])
@impl LocationService.Behaviour
def geocode(address, options \\ @bounding_options) do
options
@@ -52,7 +42,7 @@ defmodule LocationService do
|> handle_response()
end
- @decorate cacheable(cache: @cache, on_error: :nothing, opts: [ttl: @ttl])
+ # @decorate cacheable(cache: @cache, on_error: :nothing, opts: [ttl: @ttl])
@impl LocationService.Behaviour
def reverse_geocode(latitude, longitude, options \\ @bounding_options) do
options
@@ -61,41 +51,74 @@ defmodule LocationService do
|> handle_response()
end
+ defp get_place(place_id) do
+ @aws_client.get_place(index(), place_id)
+ |> handle_response()
+ end
+
defp handle_response({:error, error}) do
error |> inspect() |> Sentry.capture_message()
{:error, :internal_error}
end
+ defp handle_response({:ok, %{"Place" => place}, _raw_response}) do
+ place
+ end
+
defp handle_response({:ok, %{"Results" => results, "Summary" => summary}, _raw_reponse}) do
+ input = Map.get(summary, "Text")
+
results =
results
- |> Enum.map(fn
- # AWS suggestions
- %{"Text" => address} ->
- %LocationService.Suggestion{
- address: address,
- highlighted_spans:
- LocationService.Utils.get_highlighted_spans(%{
- search: summary["Text"],
- text: address
- })
- }
-
- # AWS format
- %{"Place" => %{"Label" => label, "Geometry" => %{"Point" => [lon, lat]}}} ->
- %LocationService.Address{
- formatted: label,
- latitude: lat,
- longitude: lon
- }
+ |> Stream.map(&parse/1)
+ |> Stream.reject(fn suggestion ->
+ place_without_placeid(suggestion) || not in_this_region(suggestion)
end)
- |> filter_results()
+ |> Stream.uniq_by(&dedup_place_text/1)
+ |> Stream.map(&get_place_from_placeid/1)
+ |> Stream.reject(fn place ->
+ match?({:error, _}, place) || metro_station?(place)
+ end)
+ |> Enum.map(&LocationService.Address.new(&1, input))
{:ok, results}
end
+ defp parse(%{"Place" => place}), do: place
+ defp parse(other), do: other
+
+ # sometimes the suggestions return a place with no place ID,
+ # just a text result and nothing else. don't need it
+ defp place_without_placeid(place), do: Map.keys(place) == ["Text"]
+
+ defp in_this_region(%{"Text" => label}), do: Regex.match?(@filter, label)
+ defp in_this_region(%{"Label" => label}), do: Regex.match?(@filter, label)
+ defp in_this_region(_), do: true
+
+ defp dedup_place_text(%{"Text" => text}),
+ do: LocationService.Address.replace_common_street_suffix(text)
+
+ defp dedup_place_text(other), do: other
+
+ defp get_place_from_placeid(%{"PlaceId" => place_id}) do
+ case get_place(place_id) do
+ {:ok, %{"Place" => place}, _} ->
+ place
+
+ error ->
+ error
+ end
+ end
+
+ defp get_place_from_placeid(other), do: other
+
+ defp metro_station?(%{"SupplementalCategories" => [category]})
+ when category in ["Bus Stop", "Metro Station"],
+ do: true
+
+ defp metro_station?(_), do: false
+
defp index do
- prefix = Application.get_env(:dotcom, :aws_index_prefix)
- "#{prefix}-esri"
+ Application.get_env(:dotcom, __MODULE__)[:aws_index]
end
end
diff --git a/lib/location_service/suggestion.ex b/lib/location_service/suggestion.ex
deleted file mode 100644
index 284db16188..0000000000
--- a/lib/location_service/suggestion.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-defmodule LocationService.Suggestion do
- @moduledoc """
- An autocomplete suggestion.
- """
- @type t :: %__MODULE__{
- address: String.t(),
- highlighted_spans: [LocationService.Utils.HighlightedSpan.t()]
- }
-
- @enforce_keys [:address, :highlighted_spans]
- @derive Jason.Encoder
- defstruct address: nil,
- highlighted_spans: nil
-
- @type result :: {:ok, [t()]} | {:error, :internal_error}
-end
diff --git a/lib/location_service/utils.ex b/lib/location_service/utils.ex
deleted file mode 100644
index 09b0931a80..0000000000
--- a/lib/location_service/utils.ex
+++ /dev/null
@@ -1,46 +0,0 @@
-defmodule LocationService.Utils do
- defmodule HighlightedSpan do
- @type t :: %__MODULE__{
- offset: integer(),
- length: non_neg_integer()
- }
-
- @derive Jason.Encoder
- @enforce_keys [:offset, :length]
- defstruct offset: nil,
- length: nil
- end
-
- @doc """
- Gets indices of spans of text that should be highlighted in the
- autocomplete dropdown.
-
- Essentially, `search` is split on whitespace, and then we search `text`
- for words that start with any of the `search` terms. The spans are
- non-overlapping, and sorted by `offset`. There are examples in the tests
- that should further clarify the behavior.
- """
- @spec get_highlighted_spans(%{search: String.t(), text: String.t()}) :: [HighlightedSpan.t()]
- def get_highlighted_spans(%{search: search, text: text}) do
- parts = String.split(search)
-
- Enum.flat_map(parts, fn p ->
- # (^|\\W) -- Match start of string or non-word character
- # (? -- Begin a capture group named `t`
- # p -- Match the current part
- # \\w* -- Match any number of word characters
- # ) -- Close `t`
- src = "(^|\\W)(?" <> Regex.escape(p) <> "\\w*)"
- {:ok, re} = Regex.compile(src, "i")
-
- Regex.scan(re, text, return: :index, capture: :all_names)
- |> Enum.map(fn
- [{offset, length}] -> %HighlightedSpan{offset: offset, length: length}
- nil -> nil
- end)
- |> Enum.filter(& &1)
- end)
- |> Enum.uniq()
- |> Enum.sort(&(&1.offset <= &2.offset))
- end
-end
diff --git a/mix.exs b/mix.exs
index 8b748628ef..a7f82f8731 100644
--- a/mix.exs
+++ b/mix.exs
@@ -70,8 +70,9 @@ defmodule DotCom.Mixfile do
defp deps do
[
{:absinthe_client, "0.1.1"},
+ {:address_us, "0.4.2"},
{:aws, "1.0.2"},
- {:aws_credentials, "0.3.2"},
+ {:aws_credentials, "0.3.2", optional: true},
{:castore, "1.0.8"},
{:crc, "0.10.5"},
{:credo, "1.7.7", only: [:dev, :test]},
diff --git a/mix.lock b/mix.lock
index a725bb301c..b0be7dc8a6 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,5 +1,6 @@
%{
"absinthe_client": {:hex, :absinthe_client, "0.1.1", "1e778d587a27b85ecc35e4a5fedc64c85d9fdfd05395745c7af5345564dff54e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:slipstream, "~> 1.0", [hex: :slipstream, repo: "hexpm", optional: false]}], "hexpm", "e75a28c5bb647f485e9c03bbc3a47e7783742794bd4c10f3307a495a9e7273b6"},
+ "address_us": {:hex, :address_us, "0.4.2", "ee6637893d2419a3d256d4332a430d9f7ac146e0affc58d21862d4f1fb1c09c5", [:mix], [], "hexpm", "d276430248965992e0fb67b974fe9801e3819f453b9b703e233444853054279f"},
"aws": {:hex, :aws, "1.0.2", "39e0844ff126662e031e4bf186a6631b3402bf39f6af639260b27a4c29fdfe1a", [:mix], [{:aws_signature, "~> 0.3", [hex: :aws_signature, repo: "hexpm", optional: false]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0e5cb8dee3f50a81cd93820f4c68407371c3ebbf860000a92a51ecb7920e808d"},
"aws_credentials": {:hex, :aws_credentials, "0.3.2", "ba2ccee4ec6dcb5426cf71830b7afd73795b1f19655f401d4401015b468fec6f", [:rebar3], [{:eini, "~> 2.2.4", [hex: :eini_beam, repo: "hexpm", optional: false]}, {:iso8601, "~> 1.3.4", [hex: :iso8601, repo: "hexpm", optional: false]}, {:jsx, "~> 3.1.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "2e748626a935a7a544647fb79d7054f38db8bf378978542c962ccbeab387387b"},
"aws_signature": {:hex, :aws_signature, "0.3.2", "adf33bc4af00b2089b7708bf20e3246f09c639a905a619b3689f0a0a22c3ef8f", [:rebar3], [], "hexpm", "b0daf61feb4250a8ab0adea60db3e336af732ff71dd3fb22e45ae3dcbd071e44"},
diff --git a/test/algolia/query_test.exs b/test/algolia/query_test.exs
index 72ad814542..9389902f90 100644
--- a/test/algolia/query_test.exs
+++ b/test/algolia/query_test.exs
@@ -54,7 +54,7 @@ defmodule Algolia.QueryTest do
}
encoded = Query.build(full_query)
- assert {:ok, %{"requests" => [query]}} = Poison.decode(encoded)
+ assert {:ok, %{"requests" => [query]}} = Jason.decode(encoded)
assert %{
"indexName" => "index",
@@ -66,13 +66,20 @@ defmodule Algolia.QueryTest do
end
test "can construct and encode a query" do
+ n = Faker.random_between(1, 5)
+
encoded =
Query.build(%{
"algoliaQuery" => "b",
- "algoliaIndexes" => ["stops", "routes"]
+ "algoliaIndexesWithParams" => %{
+ "stops" => %{
+ "hitsPerPage" => n
+ },
+ "routes" => %{}
+ }
})
- assert {:ok, %{"requests" => multiple_queries}} = Poison.decode(encoded)
+ assert {:ok, %{"requests" => multiple_queries}} = Jason.decode(encoded)
assert length(multiple_queries) == 2
assert %{
@@ -83,7 +90,7 @@ defmodule Algolia.QueryTest do
} = List.first(multiple_queries)
assert params ==
- "analytics=false&clickAnalytics=true&facetFilters=%5B%5B%5D%5D&facets=%5B%22*%22%5D&hitsPerPage=2"
+ "analytics=false&clickAnalytics=true&facetFilters=%5B%5B%5D%5D&facets=%5B%22*%22%5D&hitsPerPage=#{n}"
end
end
end
diff --git a/test/algolia/request_test.exs b/test/algolia/request_test.exs
index 0a37b5507a..6460369415 100644
--- a/test/algolia/request_test.exs
+++ b/test/algolia/request_test.exs
@@ -14,27 +14,31 @@ defmodule Algolia.Query.RequestTest do
assert %Request{query: ^search_string} = Request.new("drupal", search_string)
end
- test "changes hitsPerPage param based on index" do
- assert %Request{params: %{"hitsPerPage" => 5}} = Request.new("routes", "")
- assert %Request{params: %{"hitsPerPage" => 2}} = Request.new("drupal", "")
- end
-
test "changes attributesToHighlight param based on index" do
assert %Request{attributesToHighlight: "stop.name"} = Request.new("stops", "")
assert %Request{attributesToHighlight: ["route.name", "route.long_name"]} =
Request.new("routes", "")
end
-
- test "changes facetFilters param based on index" do
- assert %Request{params: %{"facetFilters" => [[]]}} = Request.new("routes", "")
- assert %Request{params: %{"facetFilters" => facet_filters}} = Request.new("drupal", "")
- refute facet_filters == [[]]
- end
end
test "encode/1 returns a JSON-encodable map with encoded params" do
- request = Request.new("drupal", "some special search")
+ request =
+ Request.new("drupal", "some special search", %{
+ "hitsPerPage" => 2,
+ "facetFilters" => [
+ [
+ "_content_type:page",
+ "_content_type:search_result",
+ "_content_type:diversion",
+ "_content_type:landing_page",
+ "_content_type:person",
+ "_content_type:project",
+ "_content_type:project_update"
+ ]
+ ]
+ })
+
encoded = Request.encode(request)
assert encoded == %{
diff --git a/test/dotcom_web/controllers/places_controller_test.exs b/test/dotcom_web/controllers/places_controller_test.exs
index 8414ae4e8b..8398cb76cd 100644
--- a/test/dotcom_web/controllers/places_controller_test.exs
+++ b/test/dotcom_web/controllers/places_controller_test.exs
@@ -21,7 +21,7 @@ defmodule DotcomWeb.PlacesControllerTest do
test "responds with predictions", %{conn: conn} do
input = Faker.App.name()
hit_limit = Faker.random_between(3, 5)
- suggestions = build_list(3, :suggestion)
+ suggestions = build_list(3, :address)
expect(LocationService.Mock, :autocomplete, fn ^input, ^hit_limit ->
{:ok, suggestions}
@@ -53,18 +53,6 @@ defmodule DotcomWeb.PlacesControllerTest do
assert conn.status == 500
assert %{"error" => "Internal error"} = json_response(conn, 500)
end
-
- test "responds with 500 error when location service returns zero results", %{conn: conn} do
- expect(LocationService.Mock, :autocomplete, fn _, _ ->
- {:error, :zero_results}
- end)
-
- input = Faker.App.name()
- hit_limit = Faker.random_between(3, 5)
- conn = autocomplete(conn, %{"input" => input, "hit_limit" => "#{hit_limit}"})
- assert conn.status == 500
- assert %{"error" => "Zero results"} = json_response(conn, 500)
- end
end
describe "details" do
@@ -93,17 +81,6 @@ defmodule DotcomWeb.PlacesControllerTest do
assert conn.status == 500
assert %{"error" => "Internal error"} = json_response(conn, 500)
end
-
- test "responds with 500 error when AWS returns zero_results", %{conn: conn} do
- expect(LocationService.Mock, :geocode, fn _ ->
- {:error, :zero_results}
- end)
-
- conn = details(conn, %{"address" => "PLACE_ID"})
-
- assert conn.status == 500
- assert %{"error" => "Zero results"} = json_response(conn, 500)
- end
end
describe "reverse_geocode" do
@@ -143,21 +120,6 @@ defmodule DotcomWeb.PlacesControllerTest do
assert conn.status == 500
assert %{"error" => "Internal error"} = json_response(conn, 500)
end
-
- test "responds with 500 if AWS returns zero_results", %{conn: conn} do
- expect(LocationService.Mock, :reverse_geocode, fn _, _ ->
- {:error, :zero_results}
- end)
-
- conn =
- reverse_geocode(conn, %{
- "latitude" => "#{Faker.Address.latitude()}",
- "longitude" => "#{Faker.Address.longitude()}"
- })
-
- assert conn.status == 500
- assert %{"error" => "Zero results"} = json_response(conn, 500)
- end
end
test "/places/popular returns a list of popular locations", %{conn: conn} do
@@ -196,10 +158,7 @@ defmodule DotcomWeb.PlacesControllerTest do
LocationService.Mock
|> expect(:autocomplete, fn _, _ ->
- {:ok, build_list(num_suggestions, :suggestion)}
- end)
- |> expect(:geocode, num_suggestions, fn _ ->
- {:ok, build_list(2, :address)}
+ {:ok, build_list(num_suggestions, :address)}
end)
conn = search(conn, %{"query" => "south", "hit_limit" => "3"})
@@ -211,10 +170,7 @@ defmodule DotcomWeb.PlacesControllerTest do
test "adds URLs to each result", %{conn: conn} do
LocationService.Mock
|> expect(:autocomplete, fn _, _ ->
- {:ok, build_list(10, :suggestion)}
- end)
- |> expect(:geocode, 10, fn _ ->
- {:ok, build_list(1, :address)}
+ {:ok, build_list(10, :address)}
end)
conn = search(conn, %{"query" => "south", "hit_limit" => "3"})
@@ -222,15 +178,6 @@ defmodule DotcomWeb.PlacesControllerTest do
assert Enum.all?(locations, &has_urls?/1)
end
- test "handles if AWS returns zero_results", %{conn: conn} do
- expect(LocationService.Mock, :autocomplete, fn _, _ ->
- {:error, :zero_results}
- end)
-
- conn = search(conn, %{"query" => "south", "hit_limit" => "3"})
- assert %{"result" => []} = json_response(conn, 200)
- end
-
test "returns error", %{conn: conn} do
expect(LocationService.Mock, :autocomplete, fn _, _ ->
{:error, :interal_error}
diff --git a/test/location_service/address_test.exs b/test/location_service/address_test.exs
index 65fcb1611b..9491d08398 100644
--- a/test/location_service/address_test.exs
+++ b/test/location_service/address_test.exs
@@ -11,4 +11,69 @@ defmodule LocationService.AddressTest do
assert Position.longitude(address) == address.longitude
end
end
+
+ describe "get_highlighted_spans/2" do
+ defp get_highlighted_spans_text(search, text) do
+ Address.get_highlighted_spans(search, text)
+ |> Enum.map(fn %{offset: offset, length: length} ->
+ String.slice(text, offset, length)
+ end)
+ end
+
+ test "returns empty when no matches" do
+ assert [] = get_highlighted_spans_text("Lame", "Cool")
+ end
+
+ test "returns matched word case-insensitively" do
+ spans = get_highlighted_spans_text("ses", "Sesame Street")
+
+ assert ["Sesame"] = spans
+ end
+
+ test "returns matched words case-insensitively" do
+ spans = get_highlighted_spans_text("ses str", "Sesame Street")
+
+ assert ["Sesame", "Street"] = spans
+ end
+
+ test "returns matched words case-insensitively, out of order, in the order they appear in the text" do
+ spans = get_highlighted_spans_text("str ses", "Sesame Street")
+
+ assert ["Sesame", "Street"] = spans
+ end
+
+ test "ignores non-matching parts" do
+ spans = get_highlighted_spans_text("ses cool", "Sesame Street")
+
+ assert ["Sesame"] = spans
+ end
+
+ test "removes overlapping spans" do
+ spans = get_highlighted_spans_text("ses ses", "Sesame Street")
+
+ assert ["Sesame"] = spans
+ end
+
+ test "catches double matches" do
+ spans = get_highlighted_spans_text("ses", "Sesame Sesame Street")
+
+ assert ["Sesame", "Sesame"] = spans
+ end
+
+ test "only matches start of words" do
+ spans = get_highlighted_spans_text("ses eet", "Sesame Street")
+
+ assert ["Sesame"] = spans
+ end
+
+ test "can handle text with brackets" do
+ spans =
+ get_highlighted_spans_text(
+ "Great Harvest Bread Co. (great harvest bread)",
+ "Great Harvest Bread Co."
+ )
+
+ assert ["Great", "Harvest", "Bread", "Co."] = spans
+ end
+ end
end
diff --git a/test/location_service/location_service_test.exs b/test/location_service/location_service_test.exs
index 1b51f68266..bc4519f748 100644
--- a/test/location_service/location_service_test.exs
+++ b/test/location_service/location_service_test.exs
@@ -64,16 +64,23 @@ defmodule LocationServiceTest do
describe "autocomplete/2" do
test "can parse a response with results" do
text = Faker.Company.name()
+ response = build(:search_place_index_for_suggestions_response)
+ expected_place_ids = response["Results"] |> Enum.map(& &1["PlaceId"])
expect(AwsClient.Mock, :search_place_index_for_suggestions, fn _, input ->
assert input["Text"] == text
- response = build(:search_place_index_for_suggestions_response)
response = put_in(response["Summary"]["Text"], text)
{:ok, response, %{}}
end)
+ expect(AwsClient.Mock, :get_place, length(expected_place_ids), fn _, place_id ->
+ assert place_id in expected_place_ids
+ response = %{"Place" => build(:place)}
+ {:ok, response, %{}}
+ end)
+
suggestions = autocomplete(text, 2)
- assert {:ok, [%LocationService.Suggestion{} | _]} = suggestions
+ assert {:ok, [%LocationService.Address{} | _]} = suggestions
end
test "can handle a response with error" do
diff --git a/test/location_service/utils_test.exs b/test/location_service/utils_test.exs
deleted file mode 100644
index ed5c027078..0000000000
--- a/test/location_service/utils_test.exs
+++ /dev/null
@@ -1,71 +0,0 @@
-defmodule LocationService.UtilsTest do
- use ExUnit.Case, async: false
-
- import LocationService.Utils
- alias LocationService.Utils.HighlightedSpan
-
- defp get_highlighted_spans_text(query) do
- get_highlighted_spans(query)
- |> Enum.map(fn %HighlightedSpan{offset: offset, length: length} ->
- String.slice(query[:text], offset, length)
- end)
- end
-
- describe "get_highlighted_spans/1" do
- test "returns empty when no matches" do
- assert [] = get_highlighted_spans(%{search: "Lame", text: "Cool"})
- end
-
- test "returns matched word case-insensitively" do
- spans = get_highlighted_spans_text(%{search: "ses", text: "Sesame Street"})
-
- assert ["Sesame"] = spans
- end
-
- test "returns matched words case-insensitively" do
- spans = get_highlighted_spans_text(%{search: "ses str", text: "Sesame Street"})
-
- assert ["Sesame", "Street"] = spans
- end
-
- test "returns matched words case-insensitively, out of order, in the order they appear in the text" do
- spans = get_highlighted_spans_text(%{search: "str ses", text: "Sesame Street"})
-
- assert ["Sesame", "Street"] = spans
- end
-
- test "ignores non-matching parts" do
- spans = get_highlighted_spans_text(%{search: "ses cool", text: "Sesame Street"})
-
- assert ["Sesame"] = spans
- end
-
- test "removes overlapping spans" do
- spans = get_highlighted_spans_text(%{search: "ses ses", text: "Sesame Street"})
-
- assert ["Sesame"] = spans
- end
-
- test "catches double matches" do
- spans = get_highlighted_spans_text(%{search: "ses", text: "Sesame Sesame Street"})
-
- assert ["Sesame", "Sesame"] = spans
- end
-
- test "only matches start of words" do
- spans = get_highlighted_spans_text(%{search: "ses eet", text: "Sesame Street"})
-
- assert ["Sesame"] = spans
- end
-
- test "can handle text with brackets" do
- spans =
- get_highlighted_spans_text(%{
- search: "Great Harvest Bread Co. (great harvest bread)",
- text: "Great Harvest Bread Co."
- })
-
- assert ["Great", "Harvest", "Bread", "Co."] = spans
- end
- end
-end
diff --git a/test/support/env_helpers.ex b/test/support/env_helpers.ex
index eb0b954f28..25dba56ac4 100644
--- a/test/support/env_helpers.ex
+++ b/test/support/env_helpers.ex
@@ -7,7 +7,7 @@ defmodule Test.Support.EnvHelpers do
a test:
```
- reassign_env(:dotcom, :aws_index_prefix, "dotcom-prod")
+ reassign_env(:dotcom, :key, "value")
```
"""
defmacro reassign_env(app, var, value) do
diff --git a/test/support/factories/location_service/location_service.ex b/test/support/factories/location_service/location_service.ex
index 1ec4801012..2288d9a8cc 100644
--- a/test/support/factories/location_service/location_service.ex
+++ b/test/support/factories/location_service/location_service.ex
@@ -12,14 +12,4 @@ defmodule Test.Support.Factories.LocationService.LocationService do
longitude: Faker.Address.longitude()
}
end
-
- def suggestion_factory do
- %LocationService.Suggestion{
- address: Faker.Address.street_address(),
- highlighted_spans: %LocationService.Utils.HighlightedSpan{
- offset: Faker.random_between(0, 10),
- length: Faker.random_between(0, 10)
- }
- }
- end
end