diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 56fa3ef281..3352a291bc 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -20,6 +20,7 @@ import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RendererProps as RendererProps_2 } from '@backstage/plugin-home-react'; import { RouteRef } from '@backstage/core-plugin-api'; +import { StorageApi } from '@backstage/core-plugin-api'; // @public export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; @@ -74,6 +75,19 @@ export const ComponentTabs: (props: { }[]; }) => JSX_2.Element; +// @public +export class CoreStorageVisitsApi extends VisitsApiFactory { + constructor({ + storageApi, + randomUUID, + limit, + }: { + storageApi: StorageApi; + randomUUID?: Window['crypto']['randomUUID']; + limit?: number; + }); +} + // @public @deprecated (undocumented) export const createCardExtension: typeof createCardExtension_2; diff --git a/plugins/home/src/api/CoreStorageVisitsApi.test.ts b/plugins/home/src/api/CoreStorageVisitsApi.test.ts new file mode 100644 index 0000000000..9185ece2c6 --- /dev/null +++ b/plugins/home/src/api/CoreStorageVisitsApi.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CoreStorageVisitsApi } from './CoreStorageVisitsApi'; +import { MockStorageApi } from '@backstage/test-utils'; + +describe('new CoreStorageVisitsApi({ storageApi: MockStorageApi.create() })', () => { + const mockRandomUUID = () => + '068f3129-7440-4e0e-8fd4-xxxxxxxxxxxx'.replace( + /x/g, + () => Math.floor(Math.random() * 16).toString(16), // 0x0 to 0xf + ) as `${string}-${string}-${string}-${string}-${string}`; + + beforeEach(() => { + window.crypto.randomUUID = mockRandomUUID; + }); + + afterEach(() => { + window.localStorage.clear(); + }); + + it('instantiates with no configuration', () => { + const api = new CoreStorageVisitsApi({ + storageApi: MockStorageApi.create(), + }); + expect(api).toBeTruthy(); + }); + + it('saves a visit', async () => { + const api = new CoreStorageVisitsApi({ + storageApi: MockStorageApi.create(), + }); + const visit = { + pathname: '/catalog/default/component/playback-order', + entityRef: 'component:default/playback-order', + name: 'Playback Order', + }; + const returnedVisit = await api.saveVisit({ visit }); + expect(returnedVisit).toEqual(expect.objectContaining(visit)); + expect(returnedVisit.id).toBeTruthy(); + expect(returnedVisit.timestamp).toBeTruthy(); + expect(returnedVisit.hits).toBeTruthy(); + }); + + it('retrieves visits', async () => { + const api = new CoreStorageVisitsApi({ + storageApi: MockStorageApi.create(), + }); + const visit = { + pathname: '/catalog/default/component/playback-order', + entityRef: 'component:default/playback-order', + name: 'Playback Order', + }; + const returnedVisit = await api.saveVisit({ visit }); + const visits = await api.listVisits(); + expect(visits).toHaveLength(1); + expect(visits).toEqual([expect.objectContaining(visit)]); + expect(visits).toEqual([returnedVisit]); + }); +}); diff --git a/plugins/home/src/api/CoreStorageVisitsApi.ts b/plugins/home/src/api/CoreStorageVisitsApi.ts new file mode 100644 index 0000000000..8b54fcc416 --- /dev/null +++ b/plugins/home/src/api/CoreStorageVisitsApi.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { StorageApi } from '@backstage/core-plugin-api'; +import { Visit } from './VisitsApi'; +import { VisitsApiFactory } from './VisitsApiFactory'; + +/** + * @public + * This is an implementation of VisitsApi that relies on a StorageApi + */ +export class CoreStorageVisitsApi extends VisitsApiFactory { + private readonly storageApi: StorageApi; + private readonly storageKey = '@backstage/plugin-home:visits'; + + constructor({ + storageApi, + randomUUID = window?.crypto?.randomUUID, + limit = 100, + }: { + storageApi: StorageApi; + randomUUID?: Window['crypto']['randomUUID']; + limit?: number; + }) { + super({ randomUUID, limit }); + this.storageApi = storageApi; + this.retrieveAll = async (): Promise> => { + let visits: Array; + try { + visits = + this.storageApi.snapshot>(this.storageKey).value ?? []; + } catch { + visits = []; + } + return visits; + }; + this.persistAll = async (visits: Array) => + this.storageApi.set>(this.storageKey, visits); + } +} diff --git a/plugins/home/src/api/index.ts b/plugins/home/src/api/index.ts index b90a14299c..cb283aa2cf 100644 --- a/plugins/home/src/api/index.ts +++ b/plugins/home/src/api/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ -export * from './VisitsApi'; +export * from './CoreStorageVisitsApi'; export * from './LocalStorageVisitsApi'; +export * from './VisitsApi'; export * from './VisitsApiFactory'; diff --git a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx index d73a9d1a75..a9ff2cdd7c 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Content.test.tsx @@ -33,7 +33,7 @@ const visits = [ const mockVisitsApi = { saveVisit: async () => visits[0], - listUserVisits: async () => visits, + listVisits: async () => visits, }; describe('', () => {