mirror of
https://github.com/minio/minio.git
synced 2025-04-17 17:30:07 -04:00
Add support for searching objects (#10424)
This commit is contained in:
parent
4a36cd7035
commit
d12831eb07
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
import ObjectsSearch from "../objects/ObjectsSearch"
|
||||||
import Path from "../objects/Path"
|
import Path from "../objects/Path"
|
||||||
import StorageInfo from "./StorageInfo"
|
import StorageInfo from "./StorageInfo"
|
||||||
import BrowserDropdown from "./BrowserDropdown"
|
import BrowserDropdown from "./BrowserDropdown"
|
||||||
@ -27,6 +28,7 @@ export const Header = () => {
|
|||||||
<header className="fe-header">
|
<header className="fe-header">
|
||||||
<Path />
|
<Path />
|
||||||
{loggedIn && <StorageInfo />}
|
{loggedIn && <StorageInfo />}
|
||||||
|
{loggedIn && <ObjectsSearch />}
|
||||||
<ul className="feh-actions">
|
<ul className="feh-actions">
|
||||||
{loggedIn ? (
|
{loggedIn ? (
|
||||||
<BrowserDropdown />
|
<BrowserDropdown />
|
||||||
|
@ -22,7 +22,8 @@ const bucketsFilterSelector = state => state.buckets.filter
|
|||||||
export const getFilteredBuckets = createSelector(
|
export const getFilteredBuckets = createSelector(
|
||||||
bucketsSelector,
|
bucketsSelector,
|
||||||
bucketsFilterSelector,
|
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
|
export const getCurrentBucket = state => state.buckets.currentBucket
|
||||||
|
@ -18,6 +18,7 @@ import React from "react"
|
|||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux"
|
||||||
import InfiniteScroll from "react-infinite-scroller"
|
import InfiniteScroll from "react-infinite-scroller"
|
||||||
import ObjectsList from "./ObjectsList"
|
import ObjectsList from "./ObjectsList"
|
||||||
|
import { getFilteredObjects } from "./selectors"
|
||||||
|
|
||||||
export class ObjectsListContainer extends React.Component {
|
export class ObjectsListContainer extends React.Component {
|
||||||
constructor(props) {
|
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() {
|
loadNextPage() {
|
||||||
this.setState(state => {
|
this.setState(state => {
|
||||||
return { page: state.page + 1 }
|
return { page: state.page + 1 }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
render() {
|
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 (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
pageStart={0}
|
pageStart={0}
|
||||||
loadMore={this.loadNextPage}
|
loadMore={this.loadNextPage}
|
||||||
hasMore={objects.length > visibleObjects.length}
|
hasMore={filteredObjects.length > visibleObjects.length}
|
||||||
useWindow={true}
|
useWindow={true}
|
||||||
initialLoad={false}
|
initialLoad={false}
|
||||||
>
|
>
|
||||||
@ -70,7 +78,8 @@ const mapStateToProps = state => {
|
|||||||
return {
|
return {
|
||||||
currentBucket: state.buckets.currentBucket,
|
currentBucket: state.buckets.currentBucket,
|
||||||
currentPrefix: state.objects.currentPrefix,
|
currentPrefix: state.objects.currentPrefix,
|
||||||
objects: state.objects.list,
|
filteredObjects: getFilteredObjects(state),
|
||||||
|
filter: state.objects.filter,
|
||||||
sortBy: state.objects.sortBy,
|
sortBy: state.objects.sortBy,
|
||||||
sortOrder: state.objects.sortOrder,
|
sortOrder: state.objects.sortOrder,
|
||||||
listLoading: state.objects.listLoading
|
listLoading: state.objects.listLoading
|
||||||
|
43
browser/app/js/objects/ObjectsSearch.js
Normal file
43
browser/app/js/objects/ObjectsSearch.js
Normal file
@ -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 }) => (
|
||||||
|
<div
|
||||||
|
className="input-group ig-left ig-search-dark"
|
||||||
|
style={{ display: "block" }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="ig-text"
|
||||||
|
type="input"
|
||||||
|
placeholder="Search Objects..."
|
||||||
|
onChange={e => onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
<i className="ig-helpers" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
onChange: filter =>
|
||||||
|
dispatch(actionsObjects.setFilter(filter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(undefined, mapDispatchToProps)(ObjectsSearch)
|
@ -20,13 +20,13 @@ import { ObjectsListContainer } from "../ObjectsListContainer"
|
|||||||
|
|
||||||
describe("ObjectsList", () => {
|
describe("ObjectsList", () => {
|
||||||
it("should render without crashing", () => {
|
it("should render without crashing", () => {
|
||||||
shallow(<ObjectsListContainer objects={[]} />)
|
shallow(<ObjectsListContainer filteredObjects={[]} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should render ObjectsList with objects", () => {
|
it("should render ObjectsList with objects", () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectsListContainer
|
<ObjectsListContainer
|
||||||
objects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]}
|
filteredObjects={[{ name: "test1.jpg" }, { name: "test2.jpg" }]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
expect(wrapper.find("ObjectsList").length).toBe(1)
|
expect(wrapper.find("ObjectsList").length).toBe(1)
|
||||||
@ -40,7 +40,7 @@ describe("ObjectsList", () => {
|
|||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<ObjectsListContainer
|
<ObjectsListContainer
|
||||||
currentBucket="test1"
|
currentBucket="test1"
|
||||||
objects={[]}
|
filteredObjects={[]}
|
||||||
listLoading={true}
|
listLoading={true}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
32
browser/app/js/objects/__tests__/ObjectsSearch.test.js
Normal file
32
browser/app/js/objects/__tests__/ObjectsSearch.test.js
Normal file
@ -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(<ObjectsSearch />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should call onChange with search text", () => {
|
||||||
|
const onChange = jest.fn()
|
||||||
|
const wrapper = shallow(<ObjectsSearch onChange={onChange} />)
|
||||||
|
wrapper.find("input").simulate("change", { target: { value: "test" } })
|
||||||
|
expect(onChange).toHaveBeenCalledWith("test")
|
||||||
|
})
|
||||||
|
})
|
@ -23,6 +23,7 @@ describe("objects reducer", () => {
|
|||||||
const initialState = reducer(undefined, {})
|
const initialState = reducer(undefined, {})
|
||||||
expect(initialState).toEqual({
|
expect(initialState).toEqual({
|
||||||
list: [],
|
list: [],
|
||||||
|
filter: "",
|
||||||
listLoading: false,
|
listLoading: false,
|
||||||
sortBy: "",
|
sortBy: "",
|
||||||
sortOrder: SORT_ORDER_ASC,
|
sortOrder: SORT_ORDER_ASC,
|
||||||
|
@ -36,6 +36,7 @@ import { getServerInfo, hasServerPublicDomain } from '../browser/selectors'
|
|||||||
|
|
||||||
export const SET_LIST = "objects/SET_LIST"
|
export const SET_LIST = "objects/SET_LIST"
|
||||||
export const RESET_LIST = "objects/RESET_LIST"
|
export const RESET_LIST = "objects/RESET_LIST"
|
||||||
|
export const SET_FILTER = "objects/SET_FILTER"
|
||||||
export const APPEND_LIST = "objects/APPEND_LIST"
|
export const APPEND_LIST = "objects/APPEND_LIST"
|
||||||
export const REMOVE = "objects/REMOVE"
|
export const REMOVE = "objects/REMOVE"
|
||||||
export const SET_SORT_BY = "objects/SET_SORT_BY"
|
export const SET_SORT_BY = "objects/SET_SORT_BY"
|
||||||
@ -57,6 +58,13 @@ export const resetList = () => ({
|
|||||||
type: RESET_LIST,
|
type: RESET_LIST,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const setFilter = filter => {
|
||||||
|
return {
|
||||||
|
type: SET_FILTER,
|
||||||
|
filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const setListLoading = (listLoading) => ({
|
export const setListLoading = (listLoading) => ({
|
||||||
type: SET_LIST_LOADING,
|
type: SET_LIST_LOADING,
|
||||||
listLoading,
|
listLoading,
|
||||||
|
@ -28,6 +28,7 @@ const removeObject = (list, objectToRemove, lookup) => {
|
|||||||
export default (
|
export default (
|
||||||
state = {
|
state = {
|
||||||
list: [],
|
list: [],
|
||||||
|
filter: "",
|
||||||
listLoading: false,
|
listLoading: false,
|
||||||
sortBy: "",
|
sortBy: "",
|
||||||
sortOrder: SORT_ORDER_ASC,
|
sortOrder: SORT_ORDER_ASC,
|
||||||
@ -53,6 +54,11 @@ export default (
|
|||||||
...state,
|
...state,
|
||||||
list: []
|
list: []
|
||||||
}
|
}
|
||||||
|
case actionsObjects.SET_FILTER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
filter: action.filter
|
||||||
|
}
|
||||||
case actionsObjects.SET_LIST_LOADING:
|
case actionsObjects.SET_LIST_LOADING:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -21,3 +21,13 @@ export const getCurrentPrefix = state => state.objects.currentPrefix
|
|||||||
export const getCheckedList = state => state.objects.checkedList
|
export const getCheckedList = state => state.objects.checkedList
|
||||||
|
|
||||||
export const getPrefixWritable = state => state.objects.prefixWritable
|
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()))
|
||||||
|
)
|
@ -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 {
|
.ig-search {
|
||||||
&:before {
|
&:before {
|
||||||
font-family: @font-family-icon;
|
font-family: @font-family-icon;
|
||||||
@ -270,4 +288,4 @@ select.form-control {
|
|||||||
.set-expire-decrease {
|
.set-expire-decrease {
|
||||||
bottom: -27px;
|
bottom: -27px;
|
||||||
.rotate(-180deg);
|
.rotate(-180deg);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user