Skip to content

Commit af36b8f

Browse files
authored
feat: implement usage attribution (#844)
* feat: add internalUsageAttributionIds support (#1234) - Extend MapOptions type to include internalUsageAttributionIds - Implement default and custom attribution ID handling in APIProvider and GoogleMap components - Add tests for internalUsageAttributionIds behavior - Update TypeScript configurations to include custom type definition * docs: add docs for usage attribution features * chore: add reference to type augmentations * fix: add version number to default attribution id * chore: fix tsconfig typeRoots and includes
1 parent b704d2f commit af36b8f

File tree

16 files changed

+248
-31
lines changed

16 files changed

+248
-31
lines changed

docs/api-reference/components/api-provider.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ this to a generic value unique to this library (`GMP_VISGL_react`). You may
118118
opt out at any time by setting this prop to an empty string.
119119
Read more in the [documentation][gmp-solutions-usage].
120120

121+
#### `disableUsageAttribution`: boolean
122+
123+
To help Google understand which libraries and samples are helpful to
124+
developers, usage attribution IDs are sent with map requests by default.
125+
Set this prop to `true` to opt out of sending the usage attribution ID for
126+
this library.
127+
128+
Individual maps can still specify custom attribution IDs via the
129+
`internalUsageAttributionIds` prop on the `<Map>` component, which will be
130+
used even when this option is enabled.
131+
132+
Read more in the [documentation][gmp-usage-attribution].
133+
121134
### Events
122135

123136
#### `onLoad`: () => void {#onLoad}
@@ -165,6 +178,7 @@ The following hooks are built to work with the `APIProvider` Component:
165178
[gmp-region]: https://developers.google.com/maps/documentation/javascript/localization#Region
166179
[gmp-lang]: https://developers.google.com/maps/documentation/javascript/localization
167180
[gmp-solutions-usage]: https://developers.google.com/maps/reporting-and-monitoring/reporting#solutions-usage
181+
[gmp-usage-attribution]: https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.internalUsageAttributionIds
168182
[api-provider-src]: https://github.com/visgl/react-google-maps/blob/main/src/components/api-provider.tsx
169183
[rgm-new-issue]: https://github.com/visgl/react-google-maps/issues/new/choose
170184
[gmp-channel-usage]: https://developers.google.com/maps/reporting-and-monitoring/reporting#usage-tracking-per-channel

docs/api-reference/components/map.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,18 @@ this component will reuse map instances created with the same `mapId`,
201201

202202
See also the section [Map Instance Caching](#map-instance-caching) above.
203203

204+
#### `internalUsageAttributionIds`: string[]
205+
206+
Specify custom usage attribution IDs for this map. These IDs help Google
207+
understand which libraries and samples are being used. When specified, the
208+
custom IDs are appended to a default attribution ID from the
209+
`APIProvider` context.
210+
211+
If the `APIProvider` has `disableUsageAttribution` set to `true`, only the
212+
custom IDs specified here will be used.
213+
214+
Read more in the [documentation][gmp-usage-attribution].
215+
204216
### Camera Control
205217

206218
#### `center`: [google.maps.LatLngLiteral][gmp-ll]
@@ -368,6 +380,7 @@ to get access to the `google.maps.Map` object rendered in the `<Map>` component.
368380
[gmp-mapid]: https://developers.google.com/maps/documentation/get-map-id
369381
[gmp-map-styling]: https://developers.google.com/maps/documentation/javascript/cloud-customization
370382
[gmp-rendering-type]: https://developers.google.com/maps/documentation/javascript/reference/map#RenderingType
383+
[gmp-usage-attribution]: https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.internalUsageAttributionIds
371384
[api-provider]: ./api-provider.md
372385
[get-max-tilt]: https://github.com/visgl/react-google-maps/blob/4319bd3b68c40b9aa9b0ce7f377b52d20e824849/src/libraries/limit-tilt-range.ts#L4-L19
373386
[map-source]: https://github.com/visgl/react-google-maps/tree/main/src/components/map

package-lock.json

Lines changed: 33 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"@googlemaps/jest-mocks": "^2.18.0",
6969
"@rollup/plugin-commonjs": "^28.0.8",
7070
"@rollup/plugin-node-resolve": "^16.0.3",
71+
"@rollup/plugin-replace": "^6.0.2",
7172
"@rollup/plugin-typescript": "^12.1.4",
7273
"@testing-library/jest-dom": "^6.0.1",
7374
"@testing-library/react": "^16.0.0",

rollup.config.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import {readFileSync} from 'node:fs';
2+
import replace from '@rollup/plugin-replace';
13
import typescript from '@rollup/plugin-typescript';
24
import resolve from '@rollup/plugin-node-resolve';
35
import commonjs from '@rollup/plugin-commonjs';
46
import dts from 'rollup-plugin-dts';
57

8+
const {version: VERSION} = JSON.parse(readFileSync('./package.json', 'utf-8'));
9+
610
const external = ['react', 'react-dom', 'react/jsx-runtime', 'fast-deep-equal'];
711

812
const plugins = [
@@ -12,6 +16,13 @@ const plugins = [
1216
tsconfig: './tsconfig.build.json',
1317
declaration: false,
1418
declarationDir: undefined
19+
}),
20+
replace({
21+
preventAssignment: true,
22+
include: ['src/version.ts'],
23+
values: {
24+
__PACKAGE_VERSION__: VERSION
25+
}
1526
})
1627
];
1728

src/components/__tests__/api-provider.test.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import '@testing-library/jest-dom';
66
// FIXME: this should no longer be needed with the next version of @googlemaps/jest-mocks
77
import {importLibraryMock} from '../../libraries/__mocks__/lib/import-library-mock';
88

9+
import {VERSION} from '../../version';
910
import {
1011
APIProvider,
1112
APIProviderContext,
@@ -195,3 +196,33 @@ test('calls onError when loading the Google Maps JavaScript API fails', async ()
195196

196197
expect(onErrorMock).toHaveBeenCalled();
197198
});
199+
200+
describe('internalUsageAttributionIds', () => {
201+
test('provides default attribution IDs in context', () => {
202+
render(
203+
<APIProvider apiKey={'apikey'}>
204+
<ContextSpyComponent />
205+
</APIProvider>
206+
);
207+
208+
const contextSpy = ContextSpyComponent.spy;
209+
const actualContext: APIProviderContextValue = contextSpy.mock.lastCall[0];
210+
211+
expect(actualContext.internalUsageAttributionIds).toEqual([
212+
`gmp_visgl_reactgooglemaps_v${VERSION}`
213+
]);
214+
});
215+
216+
test('sets internalUsageAttributionIds to null when disableUsageAttribution is true', () => {
217+
render(
218+
<APIProvider apiKey={'apikey'} disableUsageAttribution>
219+
<ContextSpyComponent />
220+
</APIProvider>
221+
);
222+
223+
const contextSpy = ContextSpyComponent.spy;
224+
const actualContext: APIProviderContextValue = contextSpy.mock.lastCall[0];
225+
226+
expect(actualContext.internalUsageAttributionIds).toBeNull();
227+
});
228+
});

src/components/__tests__/map.test.tsx

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ beforeEach(() => {
2626
mapInstances: {},
2727
addMapInstance: jest.fn(),
2828
removeMapInstance: jest.fn(),
29-
clearMapInstances: jest.fn()
29+
clearMapInstances: jest.fn(),
30+
internalUsageAttributionIds: null
3031
};
3132

3233
wrapper = ({children}) => (
@@ -113,7 +114,8 @@ describe('creating and updating map instance', () => {
113114
expect(actualOptions).toStrictEqual({
114115
center: {lat: 53.55, lng: 10.05},
115116
zoom: 12,
116-
mapId: 'mymapid'
117+
mapId: 'mymapid',
118+
internalUsageAttributionIds: null
117119
});
118120

119121
// should register the map-instance in the context
@@ -277,3 +279,70 @@ describe('camera configuration', () => {
277279
describe('map events and event-props', () => {
278280
test.todo('events dispatched by the map are received via event-props');
279281
});
282+
283+
describe('internalUsageAttributionIds', () => {
284+
test('uses default attribution IDs from context', () => {
285+
mockContextValue.internalUsageAttributionIds = [
286+
'GMP_LIB_VISGL_REACT_GOOGLE_MAPS'
287+
];
288+
289+
render(<GoogleMap zoom={8} center={{lat: 53.55, lng: 10.05}} />, {wrapper});
290+
291+
const [, options] = createMapSpy.mock.lastCall!;
292+
expect(options).toMatchObject({
293+
internalUsageAttributionIds: ['GMP_LIB_VISGL_REACT_GOOGLE_MAPS']
294+
});
295+
});
296+
297+
test('uses null from context when disableUsageAttribution is set', () => {
298+
mockContextValue.internalUsageAttributionIds = null;
299+
300+
render(<GoogleMap zoom={8} center={{lat: 53.55, lng: 10.05}} />, {wrapper});
301+
302+
const [, options] = createMapSpy.mock.lastCall!;
303+
expect(options).toBeDefined();
304+
expect(options?.internalUsageAttributionIds).toBeNull();
305+
});
306+
307+
test('appends custom attribution IDs to default IDs', () => {
308+
mockContextValue.internalUsageAttributionIds = [
309+
'GMP_LIB_VISGL_REACT_GOOGLE_MAPS'
310+
];
311+
312+
render(
313+
<GoogleMap
314+
zoom={8}
315+
center={{lat: 53.55, lng: 10.05}}
316+
internalUsageAttributionIds={['CUSTOM_ID_1', 'CUSTOM_ID_2']}
317+
/>,
318+
{wrapper}
319+
);
320+
321+
const [, options] = createMapSpy.mock.lastCall!;
322+
expect(options).toMatchObject({
323+
internalUsageAttributionIds: [
324+
'GMP_LIB_VISGL_REACT_GOOGLE_MAPS',
325+
'CUSTOM_ID_1',
326+
'CUSTOM_ID_2'
327+
]
328+
});
329+
});
330+
331+
test('uses only custom IDs when context is disabled', () => {
332+
mockContextValue.internalUsageAttributionIds = null;
333+
334+
render(
335+
<GoogleMap
336+
zoom={8}
337+
center={{lat: 53.55, lng: 10.05}}
338+
internalUsageAttributionIds={['CUSTOM_ID_1', 'CUSTOM_ID_2']}
339+
/>,
340+
{wrapper}
341+
);
342+
343+
const [, options] = createMapSpy.mock.lastCall!;
344+
expect(options).toMatchObject({
345+
internalUsageAttributionIds: ['CUSTOM_ID_1', 'CUSTOM_ID_2']
346+
});
347+
});
348+
});

0 commit comments

Comments
 (0)