From e1417c7d95110a027cfd176a4ad43cfaf4902f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 26 Jun 2020 22:50:35 +0200 Subject: [PATCH 1/9] Move local concerns into the CatalogTabs --- .../components/CatalogPage/CatalogPage.tsx | 11 +-- .../components/CatalogPage/CatalogTabs.tsx | 72 +++++++++++++++---- plugins/catalog/src/data/filters.ts | 51 +------------ 3 files changed, 62 insertions(+), 72 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index be357dfed1..b7e97227e8 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -24,11 +24,7 @@ import Star from '@material-ui/icons/Star'; import StarOutline from '@material-ui/icons/StarBorder'; import React, { useCallback, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; -import { - EntityGroup, - filterGroups, - LabeledEntityType, -} from '../../data/filters'; +import { EntityGroup, filterGroups } from '../../data/filters'; import { findLocationForEntityMeta } from '../../data/utils'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { useStarredEntities } from '../../hooks/useStarredEntites'; @@ -107,16 +103,13 @@ const CatalogPageContents = () => { }, ]; - const onTabChanged = useCallback((type: LabeledEntityType) => { - setSelectedTab(type.label); - }, []); const onSidebarChanged = useCallback((filterItem: CatalogFilterItem) => { setSelectedSidebarItem(filterItem.label); }, []); return ( - + setSelectedTab(label)} /> diff --git a/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx b/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx index 1845404935..931874d2fc 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx @@ -14,11 +14,45 @@ * limitations under the License. */ -import React, { useCallback, useEffect } from 'react'; -import { HeaderTabs } from '@backstage/core'; -import { labeledEntityTypes, LabeledEntityType } from '../../data/filters'; -import { useEntityFilterGroup, FilterGroup } from '../../filter'; import { Entity } from '@backstage/catalog-model'; +import { HeaderTabs } from '@backstage/core'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { FilterGroup, useEntityFilterGroup } from '../../filter'; + +type ComponentType = + | 'service' + | 'website' + | 'library' + | 'documentation' + | 'other'; + +export type LabeledComponentType = { + id: ComponentType; + label: string; +}; + +const labeledEntityTypes: LabeledComponentType[] = [ + { + id: 'service', + label: 'Services', + }, + { + id: 'website', + label: 'Websites', + }, + { + id: 'library', + label: 'Libraries', + }, + { + id: 'documentation', + label: 'Documentation', + }, + { + id: 'other', + label: 'Other', + }, +]; const filterGroup: FilterGroup = { filters: Object.fromEntries( @@ -29,27 +63,39 @@ const filterGroup: FilterGroup = { ), }; +type OnChangeCallback = (item: { type: string; label: string }) => void; + type Props = { - onChange?: (type: LabeledEntityType) => void; + onChange?: OnChangeCallback; }; +/** + * The tabs at the top of the catalog list page, for component type filtering. + */ export const CatalogTabs = ({ onChange }: Props) => { const { setSelectedFilters } = useEntityFilterGroup('type', filterGroup, [ labeledEntityTypes[0].id, ]); + const [currentTabIndex, setCurrentTabIndex] = useState(0); - const onChangeFn = useCallback( + const onChangeRef = useRef(); + useEffect(() => { + onChangeRef.current = onChange; + }, [onChange]); + useEffect(() => { + const type = labeledEntityTypes[currentTabIndex]; + onChangeRef.current?.({ type: type.id, label: type.label }); + }, [currentTabIndex]); + + const switchTab = useCallback( (index: Number) => { const type = labeledEntityTypes[index as number]; setSelectedFilters([type.id]); - onChange?.(type); + setCurrentTabIndex(index as number); + onChangeRef.current?.({ type: type.id, label: type.label }); }, - [onChange, setSelectedFilters], + [setSelectedFilters], ); - useEffect(() => { - onChange?.(labeledEntityTypes[0]); - }, [onChange]); - - return ; + return ; }; diff --git a/plugins/catalog/src/data/filters.ts b/plugins/catalog/src/data/filters.ts index 890b267b4f..5ab77609ab 100644 --- a/plugins/catalog/src/data/filters.ts +++ b/plugins/catalog/src/data/filters.ts @@ -17,10 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import SettingsIcon from '@material-ui/icons/Settings'; import StarIcon from '@material-ui/icons/Star'; -import { - CatalogFilterGroup, - CatalogFilterItem, -} from '../components/CatalogFilter/CatalogFilter'; +import { CatalogFilterGroup } from '../components/CatalogFilter/CatalogFilter'; export enum EntityGroup { ALL = 'ALL', @@ -56,17 +53,6 @@ export const filterGroups: CatalogFilterGroup[] = [ }, ]; -export const getCatalogFilterItemByType = (filterType: EntityGroup) => { - for (const group of filterGroups) { - for (const filter of group.items) { - if (filter.id === filterType) { - return filter; - } - } - } - return null; -}; - type EntityFilter = (entity: Entity, options: EntityFilterOptions) => boolean; export type EntityFilterOptions = { @@ -79,38 +65,3 @@ export const entityFilters: Record = { [EntityGroup.ALL]: () => true, [EntityGroup.STARRED]: (e, { isStarred }) => isStarred(e), }; - -export const entityTypeFilter = (e: Entity, type: string) => - (e.spec as any)?.type === type; - -type EntityType = 'service' | 'website' | 'library' | 'documentation' | 'other'; - -export type LabeledEntityType = { - id: EntityType; - label: string; -}; - -export const labeledEntityTypes: LabeledEntityType[] = [ - { - id: 'service', - label: 'Services', - }, - { - id: 'website', - label: 'Websites', - }, - { - id: 'library', - label: 'Libraries', - }, - { - id: 'documentation', - label: 'Documentation', - }, - { - id: 'other', - label: 'Other', - }, -]; - -export const defaultFilter: CatalogFilterItem = filterGroups[0].items[0]; From d341129e05dbe422bbfce61fe25dee5dc594ec3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 26 Jun 2020 23:03:35 +0200 Subject: [PATCH 2/9] Made CatalogFilter accept dynamic onChange --- .../CatalogFilter/CatalogFilter.tsx | 26 ++++++++++++++----- .../components/CatalogPage/CatalogPage.tsx | 13 +++------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index 71956e2e22..2537b7fba0 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -26,7 +26,14 @@ import { Theme, Typography, } from '@material-ui/core'; -import React, { FC, useCallback, useMemo, useState, useEffect } from 'react'; +import React, { + FC, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { EntityFilterOptions, entityFilters, @@ -35,7 +42,7 @@ import { import { FilterGroup, useEntityFilterGroup } from '../../filter'; import { useStarredEntities } from '../../hooks/useStarredEntites'; -export type CatalogFilterItem = { +type CatalogFilterItem = { id: EntityGroup; label: string; icon?: IconComponent; @@ -73,9 +80,11 @@ const useStyles = makeStyles(theme => ({ }, })); +type OnChangeCallback = (item: { id: string; label: string }) => void; + type Props = { filterGroups: CatalogFilterGroup[]; - onChange?: (filterItem: CatalogFilterItem) => void; + onChange?: OnChangeCallback; initiallySelected?: EntityGroup; }; @@ -87,12 +96,17 @@ export const CatalogFilter = ({ const classes = useStyles(); const { currentFilter, setCurrentFilter, getFilterCount } = useFilter(); + const onChangeRef = useRef(); + useEffect(() => { + onChangeRef.current = onChange; + }, [onChange]); + const setCurrent = useCallback( (item: CatalogFilterItem) => { setCurrentFilter(item.id); - onChange?.(item); + onChangeRef.current?.(item); }, - [onChange, setCurrentFilter], + [setCurrentFilter], ); // Make one initial onChange to inform the surroundings about the selected @@ -101,7 +115,7 @@ export const CatalogFilter = ({ const items = filterGroups.flatMap(g => g.items); const item = items.find(i => i.id === initiallySelected) || items[0]; if (item) { - onChange?.(item); + onChangeRef.current?.(item); } // intentionally only happens on startup // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index b7e97227e8..ecee5cc729 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -22,16 +22,13 @@ import Edit from '@material-ui/icons/Edit'; import GitHub from '@material-ui/icons/GitHub'; import Star from '@material-ui/icons/Star'; import StarOutline from '@material-ui/icons/StarBorder'; -import React, { useCallback, useState } from 'react'; +import React, { useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { EntityGroup, filterGroups } from '../../data/filters'; import { findLocationForEntityMeta } from '../../data/utils'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { useStarredEntities } from '../../hooks/useStarredEntites'; -import { - CatalogFilter, - CatalogFilterItem, -} from '../CatalogFilter/CatalogFilter'; +import { CatalogFilter } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; import CatalogLayout from './CatalogLayout'; import { CatalogTabs } from './CatalogTabs'; @@ -103,10 +100,6 @@ const CatalogPageContents = () => { }, ]; - const onSidebarChanged = useCallback((filterItem: CatalogFilterItem) => { - setSelectedSidebarItem(filterItem.label); - }, []); - return ( setSelectedTab(label)} /> @@ -127,7 +120,7 @@ const CatalogPageContents = () => {
setSelectedSidebarItem(label)} initiallySelected={EntityGroup.OWNED} />
From 6a8e975c85244725f39f172cf0bfc3f187acb698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 27 Jun 2020 20:52:18 +0200 Subject: [PATCH 3/9] Move local concerns into CatalogTable --- packages/core/src/components/Table/Table.tsx | 44 ++++++------ packages/core/src/components/Table/index.ts | 2 +- .../CatalogFilter/CatalogFilter.tsx | 5 +- .../components/CatalogPage/CatalogPage.tsx | 61 +--------------- .../CatalogTable/CatalogTable.test.tsx | 14 ++-- .../components/CatalogTable/CatalogTable.tsx | 69 ++++++++++++++++--- 6 files changed, 94 insertions(+), 101 deletions(-) diff --git a/packages/core/src/components/Table/Table.tsx b/packages/core/src/components/Table/Table.tsx index c02aca5659..047a6b1990 100644 --- a/packages/core/src/components/Table/Table.tsx +++ b/packages/core/src/components/Table/Table.tsx @@ -14,18 +14,8 @@ * limitations under the License. */ -import React, { FC, forwardRef } from 'react'; -import MTable, { - MTableCell, - MTableHeader, - MTableToolbar, - MaterialTableProps, - Options, - Column, -} from 'material-table'; import { BackstageTheme } from '@backstage/theme'; -import { makeStyles, useTheme, Typography } from '@material-ui/core'; - +import { makeStyles, Typography, useTheme } from '@material-ui/core'; // Material-table is not using the standard icons available in in material-ui. https://github.com/mbrn/material-table/issues/51 import AddBox from '@material-ui/icons/AddBox'; import ArrowUpward from '@material-ui/icons/ArrowUpward'; @@ -42,6 +32,15 @@ import Remove from '@material-ui/icons/Remove'; import SaveAlt from '@material-ui/icons/SaveAlt'; import Search from '@material-ui/icons/Search'; import ViewColumn from '@material-ui/icons/ViewColumn'; +import MTable, { + Column, + MaterialTableProps, + MTableCell, + MTableHeader, + MTableToolbar, + Options, +} from 'material-table'; +import React, { forwardRef } from 'react'; const tableIcons = { Add: forwardRef((props, ref: React.Ref) => ( @@ -131,10 +130,10 @@ const useToolbarStyles = makeStyles(theme => ({ }, })); -const convertColumns = ( - columns: TableColumn[], +function convertColumns( + columns: TableColumn[], theme: BackstageTheme, -): TableColumn[] => { +): TableColumn[] { return columns.map(column => { const headerStyle: React.CSSProperties = {}; const cellStyle: React.CSSProperties = {}; @@ -150,25 +149,26 @@ const convertColumns = ( cellStyle, }; }); -}; +} -export interface TableColumn extends Column<{}> { +export interface TableColumn extends Column { highlight?: boolean; width?: string; } -export interface TableProps extends MaterialTableProps<{}> { - columns: TableColumn[]; +export interface TableProps + extends MaterialTableProps { + columns: TableColumn[]; subtitle?: string; } -export const Table: FC = ({ +export function Table({ columns, options, title, subtitle, ...props -}) => { +}: TableProps) { const cellClasses = useCellStyles(); const headerClasses = useHeaderStyles(); const toolbarClasses = useToolbarStyles(); @@ -183,7 +183,7 @@ export const Table: FC = ({ }; return ( - components={{ Cell: cellProps => ( @@ -211,4 +211,4 @@ export const Table: FC = ({ {...props} /> ); -}; +} diff --git a/packages/core/src/components/Table/index.ts b/packages/core/src/components/Table/index.ts index aeade2b24f..c10399dc59 100644 --- a/packages/core/src/components/Table/index.ts +++ b/packages/core/src/components/Table/index.ts @@ -15,5 +15,5 @@ */ export { Table } from './Table'; -export type { TableColumn } from './Table'; +export type { TableColumn, TableProps } from './Table'; export { SubvalueCell } from './SubvalueCell'; diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index 2537b7fba0..ea22198e6d 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -42,7 +42,7 @@ import { import { FilterGroup, useEntityFilterGroup } from '../../filter'; import { useStarredEntities } from '../../hooks/useStarredEntites'; -type CatalogFilterItem = { +export type CatalogFilterItem = { id: EntityGroup; label: string; icon?: IconComponent; @@ -88,6 +88,9 @@ type Props = { initiallySelected?: EntityGroup; }; +/** + * The main filter group in the sidebar, toggling owned/starred/all. + */ export const CatalogFilter = ({ filterGroups, onChange, diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index ecee5cc729..9626b695a2 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -14,20 +14,13 @@ * limitations under the License. */ -import { Entity, LocationSpec } from '@backstage/catalog-model'; import { Content, ContentHeader, SupportButton } from '@backstage/core'; import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; -import { Button, makeStyles, withStyles } from '@material-ui/core'; -import Edit from '@material-ui/icons/Edit'; -import GitHub from '@material-ui/icons/GitHub'; -import Star from '@material-ui/icons/Star'; -import StarOutline from '@material-ui/icons/StarBorder'; +import { Button, makeStyles } from '@material-ui/core'; import React, { useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import { EntityGroup, filterGroups } from '../../data/filters'; -import { findLocationForEntityMeta } from '../../data/utils'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; -import { useStarredEntities } from '../../hooks/useStarredEntites'; import { CatalogFilter } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; import CatalogLayout from './CatalogLayout'; @@ -45,61 +38,10 @@ const useStyles = makeStyles(theme => ({ const CatalogPageContents = () => { const styles = useStyles(); - const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const { loading, error, matchingEntities } = useFilteredEntities(); const [selectedTab, setSelectedTab] = useState(); const [selectedSidebarItem, setSelectedSidebarItem] = useState(); - const YellowStar = withStyles({ - root: { - color: '#f3ba37', - }, - })(Star); - - const actions = [ - (rowData: Entity) => { - const location = findLocationForEntityMeta(rowData.metadata); - return { - icon: GitHub, - tooltip: 'View on GitHub', - onClick: () => { - if (!location) return; - window.open(location.target, '_blank'); - }, - hidden: location?.type !== 'github', - }; - }, - (rowData: Entity) => { - const createEditLink = (location: LocationSpec): string => { - switch (location.type) { - case 'github': - return location.target.replace('/blob/', '/edit/'); - default: - return location.target; - } - }; - const location = findLocationForEntityMeta(rowData.metadata); - return { - icon: Edit, - tooltip: 'Edit', - iconProps: { size: 'small' }, - onClick: () => { - if (!location) return; - window.open(createEditLink(location), '_blank'); - }, - hidden: location?.type !== 'github', - }; - }, - (rowData: Entity) => { - const isStarred = isStarredEntity(rowData); - return { - icon: isStarred ? YellowStar : StarOutline, - tooltip: isStarred ? 'Remove from favorites' : 'Add to favorites', - onClick: () => toggleStarredEntity(rowData), - }; - }, - ]; - return ( setSelectedTab(label)} /> @@ -129,7 +71,6 @@ const CatalogPageContents = () => { entities={matchingEntities} loading={loading} error={error} - actions={actions} />
diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index b50da60a50..64d55ad2ed 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { wrapInTestApp } from '@backstage/test-utils'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import * as React from 'react'; import { CatalogTable } from './CatalogTable'; @@ -66,11 +66,11 @@ describe('CatalogTable component', () => { />, ), ); - expect( - await rendered.findByText(`Owned (${entites.length})`), - ).toBeInTheDocument(); - expect(await rendered.findByText('component1')).toBeInTheDocument(); - expect(await rendered.findByText('component2')).toBeInTheDocument(); - expect(await rendered.findByText('component3')).toBeInTheDocument(); + waitFor(() => { + expect(rendered.queryByText(/Owned \(3\)/)).toBeInTheDocument(); + expect(rendered.queryByText(/component1/)).toBeInTheDocument(); + expect(rendered.queryByText(/component2/)).toBeInTheDocument(); + expect(rendered.queryByText(/component3/)).toBeInTheDocument(); + }); }); }); diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index f62749ee17..d47d8b54c6 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -13,16 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { Entity } from '@backstage/catalog-model'; -import { Table, TableColumn } from '@backstage/core'; +import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Table, TableColumn, TableProps } from '@backstage/core'; import { Link } from '@material-ui/core'; +import Edit from '@material-ui/icons/Edit'; +import GitHub from '@material-ui/icons/GitHub'; +import Star from '@material-ui/icons/Star'; +import StarOutline from '@material-ui/icons/StarBorder'; import { Alert } from '@material-ui/lab'; -import React, { FC } from 'react'; +import React from 'react'; import { generatePath, Link as RouterLink } from 'react-router-dom'; +import { findLocationForEntityMeta } from '../../data/utils'; +import { useStarredEntities } from '../../hooks/useStarredEntites'; import { entityRoute } from '../../routes'; -const columns: TableColumn[] = [ +const columns: TableColumn[] = [ { title: 'Name', field: 'metadata.name', @@ -63,16 +68,16 @@ type CatalogTableProps = { titlePreamble: string; loading: boolean; error?: any; - actions?: any; }; -export const CatalogTable: FC = ({ +export const CatalogTable = ({ entities, loading, error, titlePreamble, - actions, -}) => { +}: CatalogTableProps) => { + const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); + if (error) { return (
@@ -83,8 +88,52 @@ export const CatalogTable: FC = ({ ); } + const actions: TableProps['actions'] = [ + (rowData: Entity) => { + const location = findLocationForEntityMeta(rowData.metadata); + return { + icon: GitHub, + tooltip: 'View on GitHub', + onClick: () => { + if (!location) return; + window.open(location.target, '_blank'); + }, + hidden: location?.type !== 'github', + }; + }, + (rowData: Entity) => { + const createEditLink = (location: LocationSpec): string => { + switch (location.type) { + case 'github': + return location.target.replace('/blob/', '/edit/'); + default: + return location.target; + } + }; + const location = findLocationForEntityMeta(rowData.metadata); + return { + icon: Edit, + tooltip: 'Edit', + // iconProps: { size: 'small' }, + onClick: () => { + if (!location) return; + window.open(createEditLink(location), '_blank'); + }, + hidden: location?.type !== 'github', + }; + }, + (rowData: Entity) => { + const isStarred = isStarredEntity(rowData); + return { + icon: isStarred ? () => : StarOutline, + tooltip: isStarred ? 'Remove from favorites' : 'Add to favorites', + onClick: () => toggleStarredEntity(rowData), + }; + }, + ]; + return ( - isLoading={loading} columns={columns} options={{ From 4a1f862d869a454c674f6ad0f9d5ca4e6d62d160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 27 Jun 2020 20:58:19 +0200 Subject: [PATCH 4/9] Minor cleanup in the CatalogLayout --- .../src/components/CatalogPage/CatalogLayout.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx b/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx index 65715e04c7..39b3c66013 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogLayout.tsx @@ -14,26 +14,29 @@ * limitations under the License. */ -import React, { FC } from 'react'; import { Header, HomepageTimer, + identityApiRef, Page, pageTheme, - identityApiRef, useApi, } from '@backstage/core'; +import React from 'react'; import { getTimeBasedGreeting } from './utils/timeUtil'; -const CatalogLayout: FC<{}> = props => { - const { children } = props; +type Props = { + children?: React.ReactNode; +}; + +const CatalogLayout = ({ children }: Props) => { const greeting = getTimeBasedGreeting(); - const identityApi = useApi(identityApiRef); + const userId = useApi(identityApiRef).getUserId(); return (
Date: Sat, 27 Jun 2020 21:23:29 +0200 Subject: [PATCH 5/9] Supply tabs from the catalog page again --- .../components/CatalogPage/CatalogPage.tsx | 30 ++++++- .../components/CatalogPage/CatalogTabs.tsx | 86 ++++++++----------- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 9626b695a2..4f85ff3fb4 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -24,7 +24,7 @@ import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { CatalogFilter } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; import CatalogLayout from './CatalogLayout'; -import { CatalogTabs } from './CatalogTabs'; +import { CatalogTabs, LabeledComponentType } from './CatalogTabs'; import { WelcomeBanner } from './WelcomeBanner'; const useStyles = makeStyles(theme => ({ @@ -36,6 +36,29 @@ const useStyles = makeStyles(theme => ({ }, })); +const tabs: LabeledComponentType[] = [ + { + id: 'service', + label: 'Services', + }, + { + id: 'website', + label: 'Websites', + }, + { + id: 'library', + label: 'Libraries', + }, + { + id: 'documentation', + label: 'Documentation', + }, + { + id: 'other', + label: 'Other', + }, +]; + const CatalogPageContents = () => { const styles = useStyles(); const { loading, error, matchingEntities } = useFilteredEntities(); @@ -44,7 +67,10 @@ const CatalogPageContents = () => { return ( - setSelectedTab(label)} /> + setSelectedTab(label)} + /> diff --git a/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx b/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx index 931874d2fc..9d07eae62e 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx @@ -16,66 +16,49 @@ import { Entity } from '@backstage/catalog-model'; import { HeaderTabs } from '@backstage/core'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { FilterGroup, useEntityFilterGroup } from '../../filter'; -type ComponentType = - | 'service' - | 'website' - | 'library' - | 'documentation' - | 'other'; - +/** + * A component type, and a human readable label for it. + */ export type LabeledComponentType = { - id: ComponentType; + id: string; label: string; }; -const labeledEntityTypes: LabeledComponentType[] = [ - { - id: 'service', - label: 'Services', - }, - { - id: 'website', - label: 'Websites', - }, - { - id: 'library', - label: 'Libraries', - }, - { - id: 'documentation', - label: 'Documentation', - }, - { - id: 'other', - label: 'Other', - }, -]; - -const filterGroup: FilterGroup = { - filters: Object.fromEntries( - labeledEntityTypes.map(t => [ - t.id, - (entity: Entity) => entity.spec?.type === t.id, - ]), - ), -}; - -type OnChangeCallback = (item: { type: string; label: string }) => void; +/** + * Called on mount, and when the selected tab changes. + */ +export type OnChangeCallback = (tab: LabeledComponentType) => void; type Props = { + tabs: LabeledComponentType[]; onChange?: OnChangeCallback; }; /** * The tabs at the top of the catalog list page, for component type filtering. */ -export const CatalogTabs = ({ onChange }: Props) => { +export const CatalogTabs = ({ tabs, onChange }: Props) => { + const filterGroup = useMemo(() => { + return { + filters: Object.fromEntries( + tabs.map(t => [t.id, (entity: Entity) => entity.spec?.type === t.id]), + ), + }; + }, [tabs]); + const { setSelectedFilters } = useEntityFilterGroup('type', filterGroup, [ - labeledEntityTypes[0].id, + tabs[0].id, ]); + const [currentTabIndex, setCurrentTabIndex] = useState(0); const onChangeRef = useRef(); @@ -83,19 +66,18 @@ export const CatalogTabs = ({ onChange }: Props) => { onChangeRef.current = onChange; }, [onChange]); useEffect(() => { - const type = labeledEntityTypes[currentTabIndex]; - onChangeRef.current?.({ type: type.id, label: type.label }); - }, [currentTabIndex]); + onChangeRef.current?.(tabs[currentTabIndex]); + }, [tabs, currentTabIndex]); const switchTab = useCallback( (index: Number) => { - const type = labeledEntityTypes[index as number]; - setSelectedFilters([type.id]); + const tab = tabs[index as number]; + setSelectedFilters([tab.id]); setCurrentTabIndex(index as number); - onChangeRef.current?.({ type: type.id, label: type.label }); + onChangeRef.current?.(tab); }, - [setSelectedFilters], + [tabs, setSelectedFilters], ); - return ; + return ; }; From f32e13c6c6a01d5339b0cca0b12af6e823b49752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 27 Jun 2020 21:26:59 +0200 Subject: [PATCH 6/9] Make Tabs supply number instead of Number --- packages/core/src/layout/HeaderTabs/index.tsx | 5 +++-- plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/core/src/layout/HeaderTabs/index.tsx b/packages/core/src/layout/HeaderTabs/index.tsx index 2617a27aa9..35e952a10e 100644 --- a/packages/core/src/layout/HeaderTabs/index.tsx +++ b/packages/core/src/layout/HeaderTabs/index.tsx @@ -42,16 +42,17 @@ export type Tab = { id: string; label: string; }; + export const HeaderTabs: React.FC<{ tabs: Tab[]; - onChange?: (index: Number) => void; + onChange?: (index: number) => void; }> = ({ tabs, onChange }) => { const [selectedTab, setSelectedTab] = useState(0); const styles = useStyles(); const handleChange = (_: React.ChangeEvent<{}>, index: Number) => { setSelectedTab(index); - if (onChange) onChange(index); + if (onChange) onChange(index.valueOf()); }; return ( diff --git a/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx b/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx index 9d07eae62e..cad05c4ef6 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx @@ -70,10 +70,10 @@ export const CatalogTabs = ({ tabs, onChange }: Props) => { }, [tabs, currentTabIndex]); const switchTab = useCallback( - (index: Number) => { - const tab = tabs[index as number]; + (index: number) => { + const tab = tabs[index]; setSelectedFilters([tab.id]); - setCurrentTabIndex(index as number); + setCurrentTabIndex(index); onChangeRef.current?.(tab); }, [tabs, setSelectedFilters], From 0d4a76fbf06cd1576b53786b8d0cd1fc9f889446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 27 Jun 2020 21:42:36 +0200 Subject: [PATCH 7/9] Propagate filter/entity errors properly to the table --- plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx | 2 ++ plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx | 2 ++ plugins/catalog/src/filter/context.ts | 2 ++ plugins/catalog/src/filter/useFilteredEntities.ts | 4 ++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx b/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx index cad05c4ef6..ae1e2a4e5a 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogTabs.tsx @@ -61,10 +61,12 @@ export const CatalogTabs = ({ tabs, onChange }: Props) => { const [currentTabIndex, setCurrentTabIndex] = useState(0); + // Hold a reference to the callback const onChangeRef = useRef(); useEffect(() => { onChangeRef.current = onChange; }, [onChange]); + useEffect(() => { onChangeRef.current?.(tabs[currentTabIndex]); }, [tabs, currentTabIndex]); diff --git a/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx b/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx index e8c307ebf6..dd557a4322 100644 --- a/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx +++ b/plugins/catalog/src/filter/EntityFilterGroupsProvider.tsx @@ -113,6 +113,8 @@ function useProvideEntityFilters(): FilterGroupsContext { register, unregister, setGroupSelectedFilters, + loading: !error && !entities, + error, filterGroupStates, matchingEntities, }; diff --git a/plugins/catalog/src/filter/context.ts b/plugins/catalog/src/filter/context.ts index 0f51f11b9a..31e9c91b97 100644 --- a/plugins/catalog/src/filter/context.ts +++ b/plugins/catalog/src/filter/context.ts @@ -26,6 +26,8 @@ export type FilterGroupsContext = { ) => void; unregister: (filterGroupId: string) => void; setGroupSelectedFilters: (filterGroupId: string, filterIds: string[]) => void; + loading: boolean; + error?: Error; filterGroupStates: { [filterGroupId: string]: FilterGroupStates }; matchingEntities: Entity[]; }; diff --git a/plugins/catalog/src/filter/useFilteredEntities.ts b/plugins/catalog/src/filter/useFilteredEntities.ts index 3f3bd03858..256f5247c7 100644 --- a/plugins/catalog/src/filter/useFilteredEntities.ts +++ b/plugins/catalog/src/filter/useFilteredEntities.ts @@ -27,8 +27,8 @@ export function useFilteredEntities() { } return { - loading: false, - error: undefined, + loading: context.loading, + error: context.error, matchingEntities: context.matchingEntities, }; } From 52e784f6516c57766072303c3bafc5ad9cd338fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 27 Jun 2020 22:33:59 +0200 Subject: [PATCH 8/9] Supply the sidebar catalog filter from the page again --- .../CatalogFilter/CatalogFilter.test.tsx | 124 ++++++++++-------- .../CatalogFilter/CatalogFilter.tsx | 77 +++++------ .../components/CatalogPage/CatalogPage.tsx | 104 +++++++++++---- plugins/catalog/src/data/filters.ts | 67 ---------- 4 files changed, 180 insertions(+), 192 deletions(-) delete mode 100644 plugins/catalog/src/data/filters.ts diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx index 9dd0caa3b4..a98eec0875 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.test.tsx @@ -26,9 +26,8 @@ import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent, render, waitFor } from '@testing-library/react'; import React from 'react'; import { CatalogApi, catalogApiRef } from '../../api/types'; -import { EntityGroup } from '../../data/filters'; import { EntityFilterGroupsProvider } from '../../filter'; -import { CatalogFilter, CatalogFilterGroup } from './CatalogFilter'; +import { ButtonGroup, CatalogFilter } from './CatalogFilter'; describe('Catalog Filter', () => { const catalogApi: Partial = { @@ -79,12 +78,12 @@ describe('Catalog Filter', () => { ); it('should render the different groups', async () => { - const mockGroups: CatalogFilterGroup[] = [ + const mockGroups: ButtonGroup[] = [ { name: 'Test Group 1', items: [] }, { name: 'Test Group 2', items: [] }, ]; const { findByText } = renderWrapped( - , + , ); for (const group of mockGroups) { expect(await findByText(group.name)).toBeInTheDocument(); @@ -92,24 +91,26 @@ describe('Catalog Filter', () => { }); it('should render the different items and their names', async () => { - const mockGroups: CatalogFilterGroup[] = [ + const mockGroups: ButtonGroup[] = [ { name: 'Test Group 1', items: [ { - id: EntityGroup.ALL, + id: 'all', label: 'First Label', + filterFn: () => true, }, { - id: EntityGroup.STARRED, + id: 'starred', label: 'Second Label', + filterFn: () => false, }, ], }, ]; const { findByText } = renderWrapped( - , + , ); for (const item of mockGroups[0].items) { @@ -118,48 +119,19 @@ describe('Catalog Filter', () => { }); it('selects the first item if no desired initial one is set', async () => { - const mockGroups: CatalogFilterGroup[] = [ + const mockGroups: ButtonGroup[] = [ { name: 'Test Group 1', items: [ { - id: EntityGroup.ALL, + id: 'all', label: 'First Label', + filterFn: () => true, }, { - id: EntityGroup.STARRED, - label: 'Second Label', - }, - ], - }, - ]; - - const onChange = jest.fn(); - - renderWrapped( - , - ); - - await waitFor(() => { - expect(onChange).toHaveBeenLastCalledWith({ - id: EntityGroup.ALL, - label: 'First Label', - }); - }); - }); - - it('selects the initial item', async () => { - const mockGroups: CatalogFilterGroup[] = [ - { - name: 'Test Group 1', - items: [ - { - id: EntityGroup.ALL, - label: 'First Label', - }, - { - id: EntityGroup.STARRED, + id: 'starred', label: 'Second Label', + filterFn: () => false, }, ], }, @@ -169,32 +141,71 @@ describe('Catalog Filter', () => { renderWrapped( , ); await waitFor(() => { expect(onChange).toHaveBeenLastCalledWith({ - id: EntityGroup.STARRED, + id: 'all', + label: 'First Label', + }); + }); + }); + + it('selects the initial item', async () => { + const mockGroups: ButtonGroup[] = [ + { + name: 'Test Group 1', + items: [ + { + id: 'all', + label: 'First Label', + filterFn: () => true, + }, + { + id: 'starred', + label: 'Second Label', + filterFn: () => false, + }, + ], + }, + ]; + + const onChange = jest.fn(); + + renderWrapped( + , + ); + + await waitFor(() => { + expect(onChange).toHaveBeenLastCalledWith({ + id: 'starred', label: 'Second Label', }); }); }); it('can change the selected item', async () => { - const mockGroups: CatalogFilterGroup[] = [ + const mockGroups: ButtonGroup[] = [ { name: 'Test Group 1', items: [ { - id: EntityGroup.ALL, + id: 'all', label: 'First Label', + filterFn: () => true, }, { - id: EntityGroup.STARRED, + id: 'starred', label: 'Second Label', + filterFn: () => false, }, ], }, @@ -203,12 +214,16 @@ describe('Catalog Filter', () => { const onChange = jest.fn(); const { findByText } = renderWrapped( - , + , ); await waitFor(() => { expect(onChange).toHaveBeenLastCalledWith({ - id: EntityGroup.ALL, + id: 'all', label: 'First Label', }); }); @@ -217,27 +232,28 @@ describe('Catalog Filter', () => { await waitFor(() => { expect(onChange).toHaveBeenLastCalledWith({ - id: EntityGroup.STARRED, + id: 'starred', label: 'Second Label', }); }); }); it('displays match counts properly', async () => { - const mockGroups: CatalogFilterGroup[] = [ + const mockGroups: ButtonGroup[] = [ { name: 'Test Group 1', items: [ { - id: EntityGroup.OWNED, + id: 'owned', label: 'First Label', + filterFn: entity => entity.spec?.owner === 'tools@example.com', }, ], }, ]; const { findByText } = renderWrapped( - , + , ); expect(await findByText('1')).toBeInTheDocument(); diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx index ea22198e6d..90a1e15ad0 100644 --- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx +++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { IconComponent, identityApiRef, useApi } from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; +import { IconComponent } from '@backstage/core'; import { Card, List, @@ -27,31 +28,22 @@ import { Typography, } from '@material-ui/core'; import React, { - FC, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; -import { - EntityFilterOptions, - entityFilters, - EntityGroup, -} from '../../data/filters'; import { FilterGroup, useEntityFilterGroup } from '../../filter'; -import { useStarredEntities } from '../../hooks/useStarredEntites'; -export type CatalogFilterItem = { - id: EntityGroup; - label: string; - icon?: IconComponent; - count?: number | FC; -}; - -export type CatalogFilterGroup = { +export type ButtonGroup = { name: string; - items: CatalogFilterItem[]; + items: { + id: string; + label: string; + icon?: IconComponent; + filterFn: (entity: Entity) => boolean; + }[]; }; const useStyles = makeStyles(theme => ({ @@ -83,21 +75,24 @@ const useStyles = makeStyles(theme => ({ type OnChangeCallback = (item: { id: string; label: string }) => void; type Props = { - filterGroups: CatalogFilterGroup[]; + buttonGroups: ButtonGroup[]; + initiallySelected: string; onChange?: OnChangeCallback; - initiallySelected?: EntityGroup; }; /** * The main filter group in the sidebar, toggling owned/starred/all. */ export const CatalogFilter = ({ - filterGroups, + buttonGroups, onChange, initiallySelected, }: Props) => { const classes = useStyles(); - const { currentFilter, setCurrentFilter, getFilterCount } = useFilter(); + const { currentFilter, setCurrentFilter, getFilterCount } = useFilter( + buttonGroups, + initiallySelected, + ); const onChangeRef = useRef(); useEffect(() => { @@ -105,9 +100,9 @@ export const CatalogFilter = ({ }, [onChange]); const setCurrent = useCallback( - (item: CatalogFilterItem) => { + (item: { id: string; label: string }) => { setCurrentFilter(item.id); - onChangeRef.current?.(item); + onChangeRef.current?.({ id: item.id, label: item.label }); }, [setCurrentFilter], ); @@ -115,10 +110,10 @@ export const CatalogFilter = ({ // Make one initial onChange to inform the surroundings about the selected // item useEffect(() => { - const items = filterGroups.flatMap(g => g.items); + const items = buttonGroups.flatMap(g => g.items); const item = items.find(i => i.id === initiallySelected) || items[0]; if (item) { - onChangeRef.current?.(item); + onChangeRef.current?.({ id: item.id, label: item.label }); } // intentionally only happens on startup // eslint-disable-next-line react-hooks/exhaustive-deps @@ -126,7 +121,7 @@ export const CatalogFilter = ({ return ( - {filterGroups.map(group => ( + {buttonGroups.map(group => ( {group.name} @@ -165,31 +160,29 @@ export const CatalogFilter = ({ ); }; -function useFilter(): { +function useFilter( + buttonGroups: ButtonGroup[], + initiallySelected: string, +): { currentFilter: string; setCurrentFilter: (filterId: string) => void; getFilterCount: (filterId: string) => number | undefined; } { - const [currentFilter, setCurrentFilter] = useState('OWNED'); - const { isStarredEntity } = useStarredEntities(); - const userId = useApi(identityApiRef).getUserId(); + const [currentFilter, setCurrentFilter] = useState(initiallySelected); - const filterGroup = useMemo(() => { - const result: FilterGroup = { filters: {} }; - const options: EntityFilterOptions = { - userId, - isStarred: isStarredEntity, - }; - for (const [filterId, filterFn] of Object.entries(entityFilters)) { - result.filters[filterId] = entity => filterFn(entity, options); - } - return result; - }, [isStarredEntity, userId]); + const filterGroup = useMemo( + () => ({ + filters: Object.fromEntries( + buttonGroups.flatMap(g => g.items).map(i => [i.id, i.filterFn]), + ), + }), + [buttonGroups], + ); const { setSelectedFilters, state } = useEntityFilterGroup( 'primary-sidebar', filterGroup, - ['OWNED'], + [initiallySelected], ); const setCurrent = useCallback( diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 4f85ff3fb4..e59b753363 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -14,14 +14,22 @@ * limitations under the License. */ -import { Content, ContentHeader, SupportButton } from '@backstage/core'; +import { + Content, + ContentHeader, + identityApiRef, + SupportButton, + useApi, +} from '@backstage/core'; import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; import { Button, makeStyles } from '@material-ui/core'; -import React, { useState } from 'react'; +import SettingsIcon from '@material-ui/icons/Settings'; +import StarIcon from '@material-ui/icons/Star'; +import React, { useMemo, useState } from 'react'; import { Link as RouterLink } from 'react-router-dom'; -import { EntityGroup, filterGroups } from '../../data/filters'; import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; -import { CatalogFilter } from '../CatalogFilter/CatalogFilter'; +import { useStarredEntities } from '../../hooks/useStarredEntites'; +import { CatalogFilter, ButtonGroup } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; import CatalogLayout from './CatalogLayout'; import { CatalogTabs, LabeledComponentType } from './CatalogTabs'; @@ -36,35 +44,73 @@ const useStyles = makeStyles(theme => ({ }, })); -const tabs: LabeledComponentType[] = [ - { - id: 'service', - label: 'Services', - }, - { - id: 'website', - label: 'Websites', - }, - { - id: 'library', - label: 'Libraries', - }, - { - id: 'documentation', - label: 'Documentation', - }, - { - id: 'other', - label: 'Other', - }, -]; - const CatalogPageContents = () => { const styles = useStyles(); const { loading, error, matchingEntities } = useFilteredEntities(); + const { isStarredEntity } = useStarredEntities(); + const userId = useApi(identityApiRef).getUserId(); const [selectedTab, setSelectedTab] = useState(); const [selectedSidebarItem, setSelectedSidebarItem] = useState(); + const tabs = useMemo( + () => [ + { + id: 'service', + label: 'Services', + }, + { + id: 'website', + label: 'Websites', + }, + { + id: 'library', + label: 'Libraries', + }, + { + id: 'documentation', + label: 'Documentation', + }, + { + id: 'other', + label: 'Other', + }, + ], + [], + ); + + const filterGroups = useMemo( + () => [ + { + name: 'Personal', + items: [ + { + id: 'owned', + label: 'Owned', + icon: SettingsIcon, + filterFn: entity => entity.spec?.owner === userId, + }, + { + id: 'starred', + label: 'Starred', + icon: StarIcon, + filterFn: isStarredEntity, + }, + ], + }, + { + name: 'Company', // TODO: Replace with Company name, read from app config. + items: [ + { + id: 'all', + label: 'All', + filterFn: () => true, + }, + ], + }, + ], + [isStarredEntity, userId], + ); + return ( {
setSelectedSidebarItem(label)} - initiallySelected={EntityGroup.OWNED} + initiallySelected="owned" />
boolean; - -export type EntityFilterOptions = { - isStarred: (entity: Entity) => boolean; - userId: string; -}; - -export const entityFilters: Record = { - [EntityGroup.OWNED]: (e, { userId }) => e.spec?.owner === userId, - [EntityGroup.ALL]: () => true, - [EntityGroup.STARRED]: (e, { isStarred }) => isStarred(e), -}; From 963648eeb72d50a9d1ec05c7f7ea950d6a2fc6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 28 Jun 2020 20:48:11 +0200 Subject: [PATCH 9/9] Address comments --- packages/core/src/layout/HeaderTabs/index.tsx | 6 +++--- .../components/CatalogTable/CatalogTable.test.tsx | 12 +++++------- .../src/components/CatalogTable/CatalogTable.tsx | 13 +++++++++---- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/core/src/layout/HeaderTabs/index.tsx b/packages/core/src/layout/HeaderTabs/index.tsx index 35e952a10e..838b126569 100644 --- a/packages/core/src/layout/HeaderTabs/index.tsx +++ b/packages/core/src/layout/HeaderTabs/index.tsx @@ -47,12 +47,12 @@ export const HeaderTabs: React.FC<{ tabs: Tab[]; onChange?: (index: number) => void; }> = ({ tabs, onChange }) => { - const [selectedTab, setSelectedTab] = useState(0); + const [selectedTab, setSelectedTab] = useState(0); const styles = useStyles(); - const handleChange = (_: React.ChangeEvent<{}>, index: Number) => { + const handleChange = (_: React.ChangeEvent<{}>, index: number) => { setSelectedTab(index); - if (onChange) onChange(index.valueOf()); + if (onChange) onChange(index); }; return ( diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index 64d55ad2ed..707213f3bb 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { wrapInTestApp } from '@backstage/test-utils'; -import { render, waitFor } from '@testing-library/react'; +import { render } from '@testing-library/react'; import * as React from 'react'; import { CatalogTable } from './CatalogTable'; @@ -66,11 +66,9 @@ describe('CatalogTable component', () => { />, ), ); - waitFor(() => { - expect(rendered.queryByText(/Owned \(3\)/)).toBeInTheDocument(); - expect(rendered.queryByText(/component1/)).toBeInTheDocument(); - expect(rendered.queryByText(/component2/)).toBeInTheDocument(); - expect(rendered.queryByText(/component3/)).toBeInTheDocument(); - }); + expect(rendered.getByText(/Owned \(3\)/)).toBeInTheDocument(); + expect(rendered.getByText(/component1/)).toBeInTheDocument(); + expect(rendered.getByText(/component2/)).toBeInTheDocument(); + expect(rendered.getByText(/component3/)).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index d47d8b54c6..b507b96f05 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -92,7 +92,7 @@ export const CatalogTable = ({ (rowData: Entity) => { const location = findLocationForEntityMeta(rowData.metadata); return { - icon: GitHub, + icon: () => , tooltip: 'View on GitHub', onClick: () => { if (!location) return; @@ -112,9 +112,8 @@ export const CatalogTable = ({ }; const location = findLocationForEntityMeta(rowData.metadata); return { - icon: Edit, + icon: () => , tooltip: 'Edit', - // iconProps: { size: 'small' }, onClick: () => { if (!location) return; window.open(createEditLink(location), '_blank'); @@ -125,7 +124,13 @@ export const CatalogTable = ({ (rowData: Entity) => { const isStarred = isStarredEntity(rowData); return { - icon: isStarred ? () => : StarOutline, + cellStyle: { paddingLeft: '1em' }, + icon: () => + isStarred ? ( + + ) : ( + + ), tooltip: isStarred ? 'Remove from favorites' : 'Add to favorites', onClick: () => toggleStarredEntity(rowData), };