Merge pull request #9669 from backstage/rugvip/starred
catalog-react: move DefaultStarredEntitiesApi to catalog plugin + fixes
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
**BREAKING**: Moved **DefaultStarredEntitiesApi** to `@backstage/plugin-catalog`. If you were using this in tests, you can use the new `MockStarredEntitiesApi` from `@backstage/plugin-catalog-react` instead.
|
||||
|
||||
Fixed a risky behavior where `DefaultStarredEntitiesApi` forwarded values to observers that were later mutated.
|
||||
|
||||
Removed the `isStarred` method from `DefaultStarredEntitiesApi`, as it is not part of the `StarredEntitiesApi`.
|
||||
@@ -16,15 +16,14 @@
|
||||
|
||||
import { EntityLayout } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
DefaultStarredEntitiesApi,
|
||||
EntityProvider,
|
||||
starredEntitiesApiRef,
|
||||
MockStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { githubActionsApiRef } from '@backstage/plugin-github-actions';
|
||||
import { permissionApiRef } from '@backstage/plugin-permission-react';
|
||||
import {
|
||||
MockPermissionApi,
|
||||
MockStorageApi,
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -59,12 +58,7 @@ describe('EntityPage Test', () => {
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[githubActionsApiRef, mockedApi],
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[permissionApiRef, mockPermissionApi],
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -22,11 +22,13 @@ import {
|
||||
configApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { CatalogTableRow } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
CatalogTableRow,
|
||||
DefaultStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
|
||||
@@ -24,7 +24,6 @@ import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { StyleRules } from '@material-ui/core/styles/withStyles';
|
||||
import { SystemEntity } from '@backstage/catalog-model';
|
||||
import { TableColumn } from '@backstage/core-components';
|
||||
@@ -138,17 +137,6 @@ export type DefaultEntityFilters = {
|
||||
text?: EntityTextFilter;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class DefaultStarredEntitiesApi implements StarredEntitiesApi {
|
||||
constructor(opts: { storageApi: StorageApi });
|
||||
// (undocumented)
|
||||
isStarred(entityRef: string): boolean;
|
||||
// (undocumented)
|
||||
starredEntitie$(): Observable<Set<string>>;
|
||||
// (undocumented)
|
||||
toggleStarred(entityRef: string): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type EntityFilter = {
|
||||
getCatalogFilters?: () => Record<
|
||||
@@ -488,6 +476,14 @@ export const MockEntityListContextProvider: ({
|
||||
value?: Partial<EntityListContextProps<DefaultEntityFilters>> | undefined;
|
||||
}>) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export class MockStarredEntitiesApi implements StarredEntitiesApi {
|
||||
// (undocumented)
|
||||
starredEntitie$(): Observable<Set<string>>;
|
||||
// (undocumented)
|
||||
toggleStarred(entityRef: string): Promise<void>;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function reduceCatalogFilters(
|
||||
filters: EntityFilter[],
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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 { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { MockStorageApi } from '@backstage/test-utils';
|
||||
import { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi';
|
||||
import { performMigrationToTheNewBucket } from './migration';
|
||||
|
||||
jest.mock('./migration');
|
||||
|
||||
describe('DefaultStarredEntitiesApi', () => {
|
||||
let mockStorage: StorageApi;
|
||||
let starredEntitiesApi: DefaultStarredEntitiesApi;
|
||||
|
||||
const mockEntityRef = stringifyEntityRef({
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'mock',
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(performMigrationToTheNewBucket as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
mockStorage = MockStorageApi.create();
|
||||
starredEntitiesApi = new DefaultStarredEntitiesApi({
|
||||
storageApi: mockStorage,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should call migration', () => {
|
||||
expect(performMigrationToTheNewBucket).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleStarred', () => {
|
||||
it('should star unstarred entity', async () => {
|
||||
expect(starredEntitiesApi.isStarred(mockEntityRef)).toBe(false);
|
||||
|
||||
await starredEntitiesApi.toggleStarred(mockEntityRef);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntityRef)).toBe(true);
|
||||
});
|
||||
|
||||
it('should unstar starred entity', async () => {
|
||||
const bucket = mockStorage.forBucket('starredEntities');
|
||||
await bucket.set('entityRefs', ['component:default/mock']);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntityRef)).toBe(true);
|
||||
|
||||
await starredEntitiesApi.toggleStarred(mockEntityRef);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntityRef)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('starredEntities$', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
await new Promise<void>(resolve => {
|
||||
starredEntitiesApi.starredEntitie$().subscribe({
|
||||
next: (...args) => {
|
||||
handler(...args);
|
||||
|
||||
if (handler.mock.calls.length >= 2) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const bucket = mockStorage.forBucket('starredEntities');
|
||||
bucket.set('entityRefs', ['component:default/mock']).then();
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive updates', async () => {
|
||||
expect(handler).toBeCalledTimes(2);
|
||||
expect(handler).toBeCalledWith(new Set());
|
||||
expect(handler).toBeCalledWith(new Set(['component:default/mock']));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2021 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 { MockStarredEntitiesApi } from './MockStarredEntitiesApi';
|
||||
|
||||
describe('MockStarredEntitiesApi', () => {
|
||||
it('should toggle starred entities', async () => {
|
||||
const api = new MockStarredEntitiesApi();
|
||||
|
||||
const updates1 = new Array<Set<string>>();
|
||||
const sub1 = api
|
||||
.starredEntitie$()
|
||||
.subscribe(entities => updates1.push(entities));
|
||||
|
||||
api.toggleStarred('k:ns/e1');
|
||||
api.toggleStarred('k:ns/e2');
|
||||
|
||||
await Promise.resolve();
|
||||
expect(updates1).toEqual([
|
||||
new Set(),
|
||||
new Set(['k:ns/e1']),
|
||||
new Set(['k:ns/e1', 'k:ns/e2']),
|
||||
]);
|
||||
|
||||
const updates2 = new Array<Set<string>>();
|
||||
const sub2 = api
|
||||
.starredEntitie$()
|
||||
.subscribe(entities => updates2.push(entities));
|
||||
|
||||
api.toggleStarred('k:ns/e2');
|
||||
sub1.unsubscribe();
|
||||
api.toggleStarred('k:ns/e2');
|
||||
|
||||
await Promise.resolve();
|
||||
expect(updates1).toEqual([
|
||||
new Set(),
|
||||
new Set(['k:ns/e1']),
|
||||
new Set(['k:ns/e1', 'k:ns/e2']),
|
||||
new Set(['k:ns/e1']),
|
||||
]);
|
||||
expect(updates2).toEqual([
|
||||
new Set(['k:ns/e1', 'k:ns/e2']),
|
||||
new Set(['k:ns/e1']),
|
||||
new Set(['k:ns/e1', 'k:ns/e2']),
|
||||
]);
|
||||
sub2.unsubscribe();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Observable } from '@backstage/types';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import { StarredEntitiesApi } from './StarredEntitiesApi';
|
||||
|
||||
/**
|
||||
* An in-memory mock implementation of the StarredEntitiesApi.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class MockStarredEntitiesApi implements StarredEntitiesApi {
|
||||
private readonly starredEntities = new Set<string>();
|
||||
private readonly subscribers = new Set<
|
||||
ZenObservable.SubscriptionObserver<Set<string>>
|
||||
>();
|
||||
|
||||
private readonly observable = new ObservableImpl<Set<string>>(subscriber => {
|
||||
subscriber.next(new Set(this.starredEntities));
|
||||
|
||||
this.subscribers.add(subscriber);
|
||||
return () => {
|
||||
this.subscribers.delete(subscriber);
|
||||
};
|
||||
});
|
||||
|
||||
async toggleStarred(entityRef: string): Promise<void> {
|
||||
if (!this.starredEntities.delete(entityRef)) {
|
||||
this.starredEntities.add(entityRef);
|
||||
}
|
||||
|
||||
for (const subscription of this.subscribers) {
|
||||
subscription.next(new Set(this.starredEntities));
|
||||
}
|
||||
}
|
||||
|
||||
starredEntitie$(): Observable<Set<string>> {
|
||||
return this.observable;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi';
|
||||
export { starredEntitiesApiRef } from './StarredEntitiesApi';
|
||||
export type { StarredEntitiesApi } from './StarredEntitiesApi';
|
||||
export { MockStarredEntitiesApi } from './MockStarredEntitiesApi';
|
||||
|
||||
@@ -29,7 +29,7 @@ import qs from 'qs';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { DefaultStarredEntitiesApi, starredEntitiesApiRef } from '../apis';
|
||||
import { starredEntitiesApiRef, MockStarredEntitiesApi } from '../apis';
|
||||
import { EntityKindPicker, UserListPicker } from '../components';
|
||||
import { EntityKindFilter, EntityTypeFilter, UserListFilter } from '../filters';
|
||||
import { UserListFilterKind } from '../types';
|
||||
@@ -95,12 +95,7 @@ const wrapper = ({
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[storageApiRef, MockStorageApi.create()],
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({
|
||||
storageApi: MockStorageApi.create(),
|
||||
}),
|
||||
],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
]}
|
||||
>
|
||||
<EntityListProvider>
|
||||
|
||||
@@ -15,15 +15,18 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { MockStorageApi, TestApiProvider } from '@backstage/test-utils';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { DefaultStarredEntitiesApi, starredEntitiesApiRef } from '../apis';
|
||||
import {
|
||||
starredEntitiesApiRef,
|
||||
StarredEntitiesApi,
|
||||
MockStarredEntitiesApi,
|
||||
} from '../apis';
|
||||
import { useStarredEntities } from './useStarredEntities';
|
||||
|
||||
describe('useStarredEntities', () => {
|
||||
let mockStorage: StorageApi;
|
||||
let mockApi: StarredEntitiesApi;
|
||||
let wrapper: React.ComponentType;
|
||||
|
||||
const mockEntity: Entity = {
|
||||
@@ -44,22 +47,15 @@ describe('useStarredEntities', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockStorage = MockStorageApi.create();
|
||||
mockApi = new MockStarredEntitiesApi();
|
||||
wrapper = ({ children }: PropsWithChildren<{}>) => (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi: mockStorage }),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<TestApiProvider apis={[[starredEntitiesApiRef, mockApi]]}>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an empty set for when there is no items in storage', async () => {
|
||||
it('should return an empty set', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useStarredEntities(),
|
||||
{ wrapper },
|
||||
@@ -70,10 +66,11 @@ describe('useStarredEntities', () => {
|
||||
expect(result.current.starredEntities.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should return a set with the current items when there are items in storage', async () => {
|
||||
it('should return a set with the current items', async () => {
|
||||
const expectedIds = ['i', 'am', 'some', 'test', 'ids'];
|
||||
const store = mockStorage?.forBucket('starredEntities');
|
||||
await store?.set('entityRefs', expectedIds);
|
||||
for (const id of expectedIds) {
|
||||
mockApi.toggleStarred(id);
|
||||
}
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useStarredEntities(),
|
||||
|
||||
@@ -13,10 +13,13 @@ import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
import { IndexableDocument } from '@backstage/search-common';
|
||||
import { InfoCardVariants } from '@backstage/core-components';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { Overrides } from '@material-ui/core/styles/overrides';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { StarredEntitiesApi } from '@backstage/plugin-catalog-react';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { StyleRules } from '@material-ui/core/styles/withStyles';
|
||||
import { TableColumn } from '@backstage/core-components';
|
||||
import { TableProps } from '@backstage/core-components';
|
||||
@@ -170,6 +173,15 @@ export interface DefaultCatalogPageProps {
|
||||
initiallySelectedFilter?: UserListFilterKind;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class DefaultStarredEntitiesApi implements StarredEntitiesApi {
|
||||
constructor(opts: { storageApi: StorageApi });
|
||||
// (undocumented)
|
||||
starredEntitie$(): Observable<Set<string>>;
|
||||
// (undocumented)
|
||||
toggleStarred(entityRef: string): Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface DependencyOfComponentsCardProps {
|
||||
// (undocumented)
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"@backstage/plugin-catalog-react": "^0.7.0",
|
||||
"@backstage/search-common": "^0.2.4",
|
||||
"@backstage/theme": "^0.2.15",
|
||||
"@backstage/types": "^0.1.2",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
@@ -51,7 +52,8 @@
|
||||
"lodash": "^4.17.21",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
"react-use": "^17.2.4",
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2021 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 { MockStorageApi } from '@backstage/test-utils';
|
||||
import { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi';
|
||||
import { performMigrationToTheNewBucket } from './migration';
|
||||
|
||||
jest.mock('./migration');
|
||||
|
||||
function getStarred(api: DefaultStarredEntitiesApi) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const subscription = api.starredEntitie$().subscribe({
|
||||
next(starred) {
|
||||
resolve(starred);
|
||||
subscription.unsubscribe();
|
||||
},
|
||||
error: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('DefaultStarredEntitiesApi', () => {
|
||||
beforeEach(() => {
|
||||
(performMigrationToTheNewBucket as jest.Mock).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should call migration', () => {
|
||||
const api = new DefaultStarredEntitiesApi({
|
||||
storageApi: MockStorageApi.create(),
|
||||
});
|
||||
expect(performMigrationToTheNewBucket).toBeCalledTimes(1);
|
||||
expect(api).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should notify and toggle starred entities', async () => {
|
||||
const entityRef = 'component:default/mock';
|
||||
|
||||
const storageApi = MockStorageApi.create();
|
||||
const storageBucket = storageApi.forBucket('starredEntities');
|
||||
const api = new DefaultStarredEntitiesApi({ storageApi });
|
||||
|
||||
const values = new Array<Set<string>>();
|
||||
api.starredEntitie$().subscribe({
|
||||
next: value => {
|
||||
values.push(value);
|
||||
},
|
||||
});
|
||||
|
||||
await expect(getStarred(api)).resolves.toEqual(new Set());
|
||||
|
||||
await api.toggleStarred(entityRef);
|
||||
await expect(getStarred(api)).resolves.toEqual(new Set([entityRef]));
|
||||
expect(storageBucket.snapshot('entityRefs')).toEqual(
|
||||
expect.objectContaining({ presence: 'present', value: [entityRef] }),
|
||||
);
|
||||
|
||||
await api.toggleStarred(entityRef);
|
||||
await expect(getStarred(api)).resolves.toEqual(new Set());
|
||||
expect(storageBucket.snapshot('entityRefs')).toEqual(
|
||||
expect.objectContaining({ presence: 'present', value: [] }),
|
||||
);
|
||||
|
||||
expect(values).toEqual([new Set(), new Set([entityRef]), new Set()]);
|
||||
});
|
||||
|
||||
it('should read starred entities from storage', async () => {
|
||||
const entityRef = 'component:default/mock';
|
||||
|
||||
const storageApi = MockStorageApi.create();
|
||||
const storageBucket = storageApi.forBucket('starredEntities');
|
||||
storageBucket.set('entityRefs', [entityRef]);
|
||||
const api = new DefaultStarredEntitiesApi({ storageApi });
|
||||
|
||||
await expect(getStarred(api)).resolves.toEqual(new Set([entityRef]));
|
||||
});
|
||||
});
|
||||
+3
-7
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { StarredEntitiesApi } from '@backstage/plugin-catalog-react';
|
||||
import { Observable } from '@backstage/types';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import { performMigrationToTheNewBucket } from './migration';
|
||||
import { StarredEntitiesApi } from './StarredEntitiesApi';
|
||||
|
||||
/**
|
||||
* Default implementation of the StarredEntitiesApi that is backed by the StorageApi.
|
||||
@@ -64,17 +64,13 @@ export class DefaultStarredEntitiesApi implements StarredEntitiesApi {
|
||||
return this.observable;
|
||||
}
|
||||
|
||||
isStarred(entityRef: string): boolean {
|
||||
return this.starredEntities.has(entityRef);
|
||||
}
|
||||
|
||||
private readonly subscribers = new Set<
|
||||
ZenObservable.SubscriptionObserver<Set<string>>
|
||||
>();
|
||||
|
||||
private readonly observable = new ObservableImpl<Set<string>>(subscriber => {
|
||||
// forward the the latest value
|
||||
subscriber.next(this.starredEntities);
|
||||
subscriber.next(new Set(this.starredEntities));
|
||||
|
||||
this.subscribers.add(subscriber);
|
||||
return () => {
|
||||
@@ -84,7 +80,7 @@ export class DefaultStarredEntitiesApi implements StarredEntitiesApi {
|
||||
|
||||
private notifyChanges() {
|
||||
for (const subscription of this.subscribers) {
|
||||
subscription.next(this.starredEntities);
|
||||
subscription.next(new Set(this.starredEntities));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export * from './StarredEntitiesApi';
|
||||
@@ -29,9 +29,9 @@ import {
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
starredEntitiesApiRef,
|
||||
MockStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
mockBreakpoint,
|
||||
@@ -141,10 +141,7 @@ describe('DefaultCatalogPage', () => {
|
||||
[catalogApiRef, catalogApi],
|
||||
[identityApiRef, identityApi],
|
||||
[storageApiRef, storageApi],
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi }),
|
||||
],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -22,16 +22,12 @@ import {
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import {
|
||||
entityRouteRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
MockEntityListContextProvider,
|
||||
starredEntitiesApiRef,
|
||||
UserListFilter,
|
||||
MockStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
MockStorageApi,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { CatalogTable } from './CatalogTable';
|
||||
@@ -57,7 +53,7 @@ const entities: Entity[] = [
|
||||
describe('CatalogTable component', () => {
|
||||
const mockApis = TestApiRegistry.from([
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
|
||||
new MockStarredEntitiesApi(),
|
||||
]);
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -21,15 +21,14 @@ import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
AsyncEntityProvider,
|
||||
catalogApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
EntityProvider,
|
||||
entityRouteRef,
|
||||
starredEntitiesApiRef,
|
||||
MockStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { permissionApiRef } from '@backstage/plugin-permission-react';
|
||||
import {
|
||||
MockPermissionApi,
|
||||
MockStorageApi,
|
||||
renderInTestApp,
|
||||
TestApiRegistry,
|
||||
} from '@backstage/test-utils';
|
||||
@@ -48,10 +47,7 @@ const mockEntity = {
|
||||
const mockApis = TestApiRegistry.from(
|
||||
[catalogApiRef, {} as CatalogApi],
|
||||
[alertApiRef, {} as AlertApi],
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
|
||||
],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
[permissionApiRef, new MockPermissionApi()],
|
||||
);
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './apis';
|
||||
|
||||
export * from './components/AboutCard';
|
||||
export * from './components/CatalogKindHeader';
|
||||
export * from './components/CatalogSearchResultListItem';
|
||||
|
||||
@@ -19,7 +19,6 @@ import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
catalogApiRef,
|
||||
catalogRouteRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
@@ -33,6 +32,7 @@ import {
|
||||
fetchApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { DefaultStarredEntitiesApi } from './apis';
|
||||
import { AboutCardProps } from './components/AboutCard';
|
||||
import { DefaultCatalogPageProps } from './components/CatalogPage';
|
||||
import { DependencyOfComponentsCardProps } from './components/DependencyOfComponentsCard';
|
||||
|
||||
@@ -13,40 +13,23 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
renderInTestApp,
|
||||
TestApiProvider,
|
||||
MockStorageApi,
|
||||
} from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import {
|
||||
starredEntitiesApiRef,
|
||||
MockStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { Content } from './Content';
|
||||
|
||||
describe('StarredEntitiesContent', () => {
|
||||
it('should render list of tools', async () => {
|
||||
const mockStorageApi = MockStorageApi.create();
|
||||
await mockStorageApi
|
||||
.forBucket('starredEntities')
|
||||
.set('entityRefs', [
|
||||
'component:default/mock-starred-entity',
|
||||
'component:default/mock-starred-entity-2',
|
||||
]);
|
||||
const mockedApi = new MockStarredEntitiesApi();
|
||||
mockedApi.toggleStarred('component:default/mock-starred-entity');
|
||||
mockedApi.toggleStarred('component:default/mock-starred-entity-2');
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({
|
||||
storageApi: mockStorageApi,
|
||||
}),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<TestApiProvider apis={[[starredEntitiesApiRef, mockedApi]]}>
|
||||
<Content />
|
||||
</TestApiProvider>,
|
||||
{
|
||||
|
||||
@@ -22,8 +22,8 @@ import {
|
||||
} from '@backstage/test-utils';
|
||||
import {
|
||||
starredEntitiesApiRef,
|
||||
MockStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React, { ComponentType } from 'react';
|
||||
@@ -44,14 +44,7 @@ export default {
|
||||
(Story: ComponentType<{}>) =>
|
||||
wrapInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({
|
||||
storageApi: mockStorageApi,
|
||||
}),
|
||||
],
|
||||
]}
|
||||
apis={[[starredEntitiesApiRef, new MockStarredEntitiesApi()]]}
|
||||
>
|
||||
<Story />
|
||||
</TestApiProvider>,
|
||||
|
||||
@@ -25,8 +25,8 @@ import { wrapInTestApp, TestApiProvider, MockStorageApi} from '@backstage/test-u
|
||||
import { Content, Page, InfoCard } from '@backstage/core-components';
|
||||
import {
|
||||
starredEntitiesApiRef,
|
||||
MockStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
DefaultStarredEntitiesApi
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
HomePageSearchBar,
|
||||
@@ -57,9 +57,7 @@ export default {
|
||||
apis={[
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({
|
||||
storageApi: mockStorageApi,
|
||||
}),
|
||||
new MockStarredEntitiesApi(),
|
||||
],
|
||||
[searchApiRef, { query: () => Promise.resolve({ results: [] }) }],
|
||||
]}
|
||||
@@ -153,4 +151,3 @@ export const DefaultTemplate = () => {
|
||||
</SearchContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,16 +20,12 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import {
|
||||
catalogApiRef,
|
||||
starredEntitiesApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
MockStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
import { scaffolderApiRef, ScaffolderClient } from '../src';
|
||||
import { ScaffolderPage } from '../src/plugin';
|
||||
import {
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
|
||||
import { CatalogEntityPage } from '@backstage/plugin-catalog';
|
||||
|
||||
createDevApp()
|
||||
@@ -44,8 +40,8 @@ createDevApp()
|
||||
})
|
||||
.registerApi({
|
||||
api: starredEntitiesApiRef,
|
||||
deps: { storageApi: storageApiRef },
|
||||
factory: ({ storageApi }) => new DefaultStarredEntitiesApi({ storageApi }),
|
||||
deps: {},
|
||||
factory: () => new MockStarredEntitiesApi(),
|
||||
})
|
||||
.registerApi({
|
||||
api: scaffolderApiRef,
|
||||
|
||||
@@ -23,8 +23,8 @@ import {
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
starredEntitiesApiRef,
|
||||
MockStarredEntitiesApi,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
MockStorageApi,
|
||||
@@ -73,7 +73,7 @@ describe('TechDocs Home', () => {
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[configApiRef, configApi],
|
||||
[storageApiRef, storageApi],
|
||||
[starredEntitiesApiRef, new DefaultStarredEntitiesApi({ storageApi })],
|
||||
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
|
||||
);
|
||||
|
||||
it('should render a TechDocs home page', async () => {
|
||||
|
||||
Reference in New Issue
Block a user