Skip to content

Commit

Permalink
Adds selected filters in the url as a query parameter
Browse files Browse the repository at this point in the history
This patch updates url based on the selected filters,
search query and sort by

Signed-off-by: Shiv Verma <[email protected]>
  • Loading branch information
pratap0007 committed Mar 25, 2021
1 parent bfa6f97 commit 5c2a613
Show file tree
Hide file tree
Showing 16 changed files with 281 additions and 41 deletions.
7 changes: 7 additions & 0 deletions ui/src/common/params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum Params {
Query = 'query',
SortBY = 'sortBy',
Category = 'category',
Kind = 'kind',
Catalog = 'catalog'
}
9 changes: 6 additions & 3 deletions ui/src/containers/App/__snapshots__/App.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ exports[`App should render the component correctly and match the snapshot 1`] =
<BrowserRouter>
<Router history={{...}}>
<ParseUrl>
</ParseUrl>
<Page header={{...}} className=\\"hub-page\\" isManagedSidebar={false} isBreadcrumbWidthLimited={false} defaultManagedSidebarIsOpen={true} onPageResize={[Function: onPageResize]} mainTabIndex={-1} isNotificationDrawerExpanded={false} onNotificationDrawerExpand={[Function: onNotificationDrawerExpand]}>
<div className=\\"pf-c-page hub-page\\">
<Memo(wrappedComponent)>
Expand Down Expand Up @@ -245,16 +248,16 @@ exports[`App should render the component correctly and match the snapshot 1`] =
</GridItem>
<GridItem span={10} rowSpan={1}>
<div className=\\"pf-l-grid__item pf-m-10-col pf-m-1-row\\">
<Route exact={true} path=\\"/\\" component={[Function: Resources]}>
<Resources history={{...}} location={{...}} match={{...}} staticContext={[undefined]}>
<Route exact={true} path=\\"/\\" component={{...}}>
<Memo(wrappedComponent) history={{...}} location={{...}} match={{...}} staticContext={[undefined]}>
<Spinner className=\\"hub-spinner\\">
<span className=\\"pf-c-spinner pf-m-xl hub-spinner\\" role=\\"progressbar\\" aria-valuetext=\\"Loading...\\">
<span className=\\"pf-c-spinner__clipper\\" />
<span className=\\"pf-c-spinner__lead-ball\\" />
<span className=\\"pf-c-spinner__tail-ball\\" />
</span>
</Spinner>
</Resources>
</Memo(wrappedComponent)>
</Route>
</div>
</GridItem>
Expand Down
2 changes: 2 additions & 0 deletions ui/src/containers/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Footer from '../../components/Footer';
import Resources from '../Resources';
import Authentication from '../../containers/Authentication';
import Details from '../Details';
import ParseUrl from '../ParseUrl';
import { createProvider } from '../../store/root';
import './App.css';

Expand All @@ -19,6 +20,7 @@ const App: React.FC = observer(() => {
return (
<Provider>
<Router>
<ParseUrl />
<Page header={<Header />} className="hub-page">
<Route exact path="/" component={Background} />
<PageSection>
Expand Down
24 changes: 24 additions & 0 deletions ui/src/containers/ParseUrl/ParseUrl.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { when } from 'mobx';
import { FakeHub } from '../../api/testutil';
import { createProviderAndStore } from '../../store/root';

const TESTDATA_DIR = `src/store/testdata`;
const api = new FakeHub(TESTDATA_DIR);
const { root } = createProviderAndStore(api);

describe('ParseUrl component', () => {
it('it can set url params to resource store', (done) => {
const { resources } = root;
when(
() => {
return !resources.isLoading;
},
() => {
resources.setURLParams('?/query=ansible');
expect(resources.urlParams).toBe('?/query=ansible');

done();
}
);
});
});
23 changes: 23 additions & 0 deletions ui/src/containers/ParseUrl/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { useMst } from '../../store/root';
import { Params } from '../../common/params';

const ParseUrl: React.FC = () => {
const { resources } = useMst();

if (window.location.search) {
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has(Params.Query)) {
resources.setSearch(searchParams.get(Params.Query));
}
if (searchParams.has(Params.SortBY)) {
resources.setSortBy(searchParams.get(Params.SortBY));
}

// Storing url params to store inorder to parse the url only after successfully resource load
resources.setURLParams(window.location.search);
}

return <> </>;
};
export default ParseUrl;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exports[`Resource Component should render the resources component 1`] = `
<BrowserRouter>
<Router history={{...}}>
<Resources>
<Memo(wrappedComponent)>
<Gallery hasGutter={true} className=\\"hub-resource\\">
<div className=\\"pf-l-gallery pf-m-gutter hub-resource\\">
<Cards items={{...}}>
Expand Down Expand Up @@ -740,7 +740,7 @@ exports[`Resource Component should render the resources component 1`] = `
</Cards>
</div>
</Gallery>
</Resources>
</Memo(wrappedComponent)>
</Router>
</BrowserRouter>
Expand Down
32 changes: 21 additions & 11 deletions ui/src/containers/Resources/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { useObserver } from 'mobx-react';
import React, { useEffect } from 'react';
import { observer } from 'mobx-react';
import {
EmptyState,
EmptyStateIcon,
Expand All @@ -14,12 +14,24 @@ import { useHistory } from 'react-router-dom';
import { useMst } from '../../store/root';
import { IResource } from '../../store/resource';
import Cards from '../../components/Cards';
import { UpdateURL } from '../../utils/updateUrl';
import './Resources.css';

const Resources = () => {
const { resources } = useMst();
const Resources: React.FC = observer(() => {
const { resources, categories } = useMst();
const { catalogs, kinds, search, sortBy } = resources;

const history = useHistory();

useEffect(() => {
const selectedcategories = categories.selected.join(',');
const selectedKinds = [...kinds.selected].join(',');
const selectedCatalogs = catalogs.selectedByName.join(',');

const url = UpdateURL(search, sortBy, selectedcategories, selectedKinds, selectedCatalogs);
if (!resources.isLoading) history.replace(`?${url}`);
}, [search, sortBy, categories.selected, kinds.selected, catalogs.selected]);

const clearFilter = () => {
resources.clearAllFilters();
history.push('/');
Expand All @@ -43,12 +55,10 @@ const Resources = () => {
);
};

return useObserver(() =>
resources.resources.size === 0 ? (
<Spinner className="hub-spinner" />
) : (
<React.Fragment>{checkResources(resources.filteredResources)}</React.Fragment>
)
return resources.resources.size === 0 ? (
<Spinner className="hub-spinner" />
) : (
<React.Fragment>{checkResources(resources.filteredResources)}</React.Fragment>
);
};
});
export default Resources;
27 changes: 2 additions & 25 deletions ui/src/containers/Search/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { useObserver } from 'mobx-react';
import { TextInput } from '@patternfly/react-core';
Expand All @@ -9,35 +9,13 @@ import './Search.css';
const Search: React.FC = () => {
const { resources } = useMst();

// to get query params from the url
const searchParams = new URLSearchParams(window.location.search);
const query = searchParams.get('query') || ' ';

useEffect(() => {
if (query !== ' ') {
resources.setSearch(query);
}
}, [query, resources]);

const setParams = ({ query = '' }) => {
const searchParams = new URLSearchParams();
searchParams.set('query', query);
return searchParams.toString();
};

const updateURL = (text: string) => {
const url = setParams({ query: text });
if (window.location.pathname === '/') history.replace(`?${url}`);
};

const onSearchChange = useDebounce(resources.search, 400);

const history = useHistory();
const onSearchKeyPress = (e: React.KeyboardEvent<HTMLElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
history.push('/');
updateURL(resources.search);
if (window.location.pathname !== '/') history.push('/');
}
return;
};
Expand All @@ -48,7 +26,6 @@ const Search: React.FC = () => {
type="search"
onChange={(resourceName: string) => {
resources.setSearch(resourceName);
updateURL(resourceName);
return onSearchChange;
}}
onKeyPress={onSearchKeyPress}
Expand Down
21 changes: 21 additions & 0 deletions ui/src/store/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,25 @@ describe('Store Object', () => {

done();
});

it('should toggle catalogs by name and can get selected catlogs by name', (done) => {
const store = CatalogStore.create({});

const item = Catalog.create({
id: 1,
name: 'tekton',
type: 'community'
});

store.add(item);

store.toggleByName('tekton');
const catalogs = store.items.get('1');
assert(catalogs);

expect(catalogs.selected).toBe(true);
expect(store.selectedByName).toEqual(['tekton']);

done();
});
});
13 changes: 13 additions & 0 deletions ui/src/store/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ export const CatalogStore = types
self.items.forEach((c) => {
c.selected = false;
});
},
toggleByName(name: string) {
self.items.forEach((c) => {
if (c.name === name) {
c.selected = true;
}
});
}
}))

Expand All @@ -58,5 +65,11 @@ export const CatalogStore = types
});

return list;
},

get selectedByName() {
return Array.from(self.items.values())
.filter((c: ICatalog) => c.selected)
.reduce((acc: string[], c: ICatalog) => [...acc, c.name], []);
}
}));
50 changes: 50 additions & 0 deletions ui/src/store/category.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,54 @@ describe('Store functions', () => {
}
);
});

it('can toggle the category by name', (done) => {
const store = CategoryStore.create({}, { api });
expect(store.count).toBe(0);
expect(store.isLoading).toBe(true);

when(
() => !store.isLoading,
() => {
expect(store.count).toBe(5);
expect(store.isLoading).toBe(false);

store.toggleByName('Build Tools');

const categories = store.items.get('1');
assert(categories);
expect(categories.selected).toBe(true);

done();
}
);
});

it('can return the all selected catgories in a list', (done) => {
const store = CategoryStore.create({}, { api });
expect(store.count).toBe(0);
expect(store.isLoading).toBe(true);

when(
() => !store.isLoading,
() => {
expect(store.count).toBe(5);
expect(store.isLoading).toBe(false);

// Gets the category with id as 1
const c1 = store.items.get('1');
assert(c1);
c1.toggle();

// Gets the category with id as 2
const c2 = store.items.get('2');
assert(c2);
c2.toggle();

expect(store.selected.sort()).toEqual(['Build Tools', 'CLI'].sort());

done();
}
);
});
});
14 changes: 14 additions & 0 deletions ui/src/store/category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export const CategoryStore = types
return Array.from(self.items.values());
},

get selected() {
return Array.from(self.items.values())
.filter((c: ICategory) => c.selected)
.reduce((acc: string[], c: ICategory) => [...acc, c.name], []);
},

get selectedTags() {
return new Set(
Array.from(self.items.values())
Expand All @@ -67,6 +73,14 @@ export const CategoryStore = types
self.items.forEach((c) => {
c.selected = false;
});
},

toggleByName(name: string) {
self.items.forEach((c) => {
if (c.name === name) {
c.selected = true;
}
});
}
}))

Expand Down
23 changes: 23 additions & 0 deletions ui/src/store/resource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,27 @@ describe('Store functions', () => {
}
);
});

it('it should parse the url and can update the store', (done) => {
const store = ResourceStore.create(
{},
{
api,
categories: CategoryStore.create({}, { api })
}
);
expect(store.isLoading).toBe(true);
when(
() => !store.isLoading,
() => {
store.setURLParams('?category=Automation%2CBuild+Tools&catalog=tekton');
store.parseUrl();

expect(store.filteredResources.length).toBe(1);
expect(store.categories.selected).toEqual(['Build Tools']);

done();
}
);
});
});
Loading

0 comments on commit 5c2a613

Please sign in to comment.