diff --git a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx
index 61a83fccd8..e40f4806ed 100644
--- a/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx
+++ b/plugins/catalog/src/components/CatalogFilter/CatalogFilter.tsx
@@ -20,6 +20,7 @@ import {
List,
ListItemIcon,
ListItemText,
+ ListItemSecondaryAction,
MenuItem,
Typography,
Theme,
@@ -109,7 +110,9 @@ export const CatalogFilter: FC<{
{item.label}
- {entitiesByFilter[item.id]?.length ?? '-'}
+
+ {entitiesByFilter[item.id]?.length ?? '-'}
+
))}
diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
index ef77cf573f..9329eb9317 100644
--- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
+++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx
@@ -162,7 +162,7 @@ export const CatalogPage: FC<{}> = () => {
color="primary"
to={scaffolderRootRoute.path}
>
- Create Service
+ Create Component
All your software catalog entities
diff --git a/plugins/catalog/src/hooks/useEntities.test.tsx b/plugins/catalog/src/hooks/useEntities.test.tsx
new file mode 100644
index 0000000000..c8176f3a6c
--- /dev/null
+++ b/plugins/catalog/src/hooks/useEntities.test.tsx
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { renderHook, act } from '@testing-library/react-hooks';
+import { useEntityFilterGroup } from './useEntities';
+
+describe('useEntitiesHooks', () => {
+ const testEntities = [
+ { name: 'test1', type: 'type1' },
+ { name: 'test2', type: 'type2' },
+ { name: 'test3', type: 'type3' },
+ { name: 'test4', type: 'type2' },
+ { name: 'test5', type: 'type2' },
+ ];
+
+ type TestEntitiy = {
+ name: string;
+ type: string;
+ };
+
+ const testFilterFunctions = {
+ type1: {
+ filterFunction: (entity: TestEntitiy) => entity.type === 'type1',
+ isSelected: false,
+ },
+ type2: {
+ filterFunction: (entity: TestEntitiy) => entity.type === 'type2',
+ isSelected: false,
+ },
+ type3: {
+ filterFunction: (entity: TestEntitiy) => entity.type === 'type3',
+ isSelected: false,
+ },
+ };
+
+ it('should calculate count', async () => {
+ const { result } = renderHook(() =>
+ useEntityFilterGroup(testEntities, testFilterFunctions),
+ );
+
+ expect(result.current.states.type1.count).toBe(1);
+ expect(result.current.states.type2.count).toBe(3);
+ expect(result.current.states.type3.count).toBe(1);
+ });
+
+ it('should set the isSelected flag properly', () => {
+ const { result } = renderHook(() =>
+ useEntityFilterGroup(testEntities, testFilterFunctions),
+ );
+
+ expect(result.current.states.type1.isSelected).toBeFalsy();
+ expect(result.current.states.type2.isSelected).toBeFalsy();
+ expect(result.current.states.type3.isSelected).toBeFalsy();
+
+ act(() => {
+ result.current.selectItems(['type1']);
+ });
+
+ expect(result.current.states.type1.isSelected).toBeTruthy();
+ expect(result.current.states.type2.isSelected).toBeFalsy();
+ expect(result.current.states.type3.isSelected).toBeFalsy();
+ });
+
+ it('should filter entities', () => {
+ const { result } = renderHook(() =>
+ useEntityFilterGroup(testEntities, testFilterFunctions),
+ );
+
+ act(() => {
+ result.current.selectItems(['type1']);
+ });
+ expect(result.current.filteredItems).toEqual([
+ { name: 'test1', type: 'type1' },
+ ]);
+
+ act(() => {
+ result.current.selectItems(['type2']);
+ });
+ expect(result.current.filteredItems).toEqual([
+ { name: 'test2', type: 'type2' },
+ { name: 'test4', type: 'type2' },
+ { name: 'test5', type: 'type2' },
+ ]);
+
+ act(() => {
+ result.current.selectItems(['type3', 'type1']);
+ });
+ expect(result.current.filteredItems).toEqual([
+ { name: 'test1', type: 'type1' },
+ { name: 'test3', type: 'type3' },
+ ]);
+ });
+});
diff --git a/plugins/catalog/src/hooks/useEntities.ts b/plugins/catalog/src/hooks/useEntities.ts
index 6509bd9b4b..c48e3af026 100644
--- a/plugins/catalog/src/hooks/useEntities.ts
+++ b/plugins/catalog/src/hooks/useEntities.ts
@@ -40,6 +40,88 @@ type UseEntities = {
selectTypeFilter: (id: string) => void;
};
+type EntityFilterGroupOutput = {
+ selectItems: (items: string[]) => void;
+ filteredItems: T[];
+ states: OutputState;
+};
+
+type OutputState = { [key: string]: { isSelected: boolean; count: number } };
+
+type FilterDefinition = {
+ [key: string]: {
+ isSelected: boolean;
+ filterFunction: (entity: T) => boolean;
+ };
+};
+
+export const useEntityFilterGroup = (
+ entities: T[],
+ filterFunctions: FilterDefinition,
+): EntityFilterGroupOutput => {
+ const [filterFuncs, setFilterFuncs] = useState>(
+ filterFunctions,
+ );
+
+ // and
+ // Object.entries(filterFuncs).filter(([_, {isSelected}]) => isSelected).map(([_, {filterFunction}]) => filterFunction).reduce((acc, func) => (acc.filter(func)), entities)
+
+ return {
+ selectItems: (functionNames: Array) => {
+ const selectedFilterFunctions = Object.fromEntries(
+ Object.entries(filterFunctions).map(([key, { filterFunction }]) => [
+ key,
+ { isSelected: functionNames.includes(key), filterFunction },
+ ]),
+ );
+ setFilterFuncs(selectedFilterFunctions);
+ },
+ filteredItems: entities.filter(entity =>
+ Object.entries(filterFuncs)
+ .filter(([_, { isSelected }]) => isSelected)
+ .map(([_, { filterFunction }]) => filterFunction)
+ .map(filter => filter(entity))
+ .some(v => v === true),
+ ),
+ states: Object.keys(filterFuncs).reduce(
+ (acc, val) => ({
+ ...acc,
+ [val]: {
+ ...filterFuncs[val],
+ count: entities.filter(filterFuncs[val].filterFunction).length,
+ },
+ }),
+ {} as OutputState,
+ ),
+ };
+};
+
+// const MyFilterGroup = () => {
+// const { selectedItems, selectItems, counts } = useEntityFilterGroup(
+// 'lifecycle',
+// {
+// production: e => e.spec?.lifecyle === 'production',
+// },
+// );
+
+// return (
+//
+// selectItem('production')}
+// >
+// Production ({counts.production})
+//
+//
+// );
+// };
+
+export const useUser = () => {
+ const indentityApi = useApi(identityApiRef);
+ const userId = indentityApi.getUserId();
+ return { userId };
+};
+
export const useEntities = (): UseEntities => {
const [selectedFilter, setSelectedFilter] = useState<
EntityGroup | undefined
@@ -51,8 +133,7 @@ export const useEntities = (): UseEntities => {
async () => catalogApi.getEntities(),
);
- const indentityApi = useApi(identityApiRef);
- const userId = indentityApi.getUserId();
+ const { userId } = useUser();
const [selectedTypeFilter, selectTypeFilter] = useState(
labeledEntityTypes[0].id,