From d12831eb0726c16383b8010e8a5d50473ad9a4a5 Mon Sep 17 00:00:00 2001 From: saurabh29789 Date: Fri, 18 Sep 2020 11:31:37 +0530 Subject: [PATCH] Add support for searching objects (#10424) --- browser/app/js/browser/Header.js | 2 + browser/app/js/buckets/selectors.js | 3 +- .../app/js/objects/ObjectsListContainer.js | 17 ++++++-- browser/app/js/objects/ObjectsSearch.js | 43 +++++++++++++++++++ .../__tests__/ObjectsListContainer.test.js | 6 +-- .../objects/__tests__/ObjectsSearch.test.js | 32 ++++++++++++++ .../app/js/objects/__tests__/reducer.test.js | 1 + browser/app/js/objects/actions.js | 8 ++++ browser/app/js/objects/reducer.js | 6 +++ browser/app/js/objects/selectors.js | 10 +++++ browser/app/less/inc/form.less | 20 ++++++++- 11 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 browser/app/js/objects/ObjectsSearch.js create mode 100644 browser/app/js/objects/__tests__/ObjectsSearch.test.js diff --git a/browser/app/js/browser/Header.js b/browser/app/js/browser/Header.js index 53777b5f6..c6c92f7b3 100644 --- a/browser/app/js/browser/Header.js +++ b/browser/app/js/browser/Header.js @@ -15,6 +15,7 @@ */ import React from "react" +import ObjectsSearch from "../objects/ObjectsSearch" import Path from "../objects/Path" import StorageInfo from "./StorageInfo" import BrowserDropdown from "./BrowserDropdown" @@ -27,6 +28,7 @@ export const Header = () => {
{loggedIn && } + {loggedIn && }
    {loggedIn ? ( diff --git a/browser/app/js/buckets/selectors.js b/browser/app/js/buckets/selectors.js index adce7772d..7144f8a5a 100644 --- a/browser/app/js/buckets/selectors.js +++ b/browser/app/js/buckets/selectors.js @@ -22,7 +22,8 @@ const bucketsFilterSelector = state => state.buckets.filter export const getFilteredBuckets = createSelector( bucketsSelector, bucketsFilterSelector, - (buckets, filter) => buckets.filter(bucket => bucket.indexOf(filter) > -1) + (buckets, filter) => buckets.filter( + bucket => bucket.toLowerCase().indexOf(filter.toLowerCase()) > -1) ) export const getCurrentBucket = state => state.buckets.currentBucket diff --git a/browser/app/js/objects/ObjectsListContainer.js b/browser/app/js/objects/ObjectsListContainer.js index 2767980c3..0e5127285 100644 --- a/browser/app/js/objects/ObjectsListContainer.js +++ b/browser/app/js/objects/ObjectsListContainer.js @@ -18,6 +18,7 @@ import React from "react" import { connect } from "react-redux" import InfiniteScroll from "react-infinite-scroller" import ObjectsList from "./ObjectsList" +import { getFilteredObjects } from "./selectors" export class ObjectsListContainer extends React.Component { constructor(props) { @@ -39,22 +40,29 @@ export class ObjectsListContainer extends React.Component { }) } } + componentDidUpdate(prevProps) { + if (this.props.filter !== prevProps.filter) { + this.setState({ + page: 1 + }) + } + } loadNextPage() { this.setState(state => { return { page: state.page + 1 } }) } render() { - const { objects, listLoading } = this.props + const { filteredObjects, listLoading } = this.props - const visibleObjects = objects.slice(0, this.state.page * 100) + const visibleObjects = filteredObjects.slice(0, this.state.page * 100) return (
    visibleObjects.length} + hasMore={filteredObjects.length > visibleObjects.length} useWindow={true} initialLoad={false} > @@ -70,7 +78,8 @@ const mapStateToProps = state => { return { currentBucket: state.buckets.currentBucket, currentPrefix: state.objects.currentPrefix, - objects: state.objects.list, + filteredObjects: getFilteredObjects(state), + filter: state.objects.filter, sortBy: state.objects.sortBy, sortOrder: state.objects.sortOrder, listLoading: state.objects.listLoading diff --git a/browser/app/js/objects/ObjectsSearch.js b/browser/app/js/objects/ObjectsSearch.js new file mode 100644 index 000000000..5589db98f --- /dev/null +++ b/browser/app/js/objects/ObjectsSearch.js @@ -0,0 +1,43 @@ +/* + * MinIO Cloud Storage (C) 2020 MinIO, Inc. + * + * 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 React from "react" +import { connect } from "react-redux" +import * as actionsObjects from "./actions" + +export const ObjectsSearch = ({ onChange }) => ( +
    + onChange(e.target.value)} + /> + +
    +) + +const mapDispatchToProps = dispatch => { + return { + onChange: filter => + dispatch(actionsObjects.setFilter(filter)) + } +} + +export default connect(undefined, mapDispatchToProps)(ObjectsSearch) diff --git a/browser/app/js/objects/__tests__/ObjectsListContainer.test.js b/browser/app/js/objects/__tests__/ObjectsListContainer.test.js index 3b25d36aa..be5147cc2 100644 --- a/browser/app/js/objects/__tests__/ObjectsListContainer.test.js +++ b/browser/app/js/objects/__tests__/ObjectsListContainer.test.js @@ -20,13 +20,13 @@ import { ObjectsListContainer } from "../ObjectsListContainer" describe("ObjectsList", () => { it("should render without crashing", () => { - shallow() + shallow() }) it("should render ObjectsList with objects", () => { const wrapper = shallow( ) expect(wrapper.find("ObjectsList").length).toBe(1) @@ -40,7 +40,7 @@ describe("ObjectsList", () => { const wrapper = shallow( ) diff --git a/browser/app/js/objects/__tests__/ObjectsSearch.test.js b/browser/app/js/objects/__tests__/ObjectsSearch.test.js new file mode 100644 index 000000000..1441490a3 --- /dev/null +++ b/browser/app/js/objects/__tests__/ObjectsSearch.test.js @@ -0,0 +1,32 @@ +/* + * MinIO Cloud Storage (C) 2018 MinIO, Inc. + * + * 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 React from "react" +import { shallow } from "enzyme" +import { ObjectsSearch } from "../ObjectsSearch" + +describe("ObjectsSearch", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should call onChange with search text", () => { + const onChange = jest.fn() + const wrapper = shallow() + wrapper.find("input").simulate("change", { target: { value: "test" } }) + expect(onChange).toHaveBeenCalledWith("test") + }) +}) diff --git a/browser/app/js/objects/__tests__/reducer.test.js b/browser/app/js/objects/__tests__/reducer.test.js index 635143cbb..3235d025a 100644 --- a/browser/app/js/objects/__tests__/reducer.test.js +++ b/browser/app/js/objects/__tests__/reducer.test.js @@ -23,6 +23,7 @@ describe("objects reducer", () => { const initialState = reducer(undefined, {}) expect(initialState).toEqual({ list: [], + filter: "", listLoading: false, sortBy: "", sortOrder: SORT_ORDER_ASC, diff --git a/browser/app/js/objects/actions.js b/browser/app/js/objects/actions.js index 4c6eff340..08b64a8ba 100644 --- a/browser/app/js/objects/actions.js +++ b/browser/app/js/objects/actions.js @@ -36,6 +36,7 @@ import { getServerInfo, hasServerPublicDomain } from '../browser/selectors' export const SET_LIST = "objects/SET_LIST" export const RESET_LIST = "objects/RESET_LIST" +export const SET_FILTER = "objects/SET_FILTER" export const APPEND_LIST = "objects/APPEND_LIST" export const REMOVE = "objects/REMOVE" export const SET_SORT_BY = "objects/SET_SORT_BY" @@ -57,6 +58,13 @@ export const resetList = () => ({ type: RESET_LIST, }) +export const setFilter = filter => { + return { + type: SET_FILTER, + filter + } +} + export const setListLoading = (listLoading) => ({ type: SET_LIST_LOADING, listLoading, diff --git a/browser/app/js/objects/reducer.js b/browser/app/js/objects/reducer.js index 91f5fc58d..bb67e2afc 100644 --- a/browser/app/js/objects/reducer.js +++ b/browser/app/js/objects/reducer.js @@ -28,6 +28,7 @@ const removeObject = (list, objectToRemove, lookup) => { export default ( state = { list: [], + filter: "", listLoading: false, sortBy: "", sortOrder: SORT_ORDER_ASC, @@ -53,6 +54,11 @@ export default ( ...state, list: [] } + case actionsObjects.SET_FILTER: + return { + ...state, + filter: action.filter + } case actionsObjects.SET_LIST_LOADING: return { ...state, diff --git a/browser/app/js/objects/selectors.js b/browser/app/js/objects/selectors.js index 1943d70b5..0bad6067a 100644 --- a/browser/app/js/objects/selectors.js +++ b/browser/app/js/objects/selectors.js @@ -21,3 +21,13 @@ export const getCurrentPrefix = state => state.objects.currentPrefix export const getCheckedList = state => state.objects.checkedList export const getPrefixWritable = state => state.objects.prefixWritable + +const objectsSelector = state => state.objects.list +const objectsFilterSelector = state => state.objects.filter + +export const getFilteredObjects = createSelector( + objectsSelector, + objectsFilterSelector, + (objects, filter) => objects.filter( + object => object.name.toLowerCase().startsWith(filter.toLowerCase())) +) \ No newline at end of file diff --git a/browser/app/less/inc/form.less b/browser/app/less/inc/form.less index 0d8e70b3f..385dc52d9 100644 --- a/browser/app/less/inc/form.less +++ b/browser/app/less/inc/form.less @@ -169,6 +169,24 @@ select.form-control { } } +.ig-search-dark { + &:before { + font-family: @font-family-icon; + font-weight: 900; + content: '\f002'; + font-size: 15px; + position: absolute; + left: 2px; + top: 8px; + color: rgba(0, 0, 0, 0.5); + } + + .ig-text { + padding-left: 25px; + .placeholder(rgba(0, 0, 0, 0.5)) + } +} + .ig-search { &:before { font-family: @font-family-icon; @@ -270,4 +288,4 @@ select.form-control { .set-expire-decrease { bottom: -27px; .rotate(-180deg); -} \ No newline at end of file +}