Initial useEntityFilterGroup implementation

This commit is contained in:
Sebastian Qvarfordt
2020-06-22 16:32:05 +02:00
committed by Fredrik Adelöw
parent fd7532745f
commit 5d7679d8be
4 changed files with 193 additions and 4 deletions
@@ -20,6 +20,7 @@ import {
List,
ListItemIcon,
ListItemText,
ListItemSecondaryAction,
MenuItem,
Typography,
Theme,
@@ -109,7 +110,9 @@ export const CatalogFilter: FC<{
{item.label}
</Typography>
</ListItemText>
{entitiesByFilter[item.id]?.length ?? '-'}
<ListItemSecondaryAction>
{entitiesByFilter[item.id]?.length ?? '-'}
</ListItemSecondaryAction>
</MenuItem>
))}
</List>
@@ -162,7 +162,7 @@ export const CatalogPage: FC<{}> = () => {
color="primary"
to={scaffolderRootRoute.path}
>
Create Service
Create Component
</Button>
<SupportButton>All your software catalog entities</SupportButton>
</ContentHeader>
@@ -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<TestEntitiy>(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<TestEntitiy>(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<TestEntitiy>(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' },
]);
});
});
+83 -2
View File
@@ -40,6 +40,88 @@ type UseEntities = {
selectTypeFilter: (id: string) => void;
};
type EntityFilterGroupOutput<T> = {
selectItems: (items: string[]) => void;
filteredItems: T[];
states: OutputState;
};
type OutputState = { [key: string]: { isSelected: boolean; count: number } };
type FilterDefinition<T> = {
[key: string]: {
isSelected: boolean;
filterFunction: (entity: T) => boolean;
};
};
export const useEntityFilterGroup = <T>(
entities: T[],
filterFunctions: FilterDefinition<T>,
): EntityFilterGroupOutput<T> => {
const [filterFuncs, setFilterFuncs] = useState<FilterDefinition<T>>(
filterFunctions,
);
// and
// Object.entries(filterFuncs).filter(([_, {isSelected}]) => isSelected).map(([_, {filterFunction}]) => filterFunction).reduce((acc, func) => (acc.filter(func)), entities)
return {
selectItems: (functionNames: Array<any>) => {
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 (
// <FilterSet>
// <FilterSetItem
// selected={selectedItems.production}
// onClick={() => selectItem('production')}
// >
// Production ({counts.production})
// </FilterSetItem>
// </FilterSet>
// );
// };
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<string>(
labeledEntityTypes[0].id,