From feb726dd98c2ee0f971b8d05b22bba6f3363f87c Mon Sep 17 00:00:00 2001 From: Kanagaraj M Date: Sat, 10 Feb 2018 08:04:14 +0530 Subject: [PATCH] Refactor of components (#5499) --- .../app/js/actions/__tests__/buckets.test.js | 16 +- .../app/js/actions/__tests__/common.test.js | 41 +++++ .../app/js/actions/__tests__/objects.test.js | 171 ++++++++++++++++++ browser/app/js/actions/buckets.js | 11 ++ browser/app/js/actions/common.js | 46 +++++ browser/app/js/actions/objects.js | 126 +++++++++++++ browser/app/js/components/Browser.js | 2 + browser/app/js/components/BucketContainer.js | 2 +- browser/app/js/components/Header.js | 28 +++ browser/app/js/components/MainContent.js | 30 +++ browser/app/js/components/MobileHeader.js | 60 ++++++ browser/app/js/components/ObjectContainer.js | 35 ++++ browser/app/js/components/ObjectItem.js | 58 ++++++ browser/app/js/components/ObjectsHeader.js | 99 ++++++++++ browser/app/js/components/ObjectsList.js | 88 ++------- .../app/js/components/ObjectsListContainer.js | 75 ++++++++ browser/app/js/components/ObjectsSection.js | 28 +++ browser/app/js/components/Path.js | 57 ++++-- browser/app/js/components/PrefixContainer.js | 45 +++++ browser/app/js/components/SideBar.js | 22 ++- browser/app/js/components/StorageInfo.js | 64 +++++++ .../components/__tests__/MobileHeader.test.js | 36 ++++ .../__tests__/ObjectsHeader.test.js | 61 +++++++ .../components/__tests__/ObjectsList.test.js | 39 ++++ .../__tests__/ObjectsListContainer.test.js | 57 ++++++ .../components/__tests__/ObjetctItem.test.js | 37 ++++ .../app/js/components/__tests__/Path.test.js | 72 ++++++++ .../__tests__/PrefixContainer.test.js | 44 +++++ .../components/__tests__/StorageInfo.test.js | 41 +++++ browser/app/js/constants.js | 6 +- browser/app/js/history.js | 24 +++ .../app/js/reducers/__tests__/buckets.test.js | 39 ++-- .../app/js/reducers/__tests__/common.test.js | 70 +++++++ .../app/js/reducers/__tests__/objects.test.js | 97 ++++++++++ browser/app/js/reducers/common.js | 39 ++++ browser/app/js/reducers/index.js | 6 +- browser/app/js/reducers/objects.js | 65 +++++++ browser/app/less/inc/header.less | 7 + browser/package.json | 6 +- browser/yarn.lock | 10 + 40 files changed, 1733 insertions(+), 127 deletions(-) create mode 100644 browser/app/js/actions/__tests__/common.test.js create mode 100644 browser/app/js/actions/__tests__/objects.test.js create mode 100644 browser/app/js/actions/common.js create mode 100644 browser/app/js/actions/objects.js create mode 100644 browser/app/js/components/Header.js create mode 100644 browser/app/js/components/MainContent.js create mode 100644 browser/app/js/components/MobileHeader.js create mode 100644 browser/app/js/components/ObjectContainer.js create mode 100644 browser/app/js/components/ObjectItem.js create mode 100644 browser/app/js/components/ObjectsHeader.js create mode 100644 browser/app/js/components/ObjectsListContainer.js create mode 100644 browser/app/js/components/ObjectsSection.js create mode 100644 browser/app/js/components/PrefixContainer.js create mode 100644 browser/app/js/components/StorageInfo.js create mode 100644 browser/app/js/components/__tests__/MobileHeader.test.js create mode 100644 browser/app/js/components/__tests__/ObjectsHeader.test.js create mode 100644 browser/app/js/components/__tests__/ObjectsList.test.js create mode 100644 browser/app/js/components/__tests__/ObjectsListContainer.test.js create mode 100644 browser/app/js/components/__tests__/ObjetctItem.test.js create mode 100644 browser/app/js/components/__tests__/Path.test.js create mode 100644 browser/app/js/components/__tests__/PrefixContainer.test.js create mode 100644 browser/app/js/components/__tests__/StorageInfo.test.js create mode 100644 browser/app/js/history.js create mode 100644 browser/app/js/reducers/__tests__/common.test.js create mode 100644 browser/app/js/reducers/__tests__/objects.test.js create mode 100644 browser/app/js/reducers/common.js create mode 100644 browser/app/js/reducers/objects.js diff --git a/browser/app/js/actions/__tests__/buckets.test.js b/browser/app/js/actions/__tests__/buckets.test.js index 6218103f7..36bb6ba9f 100644 --- a/browser/app/js/actions/__tests__/buckets.test.js +++ b/browser/app/js/actions/__tests__/buckets.test.js @@ -28,14 +28,26 @@ const middlewares = [thunk] const mockStore = configureStore(middlewares) describe("Buckets actions", () => { - it("creates buckets/SET_LIST after fetching the buckets", () => { + it("creates buckets/SET_LIST and buckets/SET_CURRENT_BUCKET after fetching the buckets", () => { const store = mockStore() const expectedActions = [ - { type: "buckets/SET_LIST", buckets: ["test1", "test2"] } + { type: "buckets/SET_LIST", buckets: ["test1", "test2"] }, + { type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" } ] return store.dispatch(actionsBuckets.fetchBuckets()).then(() => { const actions = store.getActions() expect(actions).toEqual(expectedActions) }) }) + + it("should update browser url and creates buckets/SET_CURRENT_BUCKET action when selectBucket is called", () => { + const store = mockStore() + const expectedActions = [ + { type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" } + ] + store.dispatch(actionsBuckets.selectBucket("test1")) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + expect(window.location.pathname).toBe("/test1") + }) }) diff --git a/browser/app/js/actions/__tests__/common.test.js b/browser/app/js/actions/__tests__/common.test.js new file mode 100644 index 000000000..caf7a8800 --- /dev/null +++ b/browser/app/js/actions/__tests__/common.test.js @@ -0,0 +1,41 @@ +/* + * 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 configureStore from "redux-mock-store" +import thunk from "redux-thunk" +import * as actionsCommon from "../common" + +jest.mock("../../web", () => ({ + StorageInfo: jest.fn(() => { + return Promise.resolve({ storageInfo: { Total: 100, Free: 60 } }) + }) +})) + +const middlewares = [thunk] +const mockStore = configureStore(middlewares) + +describe("Common actions", () => { + it("creates common/SET_STORAGE_INFO after fetching the storage details ", () => { + const store = mockStore() + const expectedActions = [ + { type: "common/SET_STORAGE_INFO", storageInfo: { total: 100, free: 60 } } + ] + return store.dispatch(actionsCommon.fetchStorageInfo()).then(() => { + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + }) +}) diff --git a/browser/app/js/actions/__tests__/objects.test.js b/browser/app/js/actions/__tests__/objects.test.js new file mode 100644 index 000000000..ef1de722e --- /dev/null +++ b/browser/app/js/actions/__tests__/objects.test.js @@ -0,0 +1,171 @@ +/* + * 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 configureStore from "redux-mock-store" +import thunk from "redux-thunk" +import * as actionsObjects from "../objects" + +jest.mock("../../web", () => ({ + ListObjects: jest.fn(() => { + return Promise.resolve({ + objects: [{ name: "test1" }, { name: "test2" }], + istruncated: false, + nextmarker: "test2" + }) + }) +})) + +const middlewares = [thunk] +const mockStore = configureStore(middlewares) + +describe("Objects actions", () => { + it("creates objects/SET_LIST action", () => { + const store = mockStore() + const expectedActions = [ + { + type: "objects/SET_LIST", + objects: [{ name: "test1" }, { name: "test2" }], + isTruncated: false, + marker: "test2" + } + ] + store.dispatch( + actionsObjects.setList( + [{ name: "test1" }, { name: "test2" }], + "test2", + false + ) + ) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + + it("creates objects/SET_SORT_BY action", () => { + const store = mockStore() + const expectedActions = [ + { + type: "objects/SET_SORT_BY", + sortBy: "name" + } + ] + store.dispatch(actionsObjects.setSortBy("name")) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + + it("creates objects/SET_SORT_ORDER action", () => { + const store = mockStore() + const expectedActions = [ + { + type: "objects/SET_SORT_ORDER", + sortOrder: true + } + ] + store.dispatch(actionsObjects.setSortOrder(true)) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + + it("creates objects/SET_LIST after fetching the objects", () => { + const store = mockStore({ + buckets: { currentBucket: "bk1" }, + objects: { currentPrefix: "" } + }) + const expectedActions = [ + { + type: "objects/SET_LIST", + objects: [{ name: "test1" }, { name: "test2" }], + marker: "test2", + isTruncated: false + }, + { + type: "objects/SET_SORT_BY", + sortBy: "" + }, + { + type: "objects/SET_SORT_ORDER", + sortOrder: false + } + ] + return store.dispatch(actionsObjects.fetchObjects()).then(() => { + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + }) + + it("creates objects/APPEND_LIST after fetching more objects", () => { + const store = mockStore({ + buckets: { currentBucket: "bk1" }, + objects: { currentPrefix: "" } + }) + const expectedActions = [ + { + type: "objects/APPEND_LIST", + objects: [{ name: "test1" }, { name: "test2" }], + marker: "test2", + isTruncated: false + } + ] + return store.dispatch(actionsObjects.fetchObjects(true)).then(() => { + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + }) + + it("creates objects/SET_SORT_BY and objects/SET_SORT_ORDER when sortObjects is called", () => { + const store = mockStore({ + objects: { + list: [], + sortBy: "", + sortOrder: false, + isTruncated: false, + marker: "" + } + }) + const expectedActions = [ + { + type: "objects/SET_SORT_BY", + sortBy: "name" + }, + { + type: "objects/SET_SORT_ORDER", + sortOrder: true + }, + { + type: "objects/SET_LIST", + objects: [], + isTruncated: false, + marker: "" + } + ] + store.dispatch(actionsObjects.sortObjects("name")) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + + it("should update browser url and creates objects/SET_CURRENT_PREFIX action when selectPrefix is called", () => { + const store = mockStore({ + buckets: { currentBucket: "test" } + }) + const expectedActions = [ + { type: "objects/SET_CURRENT_PREFIX", prefix: "abc/" } + ] + store.dispatch(actionsObjects.selectPrefix("abc/")) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + expect(window.location.pathname.endsWith("/test/abc/")).toBeTruthy() + }) +}) diff --git a/browser/app/js/actions/buckets.js b/browser/app/js/actions/buckets.js index 74866ea00..01b40f778 100644 --- a/browser/app/js/actions/buckets.js +++ b/browser/app/js/actions/buckets.js @@ -15,6 +15,7 @@ */ import web from "../web" +import history from "../history" export const SET_LIST = "buckets/SET_LIST" export const SET_FILTER = "buckets/SET_FILTER" @@ -25,6 +26,9 @@ export const fetchBuckets = () => { return web.ListBuckets().then(res => { const buckets = res.buckets ? res.buckets.map(bucket => bucket.name) : [] dispatch(setList(buckets)) + if (buckets.length > 0) { + dispatch(selectBucket(buckets[0])) + } }) } } @@ -43,6 +47,13 @@ export const setFilter = filter => { } } +export const selectBucket = bucket => { + return function(dispatch) { + dispatch(setCurrentBucket(bucket)) + history.push(`/${bucket}`) + } +} + export const setCurrentBucket = bucket => { return { type: SET_CURRENT_BUCKET, diff --git a/browser/app/js/actions/common.js b/browser/app/js/actions/common.js new file mode 100644 index 000000000..0164f7573 --- /dev/null +++ b/browser/app/js/actions/common.js @@ -0,0 +1,46 @@ +/* + * 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 web from "../web" + +export const TOGGLE_SIDEBAR = "common/TOGGLE_SIDEBAR" +export const CLOSE_SIDEBAR = "common/CLOSE_SIDEBAR" +export const SET_STORAGE_INFO = "common/SET_STORAGE_INFO" + +export const toggleSidebar = () => ({ + type: TOGGLE_SIDEBAR +}) + +export const closeSidebar = () => ({ + type: CLOSE_SIDEBAR +}) + +export const fetchStorageInfo = () => { + return function(dispatch) { + return web.StorageInfo().then(res => { + const storageInfo = { + total: res.storageInfo.Total, + free: res.storageInfo.Free + } + dispatch(setStorageInfo(storageInfo)) + }) + } +} + +export const setStorageInfo = storageInfo => ({ + type: SET_STORAGE_INFO, + storageInfo +}) diff --git a/browser/app/js/actions/objects.js b/browser/app/js/actions/objects.js new file mode 100644 index 000000000..736a192e1 --- /dev/null +++ b/browser/app/js/actions/objects.js @@ -0,0 +1,126 @@ +/* + * 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 web from "../web" +import history from "../history" +import { + sortObjectsByName, + sortObjectsBySize, + sortObjectsByDate +} from "../utils" + +export const SET_LIST = "objects/SET_LIST" +export const APPEND_LIST = "objects/APPEND_LIST" +export const SET_SORT_BY = "objects/SET_SORT_BY" +export const SET_SORT_ORDER = "objects/SET_SORT_ORDER" +export const SET_CURRENT_PREFIX = "objects/SET_CURRENT_PREFIX" + +export const setList = (objects, marker, isTruncated) => ({ + type: SET_LIST, + objects, + marker, + isTruncated +}) + +export const appendList = (objects, marker, isTruncated) => ({ + type: APPEND_LIST, + objects, + marker, + isTruncated +}) + +export const fetchObjects = append => { + return function(dispatch, getState) { + const { + buckets: { currentBucket }, + objects: { currentPrefix, marker } + } = getState() + return web + .ListObjects({ + bucketName: currentBucket, + prefix: currentPrefix, + marker: marker + }) + .then(res => { + let objects = [] + if (res.objects) { + objects = res.objects.map(object => { + return { + ...object, + name: object.name.replace(currentPrefix, "") + } + }) + } + if (append) { + dispatch(appendList(objects, res.nextmarker, res.istruncated)) + } else { + dispatch(setList(objects, res.nextmarker, res.istruncated)) + dispatch(setSortBy("")) + dispatch(setSortOrder(false)) + } + }) + } +} + +export const sortObjects = sortBy => { + return function(dispatch, getState) { + const { objects } = getState() + const sortOrder = objects.sortBy == sortBy ? !objects.sortOrder : true + dispatch(setSortBy(sortBy)) + dispatch(setSortOrder(sortOrder)) + let list + switch (sortBy) { + case "name": + list = sortObjectsByName(objects.list, sortOrder) + break + case "size": + list = sortObjectsBySize(objects.list, sortOrder) + break + case "last-modified": + list = sortObjectsByDate(objects.list, sortOrder) + break + default: + list = objects.list + break + } + dispatch(setList(list, objects.marker, objects.isTruncated)) + } +} + +export const setSortBy = sortBy => ({ + type: SET_SORT_BY, + sortBy +}) + +export const setSortOrder = sortOrder => ({ + type: SET_SORT_ORDER, + sortOrder +}) + +export const selectPrefix = prefix => { + return function(dispatch, getState) { + dispatch(setCurrentPrefix(prefix)) + const currentBucket = getState().buckets.currentBucket + history.replace(`/${currentBucket}/${prefix}`) + } +} + +export const setCurrentPrefix = prefix => { + return { + type: SET_CURRENT_PREFIX, + prefix + } +} diff --git a/browser/app/js/components/Browser.js b/browser/app/js/components/Browser.js index 948eefa6e..f2fb5c5f9 100644 --- a/browser/app/js/components/Browser.js +++ b/browser/app/js/components/Browser.js @@ -18,6 +18,7 @@ import React from "react" import classNames from "classnames" import { connect } from "react-redux" import SideBar from "./SideBar" +import MainContent from "./MainContent" class Browser extends React.Component { render() { @@ -28,6 +29,7 @@ class Browser extends React.Component { })} > + ) } diff --git a/browser/app/js/components/BucketContainer.js b/browser/app/js/components/BucketContainer.js index 9469407a3..9f0324b9b 100644 --- a/browser/app/js/components/BucketContainer.js +++ b/browser/app/js/components/BucketContainer.js @@ -28,7 +28,7 @@ const mapStateToProps = (state, ownProps) => { const mapDispatchToProps = dispatch => { return { - selectBucket: bucket => dispatch(actionsBuckets.setCurrentBucket(bucket)) + selectBucket: bucket => dispatch(actionsBuckets.selectBucket(bucket)) } } diff --git a/browser/app/js/components/Header.js b/browser/app/js/components/Header.js new file mode 100644 index 000000000..e82d2a2f2 --- /dev/null +++ b/browser/app/js/components/Header.js @@ -0,0 +1,28 @@ +/* + * 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 Path from "./Path" +import StorageInfo from "./StorageInfo" + +export const Header = () => ( +
+ + +
+) + +export default Header diff --git a/browser/app/js/components/MainContent.js b/browser/app/js/components/MainContent.js new file mode 100644 index 000000000..55f77a335 --- /dev/null +++ b/browser/app/js/components/MainContent.js @@ -0,0 +1,30 @@ +/* + * 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 MobileHeader from "./MobileHeader" +import Header from "./Header" +import ObjectsSection from "./ObjectsSection" + +export const MainContent = () => ( +
+ +
+ +
+) + +export default MainContent diff --git a/browser/app/js/components/MobileHeader.js b/browser/app/js/components/MobileHeader.js new file mode 100644 index 000000000..d93c0744e --- /dev/null +++ b/browser/app/js/components/MobileHeader.js @@ -0,0 +1,60 @@ +/* + * 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 classNames from "classnames" +import { connect } from "react-redux" +import logo from "../../img/logo.svg" +import * as actionsCommon from "../actions/common" + +export const MobileHeader = ({ sidebarOpen, toggleSidebar }) => ( +
+
+) + +const mapStateToProps = state => { + return { + sidebarOpen: state.common.sidebarOpen + } +} + +const mapDispatchToProps = dispatch => { + return { + toggleSidebar: () => dispatch(actionsCommon.toggleSidebar()) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(MobileHeader) diff --git a/browser/app/js/components/ObjectContainer.js b/browser/app/js/components/ObjectContainer.js new file mode 100644 index 000000000..f61bb654d --- /dev/null +++ b/browser/app/js/components/ObjectContainer.js @@ -0,0 +1,35 @@ +/* + * 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 humanize from "humanize" +import Moment from "moment" +import ObjectItem from "./ObjectItem" +import * as actionsObjects from "../actions/objects" + +export const ObjectContainer = ({ object }) => { + const actionButtons = [] + const props = { + name: object.name, + contentType: object.contentType, + size: humanize.filesize(object.size), + lastModified: Moment(object.lastModified).format("lll"), + actionButtons: [] + } + return +} + +export default ObjectContainer diff --git a/browser/app/js/components/ObjectItem.js b/browser/app/js/components/ObjectItem.js new file mode 100644 index 000000000..edce808c5 --- /dev/null +++ b/browser/app/js/components/ObjectItem.js @@ -0,0 +1,58 @@ +/* + * 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 { connect } from "react-redux" +import humanize from "humanize" +import Moment from "moment" +import { getDataType } from "../mime" + +export const ObjectItem = ({ + name, + contentType, + size, + lastModified, + actionButtons, + onClick +}) => { + return ( +
+
+
+ + + +
+
+ +
{size}
+
{lastModified}
+
{actionButtons}
+
+ ) +} + +export default ObjectItem diff --git a/browser/app/js/components/ObjectsHeader.js b/browser/app/js/components/ObjectsHeader.js new file mode 100644 index 000000000..30964854e --- /dev/null +++ b/browser/app/js/components/ObjectsHeader.js @@ -0,0 +1,99 @@ +/* + * 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 classNames from "classnames" +import { connect } from "react-redux" +import * as actionsObjects from "../actions/objects" + +export const ObjectsHeader = ({ + sortNameOrder, + sortSizeOrder, + sortLastModifiedOrder, + sortObjects +}) => ( +
+
+
+
sortObjects("name")} + data-sort="name" + > + Name + +
+
sortObjects("size")} + data-sort="size" + > + Size + +
+
sortObjects("last-modified")} + data-sort="last-modified" + > + Last Modified + +
+
+
+
+) + +const mapStateToProps = state => { + return { + sortNameOrder: state.objects.sortBy == "name" && state.objects.sortOrder, + sortSizeOrder: state.objects.sortBy == "size" && state.objects.sortOrder, + sortLastModifiedOrder: + state.objects.sortBy == "last-modified" && state.objects.sortOrder + } +} + +const mapDispatchToProps = dispatch => { + return { + sortObjects: sortBy => dispatch(actionsObjects.sortObjects(sortBy)) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(ObjectsHeader) diff --git a/browser/app/js/components/ObjectsList.js b/browser/app/js/components/ObjectsList.js index acba46238..75bbaa6e0 100644 --- a/browser/app/js/components/ObjectsList.js +++ b/browser/app/js/components/ObjectsList.js @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage (C) 2016 Minio, Inc. + * Minio Cloud Storage (C) 2016, 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. @@ -14,83 +14,19 @@ * limitations under the License. */ -import React from 'react' -import Moment from 'moment' -import humanize from 'humanize' -import connect from 'react-redux/lib/components/connect' -import Dropdown from 'react-bootstrap/lib/Dropdown' +import React from "react" +import ObjectContainer from "./ObjectContainer" +import PrefixContainer from "./PrefixContainer" -let ObjectsList = ({objects, currentPath, selectPrefix, dataType, showDeleteConfirmation, shareObject, loadPath, checkObject, checkedObjectsArray}) => { - const list = objects.map((object, i) => { - let size = object.name.endsWith('/') ? '-' : humanize.filesize(object.size) - let lastModified = object.name.endsWith('/') ? '-' : Moment(object.lastModified).format('lll') - let loadingClass = loadPath === `${currentPath}${object.name}` ? 'fesl-loading' : '' - let actionButtons = '' - let deleteButton = '' - if (web.LoggedIn()) - deleteButton = showDeleteConfirmation(e, `${currentPath}${object.name}`) }> - - if (!checkedObjectsArray.length > 0) { - if (!object.name.endsWith('/')) { - actionButtons = - - - shareObject(e, `${currentPath}${object.name}`) }> - { deleteButton } - - - } +export const ObjectsList = ({ objects }) => { + const list = objects.map(object => { + if (object.name.endsWith("/")) { + return + } else { + return } - - let activeClass = '' - let isChecked = '' - - if (checkedObjectsArray.indexOf(object.name) > -1) { - activeClass = ' fesl-row-selected' - isChecked = true - } - - return ( -
-
-
- checkObject(e, object.name) } /> - - -
-
- -
- { size } -
-
- { lastModified } -
-
- { actionButtons } -
-
- ) }) - return ( -
- { list } -
- ) + return
{list}
} -// Subscribe it to state changes. -export default connect(state => { - return { - objects: state.objects, - currentPath: state.currentPath, - loadPath: state.loadPath - } -})(ObjectsList) \ No newline at end of file +export default ObjectsList diff --git a/browser/app/js/components/ObjectsListContainer.js b/browser/app/js/components/ObjectsListContainer.js new file mode 100644 index 000000000..c7af13b5a --- /dev/null +++ b/browser/app/js/components/ObjectsListContainer.js @@ -0,0 +1,75 @@ +/* + * 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 classNames from "classnames" +import { connect } from "react-redux" +import InfiniteScroll from "react-infinite-scroller" +import * as actionsObjects from "../actions/objects" +import ObjectsList from "./ObjectsList" + +export class ObjectsListContainer extends React.Component { + componentWillReceiveProps(nextProps) { + const { currentBucket, currentPrefix, loadObjects } = this.props + if ( + currentBucket != nextProps.currentBucket || + currentPrefix != nextProps.currentPrefix + ) { + loadObjects() + } + } + render() { + const { objects, isTruncated, currentBucket, loadObjects } = this.props + return ( +
+ loadObjects(true)} + hasMore={isTruncated} + useWindow={true} + initialLoad={false} + > + + +
+ Loading... +
+
+ ) + } +} + +const mapStateToProps = state => { + return { + currentBucket: state.buckets.currentBucket, + currentPrefix: state.objects.currentPrefix, + objects: state.objects.list, + isTruncated: state.objects.isTruncated + } +} + +const mapDispatchToProps = dispatch => { + return { + loadObjects: append => dispatch(actionsObjects.fetchObjects(append)) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)( + ObjectsListContainer +) diff --git a/browser/app/js/components/ObjectsSection.js b/browser/app/js/components/ObjectsSection.js new file mode 100644 index 000000000..f37d791aa --- /dev/null +++ b/browser/app/js/components/ObjectsSection.js @@ -0,0 +1,28 @@ +/* + * 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 ObjectsHeader from "./ObjectsHeader" +import ObjectsListContainer from "./ObjectsListContainer" + +export const ObjectsSection = () => ( +
+ + +
+) + +export default ObjectsSection diff --git a/browser/app/js/components/Path.js b/browser/app/js/components/Path.js index 55484da70..cd4e128fa 100644 --- a/browser/app/js/components/Path.js +++ b/browser/app/js/components/Path.js @@ -14,28 +14,57 @@ * limitations under the License. */ -import React from 'react' -import connect from 'react-redux/lib/components/connect' +import React from "react" +import { connect } from "react-redux" +import { getCurrentBucket, getCurrentPrefix } from "../selectors/buckets" +import * as actionsObjects from "../actions/objects" -let Path = ({currentBucket, currentPath, selectPrefix}) => { +export const Path = ({ currentBucket, currentPrefix, selectPrefix }) => { + const onPrefixClick = (e, prefix) => { + e.preventDefault() + selectPrefix(prefix) + } let dirPath = [] - let path = '' - if (currentPath) { - path = currentPath.split('/').map((dir, i) => { - dirPath.push(dir) - let dirPath_ = dirPath.join('/') + '/' - return selectPrefix(e, dirPath_) }>{ dir } + let path = "" + if (currentPrefix) { + path = currentPrefix.split("/").map((dir, i) => { + if (dir) { + dirPath.push(dir) + let dirPath_ = dirPath.join("/") + "/" + return ( + + onPrefixClick(e, dirPath_)}> + {dir} + + + ) + } }) } return ( -

selectPrefix(e, '') } href="">{ currentBucket }{ path }

+

+ + onPrefixClick(e, "")} href=""> + {currentBucket} + + + {path} +

) } -export default connect(state => { +const mapStateToProps = state => { return { - currentBucket: state.currentBucket, - currentPath: state.currentPath + currentBucket: getCurrentBucket(state), + currentPrefix: state.objects.currentPrefix } -})(Path) +} + +const mapDispatchToProps = dispatch => { + return { + selectPrefix: prefix => dispatch(actionsObjects.selectPrefix(prefix)) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Path) diff --git a/browser/app/js/components/PrefixContainer.js b/browser/app/js/components/PrefixContainer.js new file mode 100644 index 000000000..07ad1b4ac --- /dev/null +++ b/browser/app/js/components/PrefixContainer.js @@ -0,0 +1,45 @@ +/* + * 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 { connect } from "react-redux" +import ObjectItem from "./ObjectItem" +import * as actionsObjects from "../actions/objects" + +export const PrefixContainer = ({ object, currentPrefix, selectPrefix }) => { + const props = { + name: object.name, + contentType: object.contentType, + onClick: () => selectPrefix(`${currentPrefix}${object.name}`) + } + + return +} + +const mapStateToProps = (state, ownProps) => { + return { + object: ownProps.object, + currentPrefix: state.objects.currentPrefix + } +} + +const mapDispatchToProps = dispatch => { + return { + selectPrefix: prefix => dispatch(actionsObjects.selectPrefix(prefix)) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(PrefixContainer) diff --git a/browser/app/js/components/SideBar.js b/browser/app/js/components/SideBar.js index bb1607ef1..975dd9357 100644 --- a/browser/app/js/components/SideBar.js +++ b/browser/app/js/components/SideBar.js @@ -24,13 +24,15 @@ import Dropdown from "react-bootstrap/lib/Dropdown" import BucketSearch from "./BucketSearch" import BucketList from "./BucketList" import Host from "./Host" +import * as actionsCommon from "../actions/common" -export const SideBar = () => { +export const SideBar = ({ sidebarOpen, clickOutside }) => { return ( - +
@@ -47,4 +49,16 @@ export const SideBar = () => { ) } -export default connect(state => state)(SideBar) +const mapStateToProps = state => { + return { + sidebarOpen: state.common.sidebarOpen + } +} + +const mapDispatchToProps = dispatch => { + return { + clickOutside: () => dispatch(actionsCommon.closeSidebar()) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(SideBar) diff --git a/browser/app/js/components/StorageInfo.js b/browser/app/js/components/StorageInfo.js new file mode 100644 index 000000000..f0e5e0c02 --- /dev/null +++ b/browser/app/js/components/StorageInfo.js @@ -0,0 +1,64 @@ +/* + * 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 { connect } from "react-redux" +import humanize from "humanize" +import * as actionsCommon from "../actions/common" + +export class StorageInfo extends React.Component { + componentWillMount() { + const { fetchStorageInfo } = this.props + fetchStorageInfo() + } + render() { + const { total, free } = this.props.storageInfo + const used = total - free + const usedPercent = used / total * 100 + "%" + const freePercent = free * 100 / total + return ( +
+
+
+
+
    +
  • + Used: + {humanize.filesize(total - free)} +
  • +
  • + Free: + {humanize.filesize(total - used)} +
  • +
+
+ ) + } +} + +const mapStateToProps = state => { + return { + storageInfo: state.common.storageInfo + } +} + +const mapDispatchToProps = dispatch => { + return { + fetchStorageInfo: () => dispatch(actionsCommon.fetchStorageInfo()) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(StorageInfo) diff --git a/browser/app/js/components/__tests__/MobileHeader.test.js b/browser/app/js/components/__tests__/MobileHeader.test.js new file mode 100644 index 000000000..d6dbd92e0 --- /dev/null +++ b/browser/app/js/components/__tests__/MobileHeader.test.js @@ -0,0 +1,36 @@ +/* + * 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 { MobileHeader } from "../MobileHeader" + +describe("Bucket", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should toggleSidebar when trigger is clicked", () => { + const toggleSidebar = jest.fn() + const wrapper = shallow( + + ) + wrapper + .find("#sidebar-toggle") + .simulate("click", { stopPropagation: jest.fn() }) + expect(toggleSidebar).toHaveBeenCalled() + }) +}) diff --git a/browser/app/js/components/__tests__/ObjectsHeader.test.js b/browser/app/js/components/__tests__/ObjectsHeader.test.js new file mode 100644 index 000000000..4bf6c8485 --- /dev/null +++ b/browser/app/js/components/__tests__/ObjectsHeader.test.js @@ -0,0 +1,61 @@ +/* + * 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 { ObjectsHeader } from "../ObjectsHeader" + +describe("ObjectsHeader", () => { + it("should render without crashing", () => { + const sortObjects = jest.fn() + shallow() + }) + + it("should render columns with asc classes by default", () => { + const sortObjects = jest.fn() + const wrapper = shallow() + expect( + wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-asc") + ).toBeTruthy() + expect( + wrapper.find("#sort-by-size i").hasClass("fa-sort-amount-asc") + ).toBeTruthy() + expect( + wrapper.find("#sort-by-last-modified i").hasClass("fa-sort-numeric-asc") + ).toBeTruthy() + }) + + it("should render name column with desc class when objects are sorted by name", () => { + const sortObjects = jest.fn() + const wrapper = shallow( + + ) + expect( + wrapper.find("#sort-by-name i").hasClass("fa-sort-alpha-desc") + ).toBeTruthy() + }) + + it("should call sortObjects when a column is clicked", () => { + const sortObjects = jest.fn() + const wrapper = shallow() + wrapper.find("#sort-by-name").simulate("click") + expect(sortObjects).toHaveBeenCalledWith("name") + wrapper.find("#sort-by-size").simulate("click") + expect(sortObjects).toHaveBeenCalledWith("size") + wrapper.find("#sort-by-last-modified").simulate("click") + expect(sortObjects).toHaveBeenCalledWith("last-modified") + }) +}) diff --git a/browser/app/js/components/__tests__/ObjectsList.test.js b/browser/app/js/components/__tests__/ObjectsList.test.js new file mode 100644 index 000000000..5d6609271 --- /dev/null +++ b/browser/app/js/components/__tests__/ObjectsList.test.js @@ -0,0 +1,39 @@ +/* + * 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 { ObjectsList } from "../ObjectsList" + +describe("ObjectsList", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should render ObjectContainer for every object", () => { + const wrapper = shallow( + + ) + expect(wrapper.find("ObjectContainer").length).toBe(2) + }) + + it("should render PrefixContainer for every prefix", () => { + const wrapper = shallow( + + ) + expect(wrapper.find("Connect(PrefixContainer)").length).toBe(2) + }) +}) diff --git a/browser/app/js/components/__tests__/ObjectsListContainer.test.js b/browser/app/js/components/__tests__/ObjectsListContainer.test.js new file mode 100644 index 000000000..cea608c1c --- /dev/null +++ b/browser/app/js/components/__tests__/ObjectsListContainer.test.js @@ -0,0 +1,57 @@ +/* + * 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 { ObjectsListContainer } from "../ObjectsListContainer" + +describe("ObjectsList", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should render ObjectsList with objects", () => { + const wrapper = shallow( + + ) + expect(wrapper.find("ObjectsList").length).toBe(1) + expect(wrapper.find("ObjectsList").prop("objects")).toEqual([ + { name: "test1.jpg" }, + { name: "test2.jpg" } + ]) + }) + + it("should call loadObjects when currentBucket is changed", () => { + const loadObjects = jest.fn() + const wrapper = shallow( + + ) + wrapper.setProps({ currentBucket: "test2" }) + expect(loadObjects).toHaveBeenCalled() + }) + + it("should call loadObjects when currentPrefix is changed", () => { + const loadObjects = jest.fn() + const wrapper = shallow( + + ) + wrapper.setProps({ currentPrefix: "abc/xyz/" }) + expect(loadObjects).toHaveBeenCalled() + }) +}) diff --git a/browser/app/js/components/__tests__/ObjetctItem.test.js b/browser/app/js/components/__tests__/ObjetctItem.test.js new file mode 100644 index 000000000..507d52aa3 --- /dev/null +++ b/browser/app/js/components/__tests__/ObjetctItem.test.js @@ -0,0 +1,37 @@ +/* + * 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 { ObjectItem } from "../ObjectItem" + +describe("ObjectItem", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should render with content type", () => { + const wrapper = shallow() + expect(wrapper.prop("data-type")).toBe("image") + }) + + it("should call onClick when the object isclicked", () => { + const onClick = jest.fn() + const wrapper = shallow() + wrapper.find("a").simulate("click", { preventDefault: jest.fn() }) + expect(onClick).toHaveBeenCalled() + }) +}) diff --git a/browser/app/js/components/__tests__/Path.test.js b/browser/app/js/components/__tests__/Path.test.js new file mode 100644 index 000000000..00952c14e --- /dev/null +++ b/browser/app/js/components/__tests__/Path.test.js @@ -0,0 +1,72 @@ +/* + * 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 { Path } from "../Path" + +describe("Path", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should render only bucket if there is no prefix", () => { + const wrapper = shallow() + expect(wrapper.find("span").length).toBe(1) + expect(wrapper.text()).toBe("test1") + }) + + it("should render bucket and prefix", () => { + const wrapper = shallow( + + ) + expect(wrapper.find("span").length).toBe(3) + expect( + wrapper + .find("span") + .at(0) + .text() + ).toBe("test1") + expect( + wrapper + .find("span") + .at(1) + .text() + ).toBe("a") + expect( + wrapper + .find("span") + .at(2) + .text() + ).toBe("b") + }) + + it("should call selectPrefix when a prefix part is clicked", () => { + const selectPrefix = jest.fn() + const wrapper = shallow( + + ) + wrapper + .find("a") + .at(2) + .simulate("click", { preventDefault: jest.fn() }) + expect(selectPrefix).toHaveBeenCalledWith("a/b/") + }) +}) diff --git a/browser/app/js/components/__tests__/PrefixContainer.test.js b/browser/app/js/components/__tests__/PrefixContainer.test.js new file mode 100644 index 000000000..87bc47ba3 --- /dev/null +++ b/browser/app/js/components/__tests__/PrefixContainer.test.js @@ -0,0 +1,44 @@ +/* + * 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 { PrefixContainer } from "../PrefixContainer" + +describe("PrefixContainer", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should render ObjectItem with props", () => { + const wrapper = shallow() + expect(wrapper.find("ObjectItem").length).toBe(1) + expect(wrapper.find("ObjectItem").prop("name")).toBe("abc/") + }) + + it("should call selectPrefix when the prefix is clicked", () => { + const selectPrefix = jest.fn() + const wrapper = shallow( + + ) + wrapper.find("ObjectItem").prop("onClick")() + expect(selectPrefix).toHaveBeenCalledWith("xyz/abc/") + }) +}) diff --git a/browser/app/js/components/__tests__/StorageInfo.test.js b/browser/app/js/components/__tests__/StorageInfo.test.js new file mode 100644 index 000000000..6cf1ccbf4 --- /dev/null +++ b/browser/app/js/components/__tests__/StorageInfo.test.js @@ -0,0 +1,41 @@ +/* + * 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 { StorageInfo } from "../StorageInfo" + +describe("StorageInfo", () => { + it("should render without crashing", () => { + shallow( + + ) + }) + + it("should fetchStorageInfo before component is mounted", () => { + const fetchStorageInfo = jest.fn() + shallow( + + ) + expect(fetchStorageInfo).toHaveBeenCalled() + }) +}) diff --git a/browser/app/js/constants.js b/browser/app/js/constants.js index 177000bbf..a2cf3097b 100644 --- a/browser/app/js/constants.js +++ b/browser/app/js/constants.js @@ -20,6 +20,6 @@ var p = window.location.pathname export const minioBrowserPrefix = p.slice(0, p.indexOf("/", 1)) -export const READ_ONLY = 'readonly' -export const WRITE_ONLY = 'writeonly' -export const READ_WRITE = 'readwrite' +export const READ_ONLY = "readonly" +export const WRITE_ONLY = "writeonly" +export const READ_WRITE = "readwrite" diff --git a/browser/app/js/history.js b/browser/app/js/history.js new file mode 100644 index 000000000..a88e3740c --- /dev/null +++ b/browser/app/js/history.js @@ -0,0 +1,24 @@ +/* + * 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 createHistory from "history/createBrowserHistory" +import { minioBrowserPrefix } from "./constants" + +const history = createHistory({ + basename: minioBrowserPrefix +}) + +export default history diff --git a/browser/app/js/reducers/__tests__/buckets.test.js b/browser/app/js/reducers/__tests__/buckets.test.js index 84fec4f2e..e27e36cb3 100644 --- a/browser/app/js/reducers/__tests__/buckets.test.js +++ b/browser/app/js/reducers/__tests__/buckets.test.js @@ -19,7 +19,8 @@ import * as actions from "../../actions/buckets" describe("buckets reducer", () => { it("should return the initial state", () => { - expect(reducer(undefined, {})).toEqual({ + const initialState = reducer(undefined, {}) + expect(initialState).toEqual({ list: [], filter: "", currentBucket: "" @@ -27,38 +28,26 @@ describe("buckets reducer", () => { }) it("should handle SET_BUCKETS", () => { - expect( - reducer(undefined, { type: actions.SET_LIST, buckets: ["bk1", "bk2"] }) - ).toEqual({ - list: ["bk1", "bk2"], - filter: "", - currentBucket: "" + const newState = reducer(undefined, { + type: actions.SET_LIST, + buckets: ["bk1", "bk2"] }) + expect(newState.list).toEqual(["bk1", "bk2"]) }) it("should handle SET_BUCKETS_FILTER", () => { - expect( - reducer(undefined, { - type: actions.SET_FILTER, - filter: "test" - }) - ).toEqual({ - list: [], - filter: "test", - currentBucket: "" + const newState = reducer(undefined, { + type: actions.SET_FILTER, + filter: "test" }) + expect(newState.filter).toEqual("test") }) it("should handle SELECT_BUCKET", () => { - expect( - reducer(undefined, { - type: actions.SET_CURRENT_BUCKET, - bucket: "test" - }) - ).toEqual({ - list: [], - filter: "", - currentBucket: "test" + const newState = reducer(undefined, { + type: actions.SET_CURRENT_BUCKET, + bucket: "test" }) + expect(newState.currentBucket).toEqual("test") }) }) diff --git a/browser/app/js/reducers/__tests__/common.test.js b/browser/app/js/reducers/__tests__/common.test.js new file mode 100644 index 000000000..d49cc7757 --- /dev/null +++ b/browser/app/js/reducers/__tests__/common.test.js @@ -0,0 +1,70 @@ +/* + * 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 reducer from "../common" +import * as actionsCommon from "../../actions/common" + +describe("common reducer", () => { + it("should return the initial state", () => { + expect(reducer(undefined, {})).toEqual({ + sidebarOpen: false, + storageInfo: { + total: 0, + free: 0 + } + }) + }) + + it("should handle TOGGLE_SIDEBAR", () => { + expect( + reducer( + { sidebarOpen: false }, + { + type: actionsCommon.TOGGLE_SIDEBAR + } + ) + ).toEqual({ + sidebarOpen: true + }) + }) + + it("should handle CLOSE_SIDEBAR", () => { + expect( + reducer( + { sidebarOpen: true }, + { + type: actionsCommon.CLOSE_SIDEBAR + } + ) + ).toEqual({ + sidebarOpen: false + }) + }) + + it("should handle SET_STORAGE_INFO", () => { + expect( + reducer( + {}, + { + type: actionsCommon.SET_STORAGE_INFO, + storageInfo: { total: 100, free: 40 } + } + ) + ).toEqual({ + storageInfo: { total: 100, free: 40 } + }) + }) +}) diff --git a/browser/app/js/reducers/__tests__/objects.test.js b/browser/app/js/reducers/__tests__/objects.test.js new file mode 100644 index 000000000..979bdc150 --- /dev/null +++ b/browser/app/js/reducers/__tests__/objects.test.js @@ -0,0 +1,97 @@ +/* + * 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 reducer from "../objects" +import * as actions from "../../actions/objects" + +describe("objects reducer", () => { + it("should return the initial state", () => { + const initialState = reducer(undefined, {}) + expect(initialState).toEqual({ + list: [], + sortBy: "", + sortOrder: false, + currentPrefix: "", + marker: "", + isTruncated: false + }) + }) + + it("should handle SET_LIST", () => { + const newState = reducer(undefined, { + type: actions.SET_LIST, + objects: [{ name: "obj1" }, { name: "obj2" }], + marker: "obj2", + isTruncated: false + }) + expect(newState.list).toEqual([{ name: "obj1" }, { name: "obj2" }]) + expect(newState.marker).toBe("obj2") + expect(newState.isTruncated).toBeFalsy() + }) + + it("should handle APPEND_LIST", () => { + const newState = reducer( + { + list: [{ name: "obj1" }, { name: "obj2" }], + marker: "obj2", + isTruncated: true + }, + { + type: actions.APPEND_LIST, + objects: [{ name: "obj3" }, { name: "obj4" }], + marker: "obj4", + isTruncated: false + } + ) + expect(newState.list).toEqual([ + { name: "obj1" }, + { name: "obj2" }, + { name: "obj3" }, + { name: "obj4" } + ]) + expect(newState.marker).toBe("obj4") + expect(newState.isTruncated).toBeFalsy() + }) + + it("should handle SET_SORT_BY", () => { + const newState = reducer(undefined, { + type: actions.SET_SORT_BY, + sortBy: "name" + }) + expect(newState.sortBy).toEqual("name") + }) + + it("should handle SET_SORT_ORDER", () => { + const newState = reducer(undefined, { + type: actions.SET_SORT_ORDER, + sortOrder: true + }) + expect(newState.sortOrder).toEqual(true) + }) + + it("should handle SET_CURRENT_PREFIX", () => { + const newState = reducer( + { currentPrefix: "test1/", marker: "abc", isTruncated: true }, + { + type: actions.SET_CURRENT_PREFIX, + prefix: "test2/" + } + ) + expect(newState.currentPrefix).toEqual("test2/") + expect(newState.marker).toEqual("") + expect(newState.isTruncated).toBeFalsy() + }) +}) diff --git a/browser/app/js/reducers/common.js b/browser/app/js/reducers/common.js new file mode 100644 index 000000000..33573aede --- /dev/null +++ b/browser/app/js/reducers/common.js @@ -0,0 +1,39 @@ +/* + * 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 * as actionsCommon from "../actions/common" + +export default ( + state = { sidebarOpen: false, storageInfo: { total: 0, free: 0 } }, + action +) => { + switch (action.type) { + case actionsCommon.TOGGLE_SIDEBAR: + return Object.assign({}, state, { + sidebarOpen: !state.sidebarOpen + }) + case actionsCommon.CLOSE_SIDEBAR: + return Object.assign({}, state, { + sidebarOpen: false + }) + case actionsCommon.SET_STORAGE_INFO: + return Object.assign({}, state, { + storageInfo: action.storageInfo + }) + default: + return state + } +} diff --git a/browser/app/js/reducers/index.js b/browser/app/js/reducers/index.js index 79145dcac..3b0d8bdb2 100644 --- a/browser/app/js/reducers/index.js +++ b/browser/app/js/reducers/index.js @@ -15,12 +15,16 @@ */ import { combineReducers } from "redux" +import common from "./common" import alert from "./alert" import buckets from "./buckets" +import objects from "./objects" const rootReducer = combineReducers({ + common, alert, - buckets + buckets, + objects }) export default rootReducer diff --git a/browser/app/js/reducers/objects.js b/browser/app/js/reducers/objects.js new file mode 100644 index 000000000..7b298c15d --- /dev/null +++ b/browser/app/js/reducers/objects.js @@ -0,0 +1,65 @@ +/* + * 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 * as actionsObjects from "../actions/objects" + +export default ( + state = { + list: [], + sortBy: "", + sortOrder: false, + currentPrefix: "", + marker: "", + isTruncated: false + }, + action +) => { + switch (action.type) { + case actionsObjects.SET_LIST: + return { + ...state, + list: action.objects, + marker: action.marker, + isTruncated: action.isTruncated + } + case actionsObjects.APPEND_LIST: + return { + ...state, + list: [...state.list, ...action.objects], + marker: action.marker, + isTruncated: action.isTruncated + } + case actionsObjects.SET_SORT_BY: + return { + ...state, + sortBy: action.sortBy + } + case actionsObjects.SET_SORT_ORDER: + return { + ...state, + sortOrder: action.sortOrder + } + case actionsObjects.SET_CURRENT_PREFIX: + return { + ...state, + currentPrefix: action.prefix, + marker: "", + isTruncated: false + } + default: + return state + } +} diff --git a/browser/app/less/inc/header.less b/browser/app/less/inc/header.less index 50c619bc1..436fc4e41 100644 --- a/browser/app/less/inc/header.less +++ b/browser/app/less/inc/header.less @@ -27,6 +27,13 @@ color: @text-color; } } + &:last-child { + &:after { + content: '/'; + margin: 0 4px; + color: @text-color; + } + } } } diff --git a/browser/package.json b/browser/package.json index eae83249b..3860e2d49 100644 --- a/browser/package.json +++ b/browser/package.json @@ -11,7 +11,10 @@ }, "jest": { "setupTestFrameworkScriptFile": "./app/js/jest/setup.js", - "testURL": "https://localhost:8080" + "testURL": "https://localhost:8080", + "moduleNameMapper": { + "\\.(css|scss|svg)$": "identity-obj-proxy" + } }, "repository": { "type": "git", @@ -60,6 +63,7 @@ "font-awesome": "^4.7.0", "history": "^4.7.2", "humanize": "0.0.9", + "identity-obj-proxy": "^3.0.0", "json-loader": "^0.5.4", "local-storage-fallback": "^4.0.2", "material-design-iconic-font": "^2.2.0", diff --git a/browser/yarn.lock b/browser/yarn.lock index b1e188fbd..69ea4b261 100644 --- a/browser/yarn.lock +++ b/browser/yarn.lock @@ -2992,6 +2992,10 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +harmony-reflect@^1.4.6: + version "1.5.1" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.5.1.tgz#b54ca617b00cc8aef559bbb17b3d85431dc7e329" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -3271,6 +3275,12 @@ icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" +identity-obj-proxy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + dependencies: + harmony-reflect "^1.4.6" + ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"