feature(home-plugin): Adds user scope to storage calls

This patch adds the userEntityRef to the storage key ensuring they are
scoped per user.

Signed-off-by: Renan Mendes Carvalho <aitherios@gmail.com>
This commit is contained in:
Renan Mendes Carvalho
2023-08-30 10:26:16 +02:00
committed by Camila Belo
parent c485469098
commit 6cfcf8a4a7
5 changed files with 65 additions and 17 deletions
+6 -1
View File
@@ -14,6 +14,7 @@ import { CardSettings as CardSettings_2 } from '@backstage/plugin-home-react';
import { ComponentParts as ComponentParts_2 } from '@backstage/plugin-home-react';
import { ComponentRenderer as ComponentRenderer_2 } from '@backstage/plugin-home-react';
import { createCardExtension as createCardExtension_2 } from '@backstage/plugin-home-react';
import { IdentityApi } from '@backstage/core-plugin-api';
import { JSX as JSX_2 } from 'react';
import { default as React_2 } from 'react';
import { ReactElement } from 'react';
@@ -79,12 +80,14 @@ export const ComponentTabs: (props: {
export class CoreStorageVisitsApi extends VisitsApiFactory {
constructor({
storageApi,
identityApi,
randomUUID,
limit,
}: {
storageApi: StorageApi;
randomUUID?: Window['crypto']['randomUUID'];
limit?: number;
identityApi: IdentityApi;
});
}
@@ -181,10 +184,12 @@ export class LocalStorageVisitsApi extends VisitsApiFactory {
localStorage,
randomUUID,
limit,
}?: {
identityApi,
}: {
localStorage?: Window['localStorage'];
randomUUID?: Window['crypto']['randomUUID'];
limit?: number;
identityApi: IdentityApi;
});
}
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { BackstageUserIdentity, IdentityApi } from '@backstage/core-plugin-api';
import { CoreStorageVisitsApi } from './CoreStorageVisitsApi';
import { MockStorageApi } from '@backstage/test-utils';
@@ -24,6 +25,14 @@ describe('new CoreStorageVisitsApi({ storageApi: MockStorageApi.create() })', ()
() => Math.floor(Math.random() * 16).toString(16), // 0x0 to 0xf
) as `${string}-${string}-${string}-${string}-${string}`;
const mockIdentityApi: IdentityApi = {
signOut: jest.fn(),
getProfileInfo: jest.fn(),
getBackstageIdentity: async () =>
({ userEntityRef: 'user:default/guest' } as BackstageUserIdentity),
getCredentials: jest.fn(),
};
beforeEach(() => {
window.crypto.randomUUID = mockRandomUUID;
});
@@ -32,9 +41,10 @@ describe('new CoreStorageVisitsApi({ storageApi: MockStorageApi.create() })', ()
window.localStorage.clear();
});
it('instantiates with no configuration', () => {
it('instantiates', () => {
const api = new CoreStorageVisitsApi({
storageApi: MockStorageApi.create(),
identityApi: mockIdentityApi,
});
expect(api).toBeTruthy();
});
@@ -42,6 +52,7 @@ describe('new CoreStorageVisitsApi({ storageApi: MockStorageApi.create() })', ()
it('saves a visit', async () => {
const api = new CoreStorageVisitsApi({
storageApi: MockStorageApi.create(),
identityApi: mockIdentityApi,
});
const visit = {
pathname: '/catalog/default/component/playback-order',
@@ -58,6 +69,7 @@ describe('new CoreStorageVisitsApi({ storageApi: MockStorageApi.create() })', ()
it('retrieves visits', async () => {
const api = new CoreStorageVisitsApi({
storageApi: MockStorageApi.create(),
identityApi: mockIdentityApi,
});
const visit = {
pathname: '/catalog/default/component/playback-order',
+16 -6
View File
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { StorageApi } from '@backstage/core-plugin-api';
import { IdentityApi, StorageApi } from '@backstage/core-plugin-api';
import { Visit } from './VisitsApi';
import { VisitsApiFactory } from './VisitsApiFactory';
@@ -23,30 +23,40 @@ import { VisitsApiFactory } from './VisitsApiFactory';
*/
export class CoreStorageVisitsApi extends VisitsApiFactory {
private readonly storageApi: StorageApi;
private readonly storageKey = '@backstage/plugin-home:visits';
private readonly storageKeyPrefix = '@backstage/plugin-home:visits';
private readonly identityApi: IdentityApi;
constructor({
storageApi,
identityApi,
randomUUID = window?.crypto?.randomUUID,
limit = 100,
}: {
storageApi: StorageApi;
randomUUID?: Window['crypto']['randomUUID'];
limit?: number;
identityApi: IdentityApi;
}) {
super({ randomUUID, limit });
this.storageApi = storageApi;
this.identityApi = identityApi;
this.retrieveAll = async (): Promise<Array<Visit>> => {
let visits: Array<Visit>;
const { userEntityRef } = await this.identityApi.getBackstageIdentity();
const storageKey = `${this.storageKeyPrefix}:${userEntityRef}`;
try {
visits =
this.storageApi.snapshot<Array<Visit>>(this.storageKey).value ?? [];
visits = this.storageApi.snapshot<Array<Visit>>(storageKey).value ?? [];
} catch {
visits = [];
}
return visits;
};
this.persistAll = async (visits: Array<Visit>) =>
this.storageApi.set<Array<Visit>>(this.storageKey, visits);
this.persistAll = async (visits: Array<Visit>) => {
const { userEntityRef } = await this.identityApi.getBackstageIdentity();
const storageKey = `${this.storageKeyPrefix}:${userEntityRef}`;
return this.storageApi.set<Array<Visit>>(storageKey, visits);
};
}
}
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { BackstageUserIdentity, IdentityApi } from '@backstage/core-plugin-api';
import { LocalStorageVisitsApi } from './LocalStorageVisitsApi';
describe('new LocalStorageVisitsApi()', () => {
@@ -23,21 +24,30 @@ describe('new LocalStorageVisitsApi()', () => {
() => Math.floor(Math.random() * 16).toString(16), // 0x0 to 0xf
) as `${string}-${string}-${string}-${string}-${string}`;
const mockIdentityApi: IdentityApi = {
signOut: jest.fn(),
getProfileInfo: jest.fn(),
getBackstageIdentity: async () =>
({ userEntityRef: 'user:default/guest' } as BackstageUserIdentity),
getCredentials: jest.fn(),
};
beforeEach(() => {
window.crypto.randomUUID = mockRandomUUID;
});
afterEach(() => {
window.localStorage.clear();
jest.resetAllMocks();
});
it('instantiates with no configuration', () => {
const api = new LocalStorageVisitsApi();
it('instantiates with only identitiyApi', () => {
const api = new LocalStorageVisitsApi({ identityApi: mockIdentityApi });
expect(api).toBeTruthy();
});
it('saves a visit', async () => {
const api = new LocalStorageVisitsApi();
const api = new LocalStorageVisitsApi({ identityApi: mockIdentityApi });
const visit = {
pathname: '/catalog/default/component/playback-order',
entityRef: 'component:default/playback-order',
@@ -51,7 +61,7 @@ describe('new LocalStorageVisitsApi()', () => {
});
it('retrieves visits', async () => {
const api = new LocalStorageVisitsApi();
const api = new LocalStorageVisitsApi({ identityApi: mockIdentityApi });
const visit = {
pathname: '/catalog/default/component/playback-order',
entityRef: 'component:default/playback-order',
+16 -5
View File
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { IdentityApi } from '@backstage/core-plugin-api';
import { Visit } from './VisitsApi';
import { VisitsApiFactory } from './VisitsApiFactory';
@@ -22,31 +23,41 @@ import { VisitsApiFactory } from './VisitsApiFactory';
*/
export class LocalStorageVisitsApi extends VisitsApiFactory {
private readonly localStorage: Window['localStorage'];
private readonly storageKey = '@backstage/plugin-home:visits';
private readonly storageKeyPrefix = '@backstage/plugin-home:visits';
private readonly identityApi: IdentityApi;
constructor({
localStorage = window?.localStorage,
randomUUID = window?.crypto?.randomUUID,
limit = 100,
identityApi,
}: {
localStorage?: Window['localStorage'];
randomUUID?: Window['crypto']['randomUUID'];
limit?: number;
} = {}) {
identityApi: IdentityApi;
}) {
super({ randomUUID, limit });
this.localStorage = localStorage;
this.identityApi = identityApi;
this.retrieveAll = async (): Promise<Array<Visit>> => {
let visits: Array<Visit>;
const { userEntityRef } = await this.identityApi.getBackstageIdentity();
const storageKey = `${this.storageKeyPrefix}:${userEntityRef}`;
try {
visits = JSON.parse(this.localStorage.getItem(this.storageKey) ?? '[]');
visits = JSON.parse(this.localStorage.getItem(storageKey) ?? '[]');
} catch {
visits = [];
}
return visits;
};
this.persistAll = async (visits: Array<Visit>) => {
this.localStorage.setItem(
this.storageKey,
const { userEntityRef } = await this.identityApi.getBackstageIdentity();
const storageKey = `${this.storageKeyPrefix}:${userEntityRef}`;
return this.localStorage.setItem(
storageKey,
JSON.stringify(visits.splice(0, this.limit)),
);
};