Initial useEntityFilterGroup implementation
This commit is contained in:
committed by
Fredrik Adelöw
parent
fd7532745f
commit
5d7679d8be
@@ -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' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user