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

chore: simplify props related to configuring basemap layers #476

Merged
merged 3 commits into from
Aug 24, 2024
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
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
23 changes: 15 additions & 8 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" applySatelliteStyle 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 All @@ -67,7 +78,6 @@ <h1 style="color:red;font-size:16px;">*** This is a testing sandbox - these comp
<script>
// --- MAP --- //
const map = document.querySelector("my-map");
map.geojsonDataCopyright = `<a href="https://www.planning.data.gov.uk/dataset/title-boundary" target="_blank">Title boundary</a> subject to Crown copyright and database rights ${new Date().getFullYear()} OS (0)100026316`;
map.clipGeojsonData = {
"type": "Feature",
"geometry": {
Expand Down Expand Up @@ -104,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
Loading