Skip to content

Commit

Permalink
refactor os keys too
Browse files Browse the repository at this point in the history
  • Loading branch information
jessicamcinchak committed Aug 19, 2024
1 parent 8aea974 commit 200c7d9
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 54 deletions.
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
VITE_APP_OS_VECTOR_TILES_API_KEY=👻
VITE_APP_OS_FEATURES_API_KEY=👻
VITE_APP_OS_PLACES_API_KEY=👻
VITE_APP_OS_API_KEY=👻
VITE_APP_MAPBOX_ACCESS_TOKEN=👻
4 changes: 2 additions & 2 deletions .github/workflows/pnpm-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ jobs:
- run: pnpm install --no-frozen-lockfile
- run: pnpm test
env:
VITE_APP_OS_PLACES_API_KEY: ${{ secrets.OS_API_KEY }}
VITE_APP_OS_VECTOR_TILES_API_KEY: ${{ secrets.OS_API_KEY }}
VITE_APP_OS_API_KEY: ${{ secrets.OS_API_KEY }}
VITE_APP_MAPBOX_ACCESS_TOKEN: ${{ secrets.MAPBOX_ACCESS_TOKEN }}
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,20 @@ Find these components in the wild, including what we're learning through public

Different features rely on different APIs - namely from Ordnance Survey and Mapbox.

Address autocomplete utilises OS Places API.
You can set keys directly as props (eg `osApiKey`) on the applicable web components or via environment variables (eg `VITE_APP_OS_API_KEY`) for local development.

Address autocomplete utilises the OS Places API.

For the map:
- We'll attempt to render the map using the OS Vector Tiles API
- You can pass `disableVectorTiles` to render a raster basemap instead which uses the default OS Maps API
- If you don't have an OS API key at all, it defaults to OpenStreetMap
- The `basemap` prop defaults to `"OSVectorTile"` which requires the OS Vector Tiles API
- Basemap `"OSRaster"` uses the OS Maps API
- Basemap `"MapboxSatellite"` requires a Mapbox Access Token with with scope `style:read`
- The `"OSM"` (OpenStreetMap) basemap is available for users without any keys, and as a fallback if any of the above basemaps fail to build
- `clickFeatures` requires the OS Features API
- `applySatelliteStyle` requires a Mapbox Access Token with the scope `style:read`

When using Ordnance Survey APIs:
- Update the `osCopyright` attribution with your license number
- Configure `osProxyEndpoint` to avoid exposing your keys
- Update the `osCopyright` attribution prop with your license number
- Configure an optional `osProxyEndpoint` to avoid exposing your keys (set this instead of `osApiKey`)
- ** We are not currently supporting a similar proxy for Mapbox because access tokens can be restricted to specific URLs via your account

## Running locally
Expand Down
22 changes: 15 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,22 @@

<body style="font-family:Inter,Helvetica,sans-serif;">
<div style="display:flex;flex-direction:column;">
<h1 style="color:red;font-size:16px;">*** This is a testing sandbox - these components are unaware of each other!
***</h1>
<h1 style="color:red;font-size:16px;">
*** This is a testing sandbox - these components are unaware of each other!***
</h1>
<div style="margin-bottom:1em">
<my-map zoom="20" maxZoom="23" id="example-map" drawMode drawMany drawType="Polygon" useScaleBarStyle showScale showNorthArrow showPrint
osProxyEndpoint="https://api.editor.planx.dev/proxy/ordnance-survey" ariaLabelOlFixedOverlay="Interactive example map" />
<my-map
id="example-map"
ariaLabelOlFixedOverlay="Interactive example map"
zoom="20"
maxZoom="23"
basemap="MapboxSatellite"
drawMode
drawMany
drawType="Polygon"
osCopyright="© Crown copyright and database rights 2024 OS (0)100024857"
osProxyEndpoint="https://api.editor.planx.dev/proxy/ordnance-survey"
/>
</div>
<div style="margin-bottom:1em">
<postcode-search hintText="Optional hint text shows up here" id="example-postcode" />
Expand Down Expand Up @@ -103,9 +114,6 @@ <h1 style="color:red;font-size:16px;">*** This is a testing sandbox - these comp
});

// applicable when drawMode is enabled
map.addEventListener("areaChange", ({ detail: area }) => {
console.debug({ area });
});
map.addEventListener("geojsonChange", ({ detail: geojson }) => {
console.debug({ geojson });
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ module.exports = {
{
title: "Select an address in postcode SE19 1NT",
description: "Standard case",
template: `<address-autocomplete postcode="SE19 1NT" osPlacesApiKey=${process.env.VITE_APP_OS_PLACES_API_KEY} />`,
template: `<address-autocomplete postcode="SE19 1NT" osApiKey=${process.env.VITE_APP_OS_API_KEY} />`,
controller: function (document) {
const autocomplete = document.querySelector("address-autocomplete");

Expand All @@ -88,14 +88,14 @@ module.exports = {
"addressSelection",
({ detail: address }) => {
console.debug({ detail: address });
}
},
);
},
},
{
title: "Select an address in postcode SE19 1NT",
description: "Standard case (via proxy)",
template: `<address-autocomplete postcode="SE19 1NT" osPlacesApiKey="" osProxyEndpoint="https://api.editor.planx.dev/proxy/ordnance-survey" />`,
template: `<address-autocomplete postcode="SE19 1NT" osApiKey="" osProxyEndpoint="https://api.editor.planx.dev/proxy/ordnance-survey" />`,
controller: function (document) {
const autocomplete = document.querySelector("address-autocomplete");

Expand All @@ -107,7 +107,7 @@ module.exports = {
"addressSelection",
({ detail: address }) => {
console.debug({ detail: address });
}
},
);
},
},
Expand Down
30 changes: 18 additions & 12 deletions src/components/address-autocomplete/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export class AddressAutocomplete extends LitElement {
@property({ type: String })
initialAddress = "";

@property({ type: String })
osApiKey = import.meta.env.VITE_APP_OS_API_KEY || "";

/**
* @deprecated - please set singular `osApiKey`
*/
@property({ type: String })
osPlacesApiKey = import.meta.env.VITE_APP_OS_PLACES_API_KEY || "";

Expand Down Expand Up @@ -94,9 +100,9 @@ export class AddressAutocomplete extends LitElement {
address.LPI.ADDRESS.slice(
0,
address.LPI.ADDRESS.lastIndexOf(
`, ${address.LPI.ADMINISTRATIVE_AREA}`
)
) === option
`, ${address.LPI.ADMINISTRATIVE_AREA}`,
),
) === option,
)[0];
if (this._selectedAddress)
this.dispatch("addressSelection", { address: this._selectedAddress });
Expand All @@ -105,7 +111,7 @@ export class AddressAutocomplete extends LitElement {
}

async _fetchData(offset: number = 0, prevResults: Address[] = []) {
const isUsingOS = Boolean(this.osPlacesApiKey || this.osProxyEndpoint);
const isUsingOS = Boolean(this.osApiKey || this.osProxyEndpoint);
if (!isUsingOS)
throw Error("OS Places API key or OS proxy endpoint not found");

Expand All @@ -120,7 +126,7 @@ export class AddressAutocomplete extends LitElement {
};
const url = getServiceURL({
service: "places",
apiKey: this.osPlacesApiKey,
apiKey: this.osApiKey,
proxyEndpoint: this.osProxyEndpoint,
params,
});
Expand Down Expand Up @@ -153,17 +159,17 @@ export class AddressAutocomplete extends LitElement {
.filter(
(address: Address) =>
// filter out "ALTERNATIVE", "HISTORIC", and "PROVISIONAL" records
address.LPI.LPI_LOGICAL_STATUS_CODE_DESCRIPTION === "APPROVED"
address.LPI.LPI_LOGICAL_STATUS_CODE_DESCRIPTION === "APPROVED",
)
.map((address: Address) => {
// omit the council name and postcode from the display name
this._options.push(
address.LPI.ADDRESS.slice(
0,
address.LPI.ADDRESS.lastIndexOf(
`, ${address.LPI.ADMINISTRATIVE_AREA}`
)
)
`, ${address.LPI.ADMINISTRATIVE_AREA}`,
),
),
);
});

Expand All @@ -178,7 +184,7 @@ export class AddressAutocomplete extends LitElement {
) {
this._fetchData(
this._addressesInPostcode.length,
this._addressesInPostcode
this._addressesInPostcode,
);
}
})
Expand Down Expand Up @@ -241,7 +247,7 @@ export class AddressAutocomplete extends LitElement {
render() {
// handle various error states
let errorMessage;
if (!this.osPlacesApiKey && !this.osProxyEndpoint)
if (!this.osApiKey && !this.osProxyEndpoint)
errorMessage = "Missing OS Places API key or proxy endpoint";
else if (this._osError) errorMessage = this._osError;
else if (this._totalAddresses === 0)
Expand All @@ -263,7 +269,7 @@ export class AddressAutocomplete extends LitElement {
this.dispatchEvent(
new CustomEvent(eventName, {
detail: payload,
})
}),
);
}

Expand Down
26 changes: 10 additions & 16 deletions src/components/address-autocomplete/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ declare global {
}

test.todo(
"Replace environment variable prop dependency with mock response. Ref https://vitest.dev/guide/mocking.html"
"Replace environment variable prop dependency with mock response. Ref https://vitest.dev/guide/mocking.html",
);

describe("AddressAutocomplete on initial render with valid postcode", async () => {
beforeEach(async () => {
document.body.innerHTML = `<address-autocomplete id="autocomplete-vitest" postcode="SE5 0HU" osPlacesApiKey=${
import.meta.env.VITE_APP_OS_PLACES_API_KEY
} />`;

document.body.innerHTML = `<address-autocomplete id="autocomplete-vitest" postcode="SE5 0HU" osApiKey=${import.meta.env.VITE_APP_OS_API_KEY} />`;
await window.happyDOM.whenAsyncComplete();
}, 2500);

Expand Down Expand Up @@ -57,26 +54,23 @@ describe("AddressAutocomplete on initial render with valid postcode", async () =

it("should always render the warning message container for screenreaders", () => {
const error = getShadowRoot("address-autocomplete")?.getElementById(
"error-message-container"
"error-message-container",
);
expect(error).toBeTruthy;
});
});

describe("AddressAutocomplete on initial render with empty postcode", async () => {
beforeEach(async () => {
document.body.innerHTML = `<address-autocomplete id="autocomplete-vitest" postcode="HP11 1BR" osPlacesApiKey=${
import.meta.env.VITE_APP_OS_PLACES_API_KEY
} />`;

document.body.innerHTML = `<address-autocomplete id="autocomplete-vitest" postcode="HP11 1BR" osApiKey=${import.meta.env.VITE_APP_OS_API_KEY} />`;
await window.happyDOM.whenAsyncComplete();
}, 500);

it.todo("renders a 'no addresses in this postcode' warning", () => {
const autocomplete = getShadowRoot("address-autocomplete");
console.log(autocomplete?.innerHTML); // pnpm test:ui
expect(autocomplete?.innerHTML).toContain(
"No addresses found in postcode HP11 1BR"
"No addresses found in postcode HP11 1BR",
);
});
});
Expand All @@ -92,24 +86,24 @@ describe("External API calls", async () => {
const lastTwoCalls = () => fetchSpy.mock.calls?.slice(-2).join(", ");

it("calls proxy when 'osProxyEndpoint' provided", async () => {
document.body.innerHTML = `<address-autocomplete id="autocomplete-vitest" postcode="SE5 0HU" osPlacesApiKey="" osProxyEndpoint="https://www.my-site.com/api/v1/os" />`;
document.body.innerHTML = `<address-autocomplete id="autocomplete-vitest" postcode="SE5 0HU" osApiKey="" osProxyEndpoint="https://www.my-site.com/api/v1/os" />`;
await window.happyDOM.whenAsyncComplete();

expect(fetchSpy).toHaveBeenCalled();
expect(lastTwoCalls()).toContain(
"https://www.my-site.com/api/v1/os/search/places/v1/postcode?postcode=SE5+0HU"
"https://www.my-site.com/api/v1/os/search/places/v1/postcode?postcode=SE5+0HU",
);
expect(fetchSpy.mock.calls?.[0]).not.toContain("&key=");
});

it("calls OS API when 'osPlacesApiKey' provided", async () => {
it("calls OS API when 'osApiKey' provided", async () => {
const mockAPIKey = "test-test-test";
document.body.innerHTML = `<address-autocomplete id="autocomplete-vitest" postcode="SE5 0HU" osPlacesApiKey=${mockAPIKey} />`;
document.body.innerHTML = `<address-autocomplete id="autocomplete-vitest" postcode="SE5 0HU" osApiKey=${mockAPIKey} />`;
await window.happyDOM.whenAsyncComplete();

expect(fetchSpy).toHaveBeenCalled();
expect(lastTwoCalls()).toContain(
"https://api.os.uk/search/places/v1/postcode?postcode=SE5+0HU"
"https://api.os.uk/search/places/v1/postcode?postcode=SE5+0HU",
);
expect(lastTwoCalls()).toContain(`&key=${mockAPIKey}`);
});
Expand Down
4 changes: 1 addition & 3 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {
VITE_APP_OS_VECTOR_TILES_API_KEY: string;
VITE_APP_OS_FEATURES_API_KEY: string;
VITE_APP_OS_PLACES_API_KEY: string;
VITE_APP_OS_API_KEY: string;
VITE_APP_MAPBOX_ACCESS_TOKEN: string;
}

Expand Down

0 comments on commit 200c7d9

Please sign in to comment.