feature(home-plugin): VisitApi implementation backed by StorageApi

Signed-off-by: Renan Mendes Carvalho <aitherios@gmail.com>
This commit is contained in:
Renan Mendes Carvalho
2023-08-29 17:34:52 +02:00
committed by Camila Belo
parent 5d671e6220
commit c485469098
5 changed files with 142 additions and 2 deletions
+14
View File
@@ -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;
@@ -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]);
});
});
@@ -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<Array<Visit>> => {
let visits: Array<Visit>;
try {
visits =
this.storageApi.snapshot<Array<Visit>>(this.storageKey).value ?? [];
} catch {
visits = [];
}
return visits;
};
this.persistAll = async (visits: Array<Visit>) =>
this.storageApi.set<Array<Visit>>(this.storageKey, visits);
}
}
+2 -1
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
export * from './VisitsApi';
export * from './CoreStorageVisitsApi';
export * from './LocalStorageVisitsApi';
export * from './VisitsApi';
export * from './VisitsApiFactory';
@@ -33,7 +33,7 @@ const visits = [
const mockVisitsApi = {
saveVisit: async () => visits[0],
listUserVisits: async () => visits,
listVisits: async () => visits,
};
describe('<Content kind="recent"/>', () => {