Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Adds selected filters in the url as a query parameter #214

Merged
merged 1 commit into from
Apr 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <> </>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this return ???

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a React component so we need 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
30 changes: 20 additions & 10 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 { 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();

React.useEffect(() => {
const selectedcategories = categories.selectedByName.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.selectedByName, 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();
});
});
16 changes: 16 additions & 0 deletions ui/src/store/catalog.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Instance, types } from 'mobx-state-tree';
import { Icons } from '../common/icons';
import { titleCase } from '../common/titlecase';

const icons: { [catalog: string]: Icons } = {
official: Icons.Cat,
Expand Down Expand Up @@ -41,6 +42,13 @@ export const CatalogStore = types
self.items.forEach((c) => {
c.selected = false;
});
},
toggleByName(name: string) {
self.items.forEach((c) => {
if (titleCase(c.name) === name) {
c.selected = true;
}
});
}
}))

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

return list;
},

/* This view returns list of the selected catalos's name instead of id
to avoid loop on it inorder to get catalogs name */
get selectedByName() {
pratap0007 marked this conversation as resolved.
Show resolved Hide resolved
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.selectedByName.sort()).toEqual(['Build Tools', 'CLI'].sort());

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

// This returns list of selected category's name
get selectedByName() {
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 +74,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 @@ -679,4 +679,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.selectedByName).toEqual(['Build Tools']);

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