From 90f253aaf89bf220993d082a20c8c5e9d078fc21 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 9 Jun 2020 15:20:38 +0200 Subject: [PATCH 1/8] feat(catalog/star): Ability to star items in the catalog table --- .../components/CatalogPage/CatalogPage.tsx | 16 ++++++++-- .../catalog/src/hooks/useStarredEntites.ts | 15 ++++++++- .../src/hooks/useStarredEntities.test.tsx | 32 ++++++++++++++++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 859449c715..d7c8825c9b 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -29,6 +29,8 @@ import { import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; import { Button, makeStyles, Typography, Link } from '@material-ui/core'; import GitHub from '@material-ui/icons/GitHub'; +import StarOutline from '@material-ui/icons/StarBorder'; +import Star from '@material-ui/icons/Star'; import React, { FC, useCallback, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { useAsync } from 'react-use'; @@ -60,13 +62,14 @@ const useStyles = makeStyles(theme => ({ const CatalogPage: FC<{}> = () => { const catalogApi = useApi(catalogApiRef); - const { starredEntities } = useStarredEntities(); + const { starredEntities, toggleStarredEntity } = useStarredEntities(); const [selectedFilter, setSelectedFilter] = useState( defaultFilter, ); + const { value, error, loading } = useAsync( () => dataResolvers[selectedFilter.id]({ catalogApi, starredEntities }), - [selectedFilter.id], + [selectedFilter.id, starredEntities.size], ); const onFilterSelected = useCallback( @@ -89,6 +92,15 @@ const CatalogPage: FC<{}> = () => { hidden: location ? location?.type !== 'github' : true, }; }, + (rowData: Component) => { + return { + icon: starredEntities.has(rowData.metadata.name) ? Star : StarOutline, + toolTip: `${ + starredEntities.has(rowData.metadata.name) ? 'Unstar' : 'Star' + } ${rowData.metadata.name}`, + onClick: () => toggleStarredEntity(rowData.metadata.name), + }; + }, ]; // TODO: replace me with the proper tabs implemntation diff --git a/plugins/catalog/src/hooks/useStarredEntites.ts b/plugins/catalog/src/hooks/useStarredEntites.ts index 631991e9b0..3d1f902583 100644 --- a/plugins/catalog/src/hooks/useStarredEntites.ts +++ b/plugins/catalog/src/hooks/useStarredEntites.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { useApi, storageApiRef } from '@backstage/core'; import { useObservable } from 'react-use'; @@ -37,7 +37,20 @@ export const useStarredEntities = () => { } }, [observedItems?.newValue]); + const toggleStarredEntity = useCallback( + (entity: string) => { + if (starredEntities.has(entity)) { + starredEntities.delete(entity); + } else { + starredEntities.add(entity); + } + + settingsStore.set('starredEntities', Array.from(starredEntities)); + }, + [starredEntities, settingsStore], + ); return { starredEntities, + toggleStarredEntity, }; }; diff --git a/plugins/catalog/src/hooks/useStarredEntities.test.tsx b/plugins/catalog/src/hooks/useStarredEntities.test.tsx index 49890b7238..8127d71d50 100644 --- a/plugins/catalog/src/hooks/useStarredEntities.test.tsx +++ b/plugins/catalog/src/hooks/useStarredEntities.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react-hooks'; import { useStarredEntities } from './useStarredEntites'; import { ApiProvider, @@ -77,4 +77,34 @@ describe('useStarredEntities', () => { expect(result.current.starredEntities.size).toBe(1); expect(result.current.starredEntities.has('something')).toBeTruthy(); }); + + it('should write new entries to the local store when adding a togglging entity', async () => { + const store = mockStorage?.forBucket('settings'); + + await store?.set('starredEntities', ['something1']); + + const { result } = renderHook(() => useStarredEntities(), { wrapper }); + + act(() => { + result.current.toggleStarredEntity('something2'); + }); + + expect(result.current.starredEntities.has('something2')).toBeTruthy(); + expect(result.current.starredEntities.has('something1')).toBeTruthy(); + }); + + it('should remove an existing entity when toggling entries', async () => { + const store = mockStorage?.forBucket('settings'); + + await store?.set('starredEntities', ['something1', 'something2']); + + const { result } = renderHook(() => useStarredEntities(), { wrapper }); + + act(() => { + result.current.toggleStarredEntity('something2'); + }); + + expect(result.current.starredEntities.has('something2')).toBeFalsy(); + expect(result.current.starredEntities.has('something1')).toBeTruthy(); + }); }); From 7d0f8a2aace5aeb39934ffdf6b9f2a49b52dee62 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 10 Jun 2020 00:12:02 +0200 Subject: [PATCH 2/8] chore(catalog/star): reworking how the starring works, it now stores uri sort of references for entities --- .../components/CatalogPage/CatalogPage.tsx | 15 ++++++---- .../catalog/src/hooks/useStarredEntites.ts | 30 +++++++++++++++---- .../src/hooks/useStarredEntities.test.tsx | 9 ++++++ 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 2c59b3f319..426b3f8470 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -64,7 +64,11 @@ const useStyles = makeStyles(theme => ({ const CatalogPage: FC<{}> = () => { const catalogApi = useApi(catalogApiRef); - const { starredEntities, toggleStarredEntity } = useStarredEntities(); + const { + starredEntities, + toggleStarredEntity, + isStarredEntity, + } = useStarredEntities(); const [selectedFilter, setSelectedFilter] = useState( defaultFilter, ); @@ -118,12 +122,11 @@ const CatalogPage: FC<{}> = () => { }; }, (rowData: Component) => { + const isStarred = isStarredEntity(rowData); return { - icon: starredEntities.has(rowData.metadata.name) ? Star : StarOutline, - toolTip: `${ - starredEntities.has(rowData.metadata.name) ? 'Unstar' : 'Star' - } ${rowData.metadata.name}`, - onClick: () => toggleStarredEntity(rowData.metadata.name), + icon: isStarred ? Star : StarOutline, + tooltip: isStarred ? 'Remove from favorites' : 'Add to favorites', + onClick: () => toggleStarredEntity(rowData), }; }, ]; diff --git a/plugins/catalog/src/hooks/useStarredEntites.ts b/plugins/catalog/src/hooks/useStarredEntites.ts index 3d1f902583..4c8751d142 100644 --- a/plugins/catalog/src/hooks/useStarredEntites.ts +++ b/plugins/catalog/src/hooks/useStarredEntites.ts @@ -16,14 +16,21 @@ import { useState, useEffect, useCallback } from 'react'; import { useApi, storageApiRef } from '@backstage/core'; import { useObservable } from 'react-use'; +import { Component } from '../data/component'; + +const buildEntityKey = (component: Component) => + `entity:${component.kind}:${component.metadata.namespace ?? 'default'}:${ + component.metadata.name + }`; export const useStarredEntities = () => { const storageApi = useApi(storageApiRef); const settingsStore = storageApi.forBucket('settings'); - const rawStarredItems = settingsStore.get('starredEntities') ?? []; + const rawStarredEntityKeys = + settingsStore.get('starredEntities') ?? []; const [starredEntities, setStarredEntities] = useState( - new Set(rawStarredItems), + new Set(rawStarredEntityKeys), ); const observedItems = useObservable( @@ -38,19 +45,30 @@ export const useStarredEntities = () => { }, [observedItems?.newValue]); const toggleStarredEntity = useCallback( - (entity: string) => { - if (starredEntities.has(entity)) { - starredEntities.delete(entity); + (entity: Component) => { + const entityKey = buildEntityKey(entity); + if (starredEntities.has(entityKey)) { + starredEntities.delete(entityKey); } else { - starredEntities.add(entity); + starredEntities.add(entityKey); } settingsStore.set('starredEntities', Array.from(starredEntities)); }, [starredEntities, settingsStore], ); + + const isStarredEntity = useCallback( + (entity: Component) => { + const entityKey = buildEntityKey(entity); + return starredEntities.has(entityKey); + }, + [starredEntities], + ); + return { starredEntities, toggleStarredEntity, + isStarredEntity, }; }; diff --git a/plugins/catalog/src/hooks/useStarredEntities.test.tsx b/plugins/catalog/src/hooks/useStarredEntities.test.tsx index 8127d71d50..607318b45c 100644 --- a/plugins/catalog/src/hooks/useStarredEntities.test.tsx +++ b/plugins/catalog/src/hooks/useStarredEntities.test.tsx @@ -24,9 +24,18 @@ import { StorageApi, } from '@backstage/core'; import { MockErrorApi } from '@backstage/test-utils'; +import { Component } from '../data/component'; describe('useStarredEntities', () => { let mockStorage: StorageApi | undefined; + const mockEntity: Component = { + description: 'some mock description', + kind: 'Component', + name: 'mock', + metadata: { + name: 'mock', + }, + }; const wrapper: React.FC<{}> = ({ children }) => { return ( From 74b087daf8d82b4c62d48a75732160b591723cd8 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 10 Jun 2020 02:04:41 +0200 Subject: [PATCH 3/8] chore(catalog/star): adding a simple cache to stop flicker as a stopgap --- plugins/catalog/package.json | 1 + plugins/catalog/src/api/CatalogClient.ts | 13 ++++- .../components/CatalogPage/CatalogPage.tsx | 11 ++--- plugins/catalog/src/data/filters.ts | 10 ++-- .../catalog/src/hooks/useStarredEntites.ts | 8 ++-- .../src/hooks/useStarredEntities.test.tsx | 47 ++++++++++--------- yarn.lock | 12 +++++ 7 files changed, 61 insertions(+), 41 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 7b8c1ccc70..106f6c713a 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -30,6 +30,7 @@ "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "node-cache": "^5.1.1", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "^5.2.0", diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 9ea59aab48..32530f0b83 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -21,8 +21,10 @@ import { Location, LOCATION_ANNOTATION, } from '@backstage/catalog-model'; +import Cache from 'node-cache'; export class CatalogClient implements CatalogApi { + private cache: Cache; private apiOrigin: string; private basePath: string; constructor({ @@ -34,6 +36,7 @@ export class CatalogClient implements CatalogApi { }) { this.apiOrigin = apiOrigin; this.basePath = basePath; + this.cache = new Cache({ stdTTL: 10 }); } async getLocationById(id: String): Promise { const response = await fetch( @@ -48,6 +51,11 @@ export class CatalogClient implements CatalogApi { async getEntities( filter?: Record, ): Promise { + const cachedValue = this.cache.get( + `get:${JSON.stringify(filter)}`, + ); + if (cachedValue) return cachedValue; + let url = `${this.apiOrigin}${this.basePath}/entities`; if (filter) { url += '?'; @@ -65,8 +73,9 @@ export class CatalogClient implements CatalogApi { `Request failed with ${response.status} ${response.statusText}, ${payload}`, ); } - - return await response.json(); + const value = await response.json(); + this.cache.set(`get:${JSON.stringify(filter)}`, value); + return value; } async getEntityByName(name: string): Promise { const response = await fetch( diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 426b3f8470..37d83fcd29 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -26,7 +26,7 @@ import { SupportButton, useApi, } from '@backstage/core'; -import { LocationSpec } from '@backstage/catalog-model'; +import { LocationSpec, Entity } from '@backstage/catalog-model'; import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; import { Button, makeStyles, Typography, Link } from '@material-ui/core'; import GitHub from '@material-ui/icons/GitHub'; @@ -37,7 +37,6 @@ import React, { FC, useCallback, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { useAsync } from 'react-use'; import { catalogApiRef } from '../..'; -import { Component } from '../../data/component'; import { defaultFilter, filterGroups, dataResolvers } from '../../data/filters'; import { entityToComponent, findLocationForEntityMeta } from '../../data/utils'; import { @@ -74,7 +73,7 @@ const CatalogPage: FC<{}> = () => { ); const { value, error, loading } = useAsync( - () => dataResolvers[selectedFilter.id]({ catalogApi, starredEntities }), + () => dataResolvers[selectedFilter.id]({ catalogApi, isStarredEntity }), [selectedFilter.id, starredEntities.size], ); @@ -86,7 +85,7 @@ const CatalogPage: FC<{}> = () => { const styles = useStyles(); const actions = [ - (rowData: Component) => { + (rowData: Entity) => { const location = findLocationForEntityMeta(rowData.metadata); return { icon: GitHub, @@ -98,7 +97,7 @@ const CatalogPage: FC<{}> = () => { hidden: location ? location?.type !== 'github' : true, }; }, - (rowData: Component) => { + (rowData: Entity) => { const createEditLink = (location: LocationSpec): string => { switch (location.type) { case 'github': @@ -121,7 +120,7 @@ const CatalogPage: FC<{}> = () => { hidden: location ? location?.type !== 'github' : true, }; }, - (rowData: Component) => { + (rowData: Entity) => { const isStarred = isStarredEntity(rowData); return { icon: isStarred ? Star : StarOutline, diff --git a/plugins/catalog/src/data/filters.ts b/plugins/catalog/src/data/filters.ts index 1fcdfa27a2..a344a1cfb2 100644 --- a/plugins/catalog/src/data/filters.ts +++ b/plugins/catalog/src/data/filters.ts @@ -58,10 +58,10 @@ export const filterGroups: CatalogFilterGroup[] = [ type ResolverFunction = ({ catalogApi, - starredEntities, + isStarredEntity, }: { catalogApi: CatalogApi; - starredEntities: Set; + isStarredEntity: (entity: Entity) => boolean; }) => Promise; export const dataResolvers: Record = { @@ -69,12 +69,10 @@ export const dataResolvers: Record = { [FilterGroupItem.ALL]: async ({ catalogApi }) => { return catalogApi.getEntities(); }, - [FilterGroupItem.STARRED]: async ({ catalogApi, starredEntities }) => { + [FilterGroupItem.STARRED]: async ({ catalogApi, isStarredEntity }) => { const allEntities = await catalogApi.getEntities(); - return allEntities.filter(entity => - starredEntities.has(entity.metadata.name), - ); + return allEntities.filter(entity => isStarredEntity(entity)); }, }; diff --git a/plugins/catalog/src/hooks/useStarredEntites.ts b/plugins/catalog/src/hooks/useStarredEntites.ts index 4c8751d142..20f13f8e21 100644 --- a/plugins/catalog/src/hooks/useStarredEntites.ts +++ b/plugins/catalog/src/hooks/useStarredEntites.ts @@ -16,9 +16,9 @@ import { useState, useEffect, useCallback } from 'react'; import { useApi, storageApiRef } from '@backstage/core'; import { useObservable } from 'react-use'; -import { Component } from '../data/component'; +import { Entity } from '@backstage/catalog-model'; -const buildEntityKey = (component: Component) => +const buildEntityKey = (component: Entity) => `entity:${component.kind}:${component.metadata.namespace ?? 'default'}:${ component.metadata.name }`; @@ -45,7 +45,7 @@ export const useStarredEntities = () => { }, [observedItems?.newValue]); const toggleStarredEntity = useCallback( - (entity: Component) => { + (entity: Entity) => { const entityKey = buildEntityKey(entity); if (starredEntities.has(entityKey)) { starredEntities.delete(entityKey); @@ -59,7 +59,7 @@ export const useStarredEntities = () => { ); const isStarredEntity = useCallback( - (entity: Component) => { + (entity: Entity) => { const entityKey = buildEntityKey(entity); return starredEntities.has(entityKey); }, diff --git a/plugins/catalog/src/hooks/useStarredEntities.test.tsx b/plugins/catalog/src/hooks/useStarredEntities.test.tsx index 607318b45c..1ac9ac799d 100644 --- a/plugins/catalog/src/hooks/useStarredEntities.test.tsx +++ b/plugins/catalog/src/hooks/useStarredEntities.test.tsx @@ -24,19 +24,28 @@ import { StorageApi, } from '@backstage/core'; import { MockErrorApi } from '@backstage/test-utils'; -import { Component } from '../data/component'; +import { Entity } from '@backstage/catalog-model'; describe('useStarredEntities', () => { let mockStorage: StorageApi | undefined; - const mockEntity: Component = { - description: 'some mock description', + + const mockEntity: Entity = { + apiVersion: '1', kind: 'Component', - name: 'mock', metadata: { name: 'mock', }, }; + const secondMockEntity: Entity = { + apiVersion: '1', + kind: 'Component', + metadata: { + namespace: 'test', + name: 'mock2', + }, + }; + const wrapper: React.FC<{}> = ({ children }) => { return ( @@ -67,53 +76,45 @@ describe('useStarredEntities', () => { } }); it('should listen to changes when the storage is set elsewhere', async () => { - const store = mockStorage?.forBucket('settings'); - const { result, waitForNextUpdate } = renderHook( () => useStarredEntities(), { wrapper }, ); expect(result.current.starredEntities.size).toBe(0); - expect(result.current.starredEntities.has('something')).toBeFalsy(); + expect(result.current.isStarredEntity(mockEntity)).toBeFalsy(); // Make this happen after awaiting for the next update so we can // catch when the hook re-renders with the latest data - setTimeout(() => store?.set('starredEntities', ['something']), 1); + setTimeout(() => result.current.toggleStarredEntity(mockEntity), 1); await waitForNextUpdate(); expect(result.current.starredEntities.size).toBe(1); - expect(result.current.starredEntities.has('something')).toBeTruthy(); + expect(result.current.isStarredEntity(mockEntity)).toBeTruthy(); }); it('should write new entries to the local store when adding a togglging entity', async () => { - const store = mockStorage?.forBucket('settings'); - - await store?.set('starredEntities', ['something1']); - const { result } = renderHook(() => useStarredEntities(), { wrapper }); act(() => { - result.current.toggleStarredEntity('something2'); + result.current.toggleStarredEntity(mockEntity); }); - expect(result.current.starredEntities.has('something2')).toBeTruthy(); - expect(result.current.starredEntities.has('something1')).toBeTruthy(); + expect(result.current.isStarredEntity(mockEntity)).toBeTruthy(); + expect(result.current.isStarredEntity(secondMockEntity)).toBeFalsy(); }); it('should remove an existing entity when toggling entries', async () => { - const store = mockStorage?.forBucket('settings'); - - await store?.set('starredEntities', ['something1', 'something2']); - const { result } = renderHook(() => useStarredEntities(), { wrapper }); act(() => { - result.current.toggleStarredEntity('something2'); + result.current.toggleStarredEntity(mockEntity); + result.current.toggleStarredEntity(secondMockEntity); + result.current.toggleStarredEntity(mockEntity); }); - expect(result.current.starredEntities.has('something2')).toBeFalsy(); - expect(result.current.starredEntities.has('something1')).toBeTruthy(); + expect(result.current.isStarredEntity(mockEntity)).toBeFalsy(); + expect(result.current.isStarredEntity(secondMockEntity)).toBeTruthy(); }); }); diff --git a/yarn.lock b/yarn.lock index 2b7246b230..73d9ccfad1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6221,6 +6221,11 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone@2.x: + version "2.1.2" + resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + clone@^1.0.2: version "1.0.4" resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -13259,6 +13264,13 @@ nocache@2.1.0: resolved "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f" integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q== +node-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/node-cache/-/node-cache-5.1.1.tgz#5fcc887176b23bdcd19cd1461b9544d2d501e786" + integrity sha512-bJ9nH25Z51HG2QIu66K4dMVyMs6o8bNQpviDnXzG+O/gfNxPU9IpIig0j4pzlO707GcGZ6QA4rWhlRxjJsjnZw== + dependencies: + clone "2.x" + node-cleanup@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c" From 01868352cf99a4831ae1ffbef4128b705b8cd7da Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 10 Jun 2020 02:16:36 +0200 Subject: [PATCH 4/8] chore(catalog/star): fixing issues with unmocked deps --- .../catalog/src/components/CatalogFilter/StarredCount.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/catalog/src/components/CatalogFilter/StarredCount.test.tsx b/plugins/catalog/src/components/CatalogFilter/StarredCount.test.tsx index 57581e98f2..546d00e854 100644 --- a/plugins/catalog/src/components/CatalogFilter/StarredCount.test.tsx +++ b/plugins/catalog/src/components/CatalogFilter/StarredCount.test.tsx @@ -24,6 +24,8 @@ describe('Starred Count', () => { it('should render the count returned from the hook', async () => { jest.spyOn(Hooks, 'useStarredEntities').mockReturnValue({ starredEntities: new Set(['id1', 'id2', 'id3', 'id4']), + isStarredEntity: () => false, + toggleStarredEntity: () => undefined, }); const { findByText } = render(wrapInTestApp()); From 01d0f3ae80f21ee395a0ff49c1a8231cff34de14 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 10 Jun 2020 02:14:00 +0200 Subject: [PATCH 5/8] chore(msw): Added msw dependency (cherry picked from commit 1c73ca376bdf5525f6a5bb60787da68e8dd49498) --- plugins/catalog/package.json | 1 + yarn.lock | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 106f6c713a..4fc6aed0f8 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -49,6 +49,7 @@ "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", + "msw": "^0.19.0", "react-test-renderer": "^16.13.1" }, "files": [ diff --git a/yarn.lock b/yarn.lock index 73d9ccfad1..c75690a1fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2406,6 +2406,11 @@ dependencies: "@types/node" ">= 8" +"@open-draft/until@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" + integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== + "@reach/router@^1.2.1": version "1.3.3" resolved "https://registry.npmjs.org/@reach/router/-/router-1.3.3.tgz#58162860dce6c9449d49be86b0561b5ef46d80db" @@ -3476,6 +3481,11 @@ dependencies: "@types/express" "*" +"@types/cookie@^0.3.3": + version "0.3.3" + resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" + integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== + "@types/cookiejar@*": version "2.1.1" resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" @@ -6647,6 +6657,11 @@ cookie@0.4.0: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + cookiejar@^2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" @@ -9756,6 +9771,11 @@ graphql@15.0.0: resolved "https://registry.npmjs.org/graphql/-/graphql-15.0.0.tgz#042a5eb5e2506a2e2111ce41eb446a8e570b8be9" integrity sha512-ZyVO1xIF9F+4cxfkdhOJINM+51B06Friuv4M66W7HzUOeFd+vNzUn4vtswYINPi6sysjf1M2Ri/rwZALqgwbaQ== +graphql@^15.0.0: + version "15.1.0" + resolved "https://registry.npmjs.org/graphql/-/graphql-15.1.0.tgz#b93e28de805294ec08e1630d901db550cb8960a1" + integrity sha512-0TVyfOlCGhv/DBczQkJmwXOK6fjWkjzY3Pt7wY8i0gcYXq8aogG3weCsg48m72lywKSeOqedEHvVPOvZvSD51Q== + growly@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -9920,6 +9940,11 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +headers-utils@^1.1.3, headers-utils@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.0.tgz#5e10d1bc9d2bccf789547afca5b991a3167241e8" + integrity sha512-4/BMXcWrJErw7JpM87gF8MNEXcIMLzepYZjNRv/P9ctgupl2Ywa3u1PgHtNhSRq84bHH9Ndlkdy7bSi+bZ9I9A== + helmet-crossdomain@0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz#5f1fe5a836d0325f1da0a78eaa5fd8429078894e" @@ -13144,6 +13169,22 @@ ms@^2.0.0, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +msw@^0.19.0: + version "0.19.0" + resolved "https://registry.npmjs.org/msw/-/msw-0.19.0.tgz#fd37015787d40db82d243a2853be66c466675e72" + integrity sha512-1TpmJzJ+afBWTRNJYoeW8KwLQbCVlvvhw2u/eRuIYfel+bPqcut5NaSgo+Bi4C0Q/7M5wza00w1GEuOXQu6FCA== + dependencies: + "@open-draft/until" "^1.0.0" + "@types/cookie" "^0.3.3" + chalk "^4.0.0" + cookie "^0.4.1" + graphql "^15.0.0" + headers-utils "^1.1.9" + node-match-path "^0.4.2" + node-request-interceptor "^0.2.4" + statuses "^2.0.0" + yargs "^15.3.1" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -13358,6 +13399,11 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" +node-match-path@^0.4.2: + version "0.4.2" + resolved "https://registry.npmjs.org/node-match-path/-/node-match-path-0.4.2.tgz#30cc39510fa493bff03c3d0d2fff711c868ec457" + integrity sha512-wfde4FOC5A8RTSUVZ7pTpBV+dJsr2vVxT6374VrNam6wnnhx6EvwAwL/E/r3AW/YU6XkeZggF5xfBlu4a/ULBg== + node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" @@ -13398,6 +13444,14 @@ node-releases@^1.1.29, node-releases@^1.1.52: dependencies: semver "^6.3.0" +node-request-interceptor@^0.2.4: + version "0.2.4" + resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.2.4.tgz#f03a1b874823d0bea311a14280227707be946298" + integrity sha512-/htjDLmygBczT5qYPaSxfAEtMkc0LGuH6jqAP1o+TKfQh6yQfFyTtac25cpY8+pb4EawHljCLUN7dCeed9SdPA== + dependencies: + debug "^4.1.1" + headers-utils "^1.1.3" + nodemon@^2.0.2: version "2.0.4" resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz#55b09319eb488d6394aa9818148c0c2d1c04c416" @@ -17260,6 +17314,11 @@ static-extend@^0.1.1: resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +statuses@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.0.tgz#aa7b107e018eb33e08e8aee2e7337e762dda1028" + integrity sha512-w9jNUUQdpuVoYqXxnyOakhckBbOxRaoYqJscyIBYCS5ixyCnO7nQn7zBZvP9zf5QOPZcz2DLUpE3KsNPbJBOFA== + stealthy-require@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" From 27879655a7df360735edb66e97ab8a97f6fbe157 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 10 Jun 2020 13:53:55 +0200 Subject: [PATCH 6/8] chore(catalog/star): removing msw dependency, wrong branch --- plugins/catalog/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 4fc6aed0f8..106f6c713a 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -49,7 +49,6 @@ "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3", - "msw": "^0.19.0", "react-test-renderer": "^16.13.1" }, "files": [ From 983c119c78cc31f756079d6c7057f5d48cbb16b5 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 10 Jun 2020 13:57:17 +0200 Subject: [PATCH 7/8] chore(catalog/star): added a comment about why we are using a simple cache here --- plugins/catalog/src/api/CatalogClient.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 9b7928a275..39e6ca8360 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -24,6 +24,9 @@ import { import Cache from 'node-cache'; export class CatalogClient implements CatalogApi { + // TODO(blam): This cache is just temporary until we have GraphQL. + // And client side caching using things like React Apollo or Relay. + // There's a lot of loading states that cause flickering around the app which aren't needed. private cache: Cache; private apiOrigin: string; private basePath: string; From b619490fe0f57694fcd0687b1a7c32f38c1c9403 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 10 Jun 2020 13:58:50 +0200 Subject: [PATCH 8/8] chore(catalog/star): only set the cache if there are entries from the response --- plugins/catalog/src/api/CatalogClient.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 39e6ca8360..1d88a57ecc 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -77,7 +77,11 @@ export class CatalogClient implements CatalogApi { ); } const value = await response.json(); - this.cache.set(`get:${JSON.stringify(filter)}`, value); + + if (value?.length) { + this.cache.set(`get:${JSON.stringify(filter)}`, value); + } + return value; }