Merge pull request #12571 from alexrybch/shortcuts-storage-observe
added initial storage api subscription in Shortcuts plugin
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/plugin-shortcuts': minor
|
||||
---
|
||||
|
||||
Internal observable replaced with a mapping from the storage API. This fixes shortcuts initialization when using firestore.
|
||||
|
||||
`ShortcutApi.get` method, that returns an immediate snapshot of shortcuts, made public.
|
||||
|
||||
Example of how to get and observe `shortcuts`:
|
||||
|
||||
```typescript
|
||||
const shortcutApi = useApi(shortcutsApiRef);
|
||||
const shortcuts = useObservable(shortcutApi.shortcut$(), shortcutApi.get());
|
||||
```
|
||||
@@ -9,7 +9,7 @@ import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import { default as Observable_2 } from 'zen-observable';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "LocalStoredShortcuts" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -20,11 +20,13 @@ export class LocalStoredShortcuts implements ShortcutApi {
|
||||
// (undocumented)
|
||||
add(shortcut: Omit<Shortcut, 'id'>): Promise<void>;
|
||||
// (undocumented)
|
||||
get(): Shortcut[];
|
||||
// (undocumented)
|
||||
getColor(url: string): string;
|
||||
// (undocumented)
|
||||
remove(id: string): Promise<void>;
|
||||
// (undocumented)
|
||||
shortcut$(): ObservableImpl<Shortcut[]>;
|
||||
shortcut$(): Observable_2<Shortcut[]>;
|
||||
// (undocumented)
|
||||
update(shortcut: Shortcut): Promise<void>;
|
||||
}
|
||||
@@ -43,6 +45,7 @@ export type Shortcut = {
|
||||
// @public (undocumented)
|
||||
export interface ShortcutApi {
|
||||
add(shortcut: Omit<Shortcut, 'id'>): Promise<void>;
|
||||
get(): Shortcut[];
|
||||
getColor(url: string): string;
|
||||
remove(id: string): Promise<void>;
|
||||
shortcut$(): Observable<Shortcut[]>;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import PlayListAddIcon from '@material-ui/icons/PlaylistAdd';
|
||||
import { ShortcutItem } from './ShortcutItem';
|
||||
@@ -39,9 +39,7 @@ export interface ShortcutsProps {
|
||||
|
||||
export const Shortcuts = (props: ShortcutsProps) => {
|
||||
const shortcutApi = useApi(shortcutsApiRef);
|
||||
const shortcuts = useObservable(
|
||||
useMemo(() => shortcutApi.shortcut$(), [shortcutApi]),
|
||||
);
|
||||
const shortcuts = useObservable(shortcutApi.shortcut$(), shortcutApi.get());
|
||||
const [anchorEl, setAnchorEl] = React.useState<Element | undefined>();
|
||||
const loading = Boolean(!shortcuts);
|
||||
|
||||
|
||||
@@ -27,17 +27,22 @@ describe('LocalStoredShortcuts', () => {
|
||||
);
|
||||
const shortcut: Shortcut = { id: 'id', title: 'title', url: '/url' };
|
||||
|
||||
await shortcutApi.add(shortcut);
|
||||
const observerNextHandler = jest.fn();
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
const subscription = shortcutApi.shortcut$().subscribe(data => {
|
||||
expect(data).toEqual(
|
||||
expect.arrayContaining([{ ...shortcut, id: expect.anything() }]),
|
||||
);
|
||||
subscription.unsubscribe();
|
||||
resolve();
|
||||
shortcutApi.shortcut$().subscribe({
|
||||
next: data => {
|
||||
observerNextHandler(data);
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
shortcutApi.add(shortcut);
|
||||
});
|
||||
|
||||
expect(observerNextHandler).toHaveBeenCalledTimes(1);
|
||||
expect(observerNextHandler).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([{ ...shortcut, id: expect.anything() }]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should add shortcuts with ids', async () => {
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
import { pageTheme } from '@backstage/theme';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { ShortcutApi } from './ShortcutApi';
|
||||
import { Shortcut } from '../types';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import Observable from 'zen-observable';
|
||||
|
||||
/**
|
||||
* Implementation of the ShortcutApi that uses the StorageApi to store shortcuts.
|
||||
@@ -28,7 +28,15 @@ export class LocalStoredShortcuts implements ShortcutApi {
|
||||
constructor(private readonly storageApi: StorageApi) {}
|
||||
|
||||
shortcut$() {
|
||||
return this.observable;
|
||||
return Observable.from(this.storageApi.observe$<Shortcut[]>('items')).map(
|
||||
snapshot => snapshot.value ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
get() {
|
||||
return Array.from(
|
||||
this.storageApi.snapshot<Shortcut[]>('items').value ?? [],
|
||||
).sort((a, b) => (a.title >= b.title ? 1 : -1));
|
||||
}
|
||||
|
||||
async add(shortcut: Omit<Shortcut, 'id'>) {
|
||||
@@ -36,14 +44,12 @@ export class LocalStoredShortcuts implements ShortcutApi {
|
||||
shortcuts.push({ ...shortcut, id: uuid() });
|
||||
|
||||
await this.storageApi.set('items', shortcuts);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
const shortcuts = this.get().filter(s => s.id !== id);
|
||||
|
||||
await this.storageApi.set('items', shortcuts);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
async update(shortcut: Shortcut) {
|
||||
@@ -51,7 +57,6 @@ export class LocalStoredShortcuts implements ShortcutApi {
|
||||
shortcuts.push(shortcut);
|
||||
|
||||
await this.storageApi.set('items', shortcuts);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
getColor(url: string) {
|
||||
@@ -63,33 +68,8 @@ export class LocalStoredShortcuts implements ShortcutApi {
|
||||
return pageTheme[theme].colors[0];
|
||||
}
|
||||
|
||||
private subscribers = new Set<
|
||||
ZenObservable.SubscriptionObserver<Shortcut[]>
|
||||
>();
|
||||
|
||||
private readonly observable = new ObservableImpl<Shortcut[]>(subscriber => {
|
||||
subscriber.next(this.get());
|
||||
this.subscribers.add(subscriber);
|
||||
|
||||
return () => {
|
||||
this.subscribers.delete(subscriber);
|
||||
};
|
||||
});
|
||||
|
||||
private readonly THEME_MAP: Record<string, keyof typeof pageTheme> = {
|
||||
catalog: 'home',
|
||||
docs: 'documentation',
|
||||
};
|
||||
|
||||
private get() {
|
||||
return Array.from(
|
||||
this.storageApi.snapshot<Shortcut[]>('items').value ?? [],
|
||||
).sort((a, b) => (a.title >= b.title ? 1 : -1));
|
||||
}
|
||||
|
||||
private notify() {
|
||||
for (const subscription of this.subscribers) {
|
||||
subscription.next(this.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ export interface ShortcutApi {
|
||||
*/
|
||||
shortcut$(): Observable<Shortcut[]>;
|
||||
|
||||
/**
|
||||
* Returns an immediate snapshot of shortcuts, sorted by title
|
||||
*/
|
||||
get(): Shortcut[];
|
||||
|
||||
/**
|
||||
* Generates a unique id for the shortcut and then saves it.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user