From 44f8f7059c6286e033ce125daed603db9db5a164 Mon Sep 17 00:00:00 2001 From: Kanagaraj M Date: Thu, 15 Feb 2018 08:04:59 +0530 Subject: [PATCH] Refactor make bucket and upload components (#5521) --- browser/app/js/alert/AlertContainer.js | 41 +++++ browser/app/js/alert/__tests___/Alert.test.js | 2 +- .../alert/__tests___/AlertContainer.test.js | 34 ++++ browser/app/js/browser/Browser.js | 2 + .../{components => browser}/ConfirmModal.js | 47 +++-- browser/app/js/browser/MainActions.js | 83 +++++++++ browser/app/js/browser/MainContent.js | 6 + .../js/browser/__tests__/MainActions.test.js | 47 +++++ browser/app/js/buckets/MakeBucketModal.js | 90 +++++++++ .../buckets/__tests__/MakeBucketModal.test.js | 80 ++++++++ .../app/js/buckets/__tests__/actions.test.js | 66 +++++-- .../app/js/buckets/__tests__/reducer.test.js | 22 ++- browser/app/js/buckets/actions.js | 41 +++++ browser/app/js/buckets/reducer.js | 17 +- browser/app/js/components/UploadModal.js | 141 -------------- .../app/js/objects/ObjectsListContainer.js | 9 - .../__tests__/ObjectsListContainer.test.js | 18 -- .../app/js/objects/__tests__/actions.test.js | 3 +- browser/app/js/objects/actions.js | 7 +- browser/app/js/objects/selectors.js | 19 ++ browser/app/js/reducers.js | 4 +- browser/app/js/uploads/AbortConfirmModal.js | 75 ++++++++ browser/app/js/uploads/UploadModal.js | 91 +++++++++ .../__tests__/AbortConfirmModal.test.js | 49 +++++ .../js/uploads/__tests__/UploadModal.test.js | 56 ++++++ .../app/js/uploads/__tests__/actions.test.js | 166 +++++++++++++++++ .../app/js/uploads/__tests__/reducer.test.js | 87 +++++++++ browser/app/js/uploads/actions.js | 172 ++++++++++++++++++ browser/app/js/uploads/reducer.js | 67 +++++++ 29 files changed, 1329 insertions(+), 213 deletions(-) create mode 100644 browser/app/js/alert/AlertContainer.js create mode 100644 browser/app/js/alert/__tests___/AlertContainer.test.js rename browser/app/js/{components => browser}/ConfirmModal.js (51%) create mode 100644 browser/app/js/browser/MainActions.js create mode 100644 browser/app/js/browser/__tests__/MainActions.test.js create mode 100644 browser/app/js/buckets/MakeBucketModal.js create mode 100644 browser/app/js/buckets/__tests__/MakeBucketModal.test.js delete mode 100644 browser/app/js/components/UploadModal.js create mode 100644 browser/app/js/objects/selectors.js create mode 100644 browser/app/js/uploads/AbortConfirmModal.js create mode 100644 browser/app/js/uploads/UploadModal.js create mode 100644 browser/app/js/uploads/__tests__/AbortConfirmModal.test.js create mode 100644 browser/app/js/uploads/__tests__/UploadModal.test.js create mode 100644 browser/app/js/uploads/__tests__/actions.test.js create mode 100644 browser/app/js/uploads/__tests__/reducer.test.js create mode 100644 browser/app/js/uploads/actions.js create mode 100644 browser/app/js/uploads/reducer.js diff --git a/browser/app/js/alert/AlertContainer.js b/browser/app/js/alert/AlertContainer.js new file mode 100644 index 000000000..a36e9910d --- /dev/null +++ b/browser/app/js/alert/AlertContainer.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 { connect } from "react-redux" +import Alert from "./Alert" +import * as alertActions from "./actions" + +export const AlertContainer = ({ alert, clearAlert }) => { + if (!alert.message) { + return "" + } + return +} + +const mapStateToProps = state => { + return { + alert: state.alert + } +} + +const mapDispatchToProps = dispatch => { + return { + clearAlert: () => dispatch(alertActions.clear()) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AlertContainer) diff --git a/browser/app/js/alert/__tests___/Alert.test.js b/browser/app/js/alert/__tests___/Alert.test.js index 5b8e3a492..b3041d8f6 100644 --- a/browser/app/js/alert/__tests___/Alert.test.js +++ b/browser/app/js/alert/__tests___/Alert.test.js @@ -26,7 +26,7 @@ describe("Alert", () => { it("should call onDismiss when close button is clicked", () => { const onDismiss = jest.fn() const wrapper = mount( - + ) wrapper.find("button").simulate("click", { preventDefault: jest.fn() }) expect(onDismiss).toHaveBeenCalled() diff --git a/browser/app/js/alert/__tests___/AlertContainer.test.js b/browser/app/js/alert/__tests___/AlertContainer.test.js new file mode 100644 index 000000000..d41bc6f02 --- /dev/null +++ b/browser/app/js/alert/__tests___/AlertContainer.test.js @@ -0,0 +1,34 @@ +/* + * 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, mount } from "enzyme" +import { AlertContainer } from "../AlertContainer" + +describe("Alert", () => { + it("should render without crashing", () => { + shallow( + + ) + }) + + it("should render nothing if message is empty", () => { + const wrapper = shallow( + + ) + expect(wrapper.find("Alert").length).toBe(0) + }) +}) diff --git a/browser/app/js/browser/Browser.js b/browser/app/js/browser/Browser.js index f2fb5c5f9..a469351f7 100644 --- a/browser/app/js/browser/Browser.js +++ b/browser/app/js/browser/Browser.js @@ -19,6 +19,7 @@ import classNames from "classnames" import { connect } from "react-redux" import SideBar from "./SideBar" import MainContent from "./MainContent" +import AlertContainer from "../alert/AlertContainer" class Browser extends React.Component { render() { @@ -30,6 +31,7 @@ class Browser extends React.Component { > + ) } diff --git a/browser/app/js/components/ConfirmModal.js b/browser/app/js/browser/ConfirmModal.js similarity index 51% rename from browser/app/js/components/ConfirmModal.js rename to browser/app/js/browser/ConfirmModal.js index 3677c3bbd..7fd4a2d20 100644 --- a/browser/app/js/components/ConfirmModal.js +++ b/browser/app/js/browser/ConfirmModal.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,33 +14,40 @@ * limitations under the License. */ -import React from 'react' -import Modal from 'react-bootstrap/lib/Modal' -import ModalBody from 'react-bootstrap/lib/ModalBody' +import React from "react" +import { Modal, ModalBody } from "react-bootstrap" -let ConfirmModal = ({baseClass, icon, text, sub, okText, cancelText, okHandler, cancelHandler, show}) => { +let ConfirmModal = ({ + baseClass, + icon, + text, + sub, + okText, + cancelText, + okHandler, + cancelHandler, + show +}) => { return ( - +
- -
-
- { text } -
-
- { sub } +
+
{text}
+
{sub}
- -
diff --git a/browser/app/js/browser/MainActions.js b/browser/app/js/browser/MainActions.js new file mode 100644 index 000000000..996a1339b --- /dev/null +++ b/browser/app/js/browser/MainActions.js @@ -0,0 +1,83 @@ +/* + * 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 { Dropdown, OverlayTrigger, Tooltip } from "react-bootstrap" +import * as actionsBuckets from "../buckets/actions" +import * as uploadsActions from "../uploads/actions" + +export const MainActions = ({ uploadFile, showMakeBucketModal }) => { + const uploadTooltip = Upload file + const makeBucketTooltip = ( + Create bucket + ) + const onFileUpload = e => { + e.preventDefault() + uploadFile(e.target.files[0]) + e.target.value = null + } + + return ( + + + + + + + + + + + + + + + { + e.preventDefault() + showMakeBucketModal() + }} + > + + + + + + ) +} + +const mapStateToProps = state => state + +const mapDispatchToProps = dispatch => { + return { + uploadFile: file => dispatch(uploadsActions.uploadFile(file)), + showMakeBucketModal: () => dispatch(actionsBuckets.showMakeBucketModal()) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(MainActions) diff --git a/browser/app/js/browser/MainContent.js b/browser/app/js/browser/MainContent.js index 46b728901..08f8bfb0d 100644 --- a/browser/app/js/browser/MainContent.js +++ b/browser/app/js/browser/MainContent.js @@ -18,12 +18,18 @@ import React from "react" import MobileHeader from "./MobileHeader" import Header from "./Header" import ObjectsSection from "../objects/ObjectsSection" +import MainActions from "./MainActions" +import MakeBucketModal from "../buckets/MakeBucketModal" +import UploadModal from "../uploads/UploadModal" export const MainContent = () => (
+ + +
) diff --git a/browser/app/js/browser/__tests__/MainActions.test.js b/browser/app/js/browser/__tests__/MainActions.test.js new file mode 100644 index 000000000..88ff84144 --- /dev/null +++ b/browser/app/js/browser/__tests__/MainActions.test.js @@ -0,0 +1,47 @@ +/* + * 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, mount } from "enzyme" +import { MainActions } from "../MainActions" + +describe("MainActions", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should call showMakeBucketModal when create bucket icon is clicked", () => { + const showMakeBucketModal = jest.fn() + const wrapper = shallow( + + ) + wrapper + .find("#show-make-bucket") + .simulate("click", { preventDefault: jest.fn() }) + expect(showMakeBucketModal).toHaveBeenCalled() + }) + + it("should call uploadFile when a file is selected for upload", () => { + const uploadFile = jest.fn() + const wrapper = shallow() + const file = new Blob(["file content"], { type: "text/plain" }) + wrapper.find("#file-input").simulate("change", { + preventDefault: jest.fn(), + target: { files: [file] } + }) + expect(uploadFile).toHaveBeenCalledWith(file) + }) +}) diff --git a/browser/app/js/buckets/MakeBucketModal.js b/browser/app/js/buckets/MakeBucketModal.js new file mode 100644 index 000000000..a16645801 --- /dev/null +++ b/browser/app/js/buckets/MakeBucketModal.js @@ -0,0 +1,90 @@ +/* + * 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 { Modal, ModalBody } from "react-bootstrap" +import * as actionsBuckets from "./actions" + +export class MakeBucketModal extends React.Component { + constructor(props) { + super(props) + this.state = { + bucketName: "" + } + } + onSubmit(e) { + e.preventDefault() + const { makeBucket } = this.props + const bucket = this.state.bucketName + if (bucket) { + makeBucket(bucket) + this.hideModal() + } + } + hideModal() { + this.setState({ + bucketName: "" + }) + this.props.hideMakeBucketModal() + } + render() { + const { showMakeBucketModal } = this.props + return ( + + + +
+
+ this.setState({ bucketName: e.target.value })} + autoFocus + /> + +
+ +
+
+ ) + } +} + +const mapStateToProps = state => { + return { + showMakeBucketModal: state.buckets.showMakeBucketModal + } +} + +const mapDispatchToProps = dispatch => { + return { + makeBucket: bucket => dispatch(actionsBuckets.makeBucket(bucket)), + hideMakeBucketModal: () => dispatch(actionsBuckets.hideMakeBucketModal()) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(MakeBucketModal) diff --git a/browser/app/js/buckets/__tests__/MakeBucketModal.test.js b/browser/app/js/buckets/__tests__/MakeBucketModal.test.js new file mode 100644 index 000000000..1d956ac79 --- /dev/null +++ b/browser/app/js/buckets/__tests__/MakeBucketModal.test.js @@ -0,0 +1,80 @@ +/* + * 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, mount } from "enzyme" +import { MakeBucketModal } from "../MakeBucketModal" + +describe("MakeBucketModal", () => { + it("should render without crashing", () => { + shallow() + }) + + it("should call hideMakeBucketModal when close button is clicked", () => { + const hideMakeBucketModal = jest.fn() + const wrapper = shallow( + + ) + wrapper.find("button").simulate("click") + expect(hideMakeBucketModal).toHaveBeenCalled() + }) + + it("bucketName should be cleared before hiding the modal", () => { + const hideMakeBucketModal = jest.fn() + const wrapper = shallow( + + ) + wrapper.find("input").simulate("change", { + target: { value: "test" } + }) + expect(wrapper.state("bucketName")).toBe("test") + wrapper.find("button").simulate("click") + expect(wrapper.state("bucketName")).toBe("") + }) + + it("should call makeBucket when the form is submitted", () => { + const makeBucket = jest.fn() + const hideMakeBucketModal = jest.fn() + const wrapper = shallow( + + ) + wrapper.find("input").simulate("change", { + target: { value: "test" } + }) + wrapper.find("form").simulate("submit", { preventDefault: jest.fn() }) + expect(makeBucket).toHaveBeenCalledWith("test") + }) + + it("should call hideMakeBucketModal and clear bucketName after the form is submited", () => { + const makeBucket = jest.fn() + const hideMakeBucketModal = jest.fn() + const wrapper = shallow( + + ) + wrapper.find("input").simulate("change", { + target: { value: "test" } + }) + wrapper.find("form").simulate("submit", { preventDefault: jest.fn() }) + expect(hideMakeBucketModal).toHaveBeenCalled() + expect(wrapper.state("bucketName")).toBe("") + }) +}) diff --git a/browser/app/js/buckets/__tests__/actions.test.js b/browser/app/js/buckets/__tests__/actions.test.js index e56d8a8c1..d5b80521c 100644 --- a/browser/app/js/buckets/__tests__/actions.test.js +++ b/browser/app/js/buckets/__tests__/actions.test.js @@ -17,13 +17,21 @@ import configureStore from "redux-mock-store" import thunk from "redux-thunk" import * as actionsBuckets from "../actions" +import * as objectActions from "../../objects/actions" jest.mock("../../web", () => ({ ListBuckets: jest.fn(() => { return Promise.resolve({ buckets: [{ name: "test1" }, { name: "test2" }] }) + }), + MakeBucket: jest.fn(() => { + return Promise.resolve() }) })) +jest.mock("../../objects/actions", () => ({ + selectPrefix: () => dispatch => {} +})) + const middlewares = [thunk] const mockStore = configureStore(middlewares) @@ -40,24 +48,6 @@ describe("Buckets actions", () => { }) }) - it("creates buckets/SET_LIST directly", () => { - const store = mockStore() - const expectedActions = [ - { type: "buckets/SET_LIST", buckets: ["test1", "test2"] } - ] - store.dispatch(actionsBuckets.setList(["test1", "test2"])) - const actions = store.getActions() - expect(actions).toEqual(expectedActions) - }) - - it("creates buckets/SET_FILTER directly", () => { - const store = mockStore() - const expectedActions = [{ type: "buckets/SET_FILTER", filter: "test" }] - store.dispatch(actionsBuckets.setFilter("test")) - 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 = [ @@ -68,4 +58,44 @@ describe("Buckets actions", () => { expect(actions).toEqual(expectedActions) expect(window.location.pathname).toBe("/test1") }) + + it("creates buckets/SHOW_MAKE_BUCKET_MODAL for showMakeBucketModal", () => { + const store = mockStore() + const expectedActions = [ + { type: "buckets/SHOW_MAKE_BUCKET_MODAL", show: true } + ] + store.dispatch(actionsBuckets.showMakeBucketModal()) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + + it("creates buckets/SHOW_MAKE_BUCKET_MODAL for hideMakeBucketModal", () => { + const store = mockStore() + const expectedActions = [ + { type: "buckets/SHOW_MAKE_BUCKET_MODAL", show: false } + ] + store.dispatch(actionsBuckets.hideMakeBucketModal()) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + + it("creates buckets/ADD action", () => { + const store = mockStore() + const expectedActions = [{ type: "buckets/ADD", bucket: "test" }] + store.dispatch(actionsBuckets.addBucket("test")) + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + + it("creates buckets/ADD and buckets/SET_CURRENT_BUCKET after creating the bucket", () => { + const store = mockStore() + const expectedActions = [ + { type: "buckets/ADD", bucket: "test1" }, + { type: "buckets/SET_CURRENT_BUCKET", bucket: "test1" } + ] + return store.dispatch(actionsBuckets.makeBucket("test1")).then(() => { + const actions = store.getActions() + expect(actions).toEqual(expectedActions) + }) + }) }) diff --git a/browser/app/js/buckets/__tests__/reducer.test.js b/browser/app/js/buckets/__tests__/reducer.test.js index 298389af0..6906198e2 100644 --- a/browser/app/js/buckets/__tests__/reducer.test.js +++ b/browser/app/js/buckets/__tests__/reducer.test.js @@ -23,7 +23,8 @@ describe("buckets reducer", () => { expect(initialState).toEqual({ list: [], filter: "", - currentBucket: "" + currentBucket: "", + showMakeBucketModal: false }) }) @@ -35,6 +36,17 @@ describe("buckets reducer", () => { expect(newState.list).toEqual(["bk1", "bk2"]) }) + it("should handle ADD", () => { + const newState = reducer( + { list: ["test1", "test2"] }, + { + type: actions.ADD, + bucket: "test3" + } + ) + expect(newState.list).toEqual(["test3", "test1", "test2"]) + }) + it("should handle SET_FILTER", () => { const newState = reducer(undefined, { type: actions.SET_FILTER, @@ -50,4 +62,12 @@ describe("buckets reducer", () => { }) expect(newState.currentBucket).toEqual("test") }) + + it("should handle SHOW_MAKE_BUCKET_MODAL", () => { + const newState = reducer(undefined, { + type: actions.SHOW_MAKE_BUCKET_MODAL, + show: true + }) + expect(newState.showMakeBucketModal).toBeTruthy() + }) }) diff --git a/browser/app/js/buckets/actions.js b/browser/app/js/buckets/actions.js index 01b40f778..250dd839d 100644 --- a/browser/app/js/buckets/actions.js +++ b/browser/app/js/buckets/actions.js @@ -16,10 +16,14 @@ import web from "../web" import history from "../history" +import * as alertActions from "../alert/actions" +import * as objectsActions from "../objects/actions" export const SET_LIST = "buckets/SET_LIST" +export const ADD = "buckets/ADD" export const SET_FILTER = "buckets/SET_FILTER" export const SET_CURRENT_BUCKET = "buckets/SET_CURRENT_BUCKET" +export const SHOW_MAKE_BUCKET_MODAL = "buckets/SHOW_MAKE_BUCKET_MODAL" export const fetchBuckets = () => { return function(dispatch) { @@ -50,6 +54,7 @@ export const setFilter = filter => { export const selectBucket = bucket => { return function(dispatch) { dispatch(setCurrentBucket(bucket)) + dispatch(objectsActions.selectPrefix("")) history.push(`/${bucket}`) } } @@ -60,3 +65,39 @@ export const setCurrentBucket = bucket => { bucket } } + +export const makeBucket = bucket => { + return function(dispatch) { + return web + .MakeBucket({ + bucketName: bucket + }) + .then(() => { + dispatch(addBucket(bucket)) + dispatch(selectBucket(bucket)) + }) + .catch(err => + dispatch( + alertActions.set({ + type: "danger", + message: err.message + }) + ) + ) + } +} + +export const addBucket = bucket => ({ + type: ADD, + bucket +}) + +export const showMakeBucketModal = () => ({ + type: SHOW_MAKE_BUCKET_MODAL, + show: true +}) + +export const hideMakeBucketModal = () => ({ + type: SHOW_MAKE_BUCKET_MODAL, + show: false +}) diff --git a/browser/app/js/buckets/reducer.js b/browser/app/js/buckets/reducer.js index 69be47e34..7bebdef21 100644 --- a/browser/app/js/buckets/reducer.js +++ b/browser/app/js/buckets/reducer.js @@ -17,7 +17,12 @@ import * as actionsBuckets from "./actions" export default ( - state = { list: [], filter: "", currentBucket: "" }, + state = { + list: [], + filter: "", + currentBucket: "", + showMakeBucketModal: false + }, action ) => { switch (action.type) { @@ -26,6 +31,11 @@ export default ( ...state, list: action.buckets } + case actionsBuckets.ADD: + return { + ...state, + list: [action.bucket, ...state.list] + } case actionsBuckets.SET_FILTER: return { ...state, @@ -36,6 +46,11 @@ export default ( ...state, currentBucket: action.bucket } + case actionsBuckets.SHOW_MAKE_BUCKET_MODAL: + return { + ...state, + showMakeBucketModal: action.show + } default: return state } diff --git a/browser/app/js/components/UploadModal.js b/browser/app/js/components/UploadModal.js deleted file mode 100644 index f68b32ee8..000000000 --- a/browser/app/js/components/UploadModal.js +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Minio Cloud Storage (C) 2016 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 classNames from 'classnames' -import connect from 'react-redux/lib/components/connect' - -import ProgressBar from 'react-bootstrap/lib/ProgressBar' -import ConfirmModal from './ConfirmModal' - -import * as actions from '../actions' - -// UploadModal is a modal that handles multiple file uploads. -// During the upload, it displays a progress bar, and can transform into an -// abort modal if the user decides to abort the uploads. -class UploadModal extends React.Component { - - // Abort all the current uploads. - abortUploads(e) { - e.preventDefault() - const {dispatch, uploads} = this.props - - for (var slug in uploads) { - let upload = uploads[slug] - upload.xhr.abort() - dispatch(actions.stopUpload({ - slug - })) - } - - this.hideAbort(e) - } - - // Show the abort modal instead of the progress modal. - showAbort(e) { - e.preventDefault() - const {dispatch} = this.props - - dispatch(actions.setShowAbortModal(true)) - } - - // Show the progress modal instead of the abort modal. - hideAbort(e) { - e.preventDefault() - const {dispatch} = this.props - - dispatch(actions.setShowAbortModal(false)) - } - - render() { - const {uploads, showAbortModal} = this.props - - // Show the abort modal. - if (showAbortModal) { - let baseClass = classNames({ - 'abort-upload': true - }) - let okIcon = classNames({ - 'fa': true, - 'fa-times': true - }) - let cancelIcon = classNames({ - 'fa': true, - 'fa-cloud-upload': true - }) - - return ( - - - ) - } - - // If we don't have any files uploading, don't show anything. - let numberUploading = Object.keys(uploads).length - if (numberUploading == 0) - return ( ) - - let totalLoaded = 0 - let totalSize = 0 - - // Iterate over each upload, adding together the total size and that - // which has been uploaded. - for (var slug in uploads) { - let upload = uploads[slug] - totalLoaded += upload.loaded - totalSize += upload.size - } - - let percent = (totalLoaded / totalSize) * 100 - - // If more than one: "Uploading files (5)..." - // If only one: "Uploading myfile.txt..." - let text = 'Uploading ' + (numberUploading == 1 ? `'${uploads[Object.keys(uploads)[0]].name}'` : `files (${numberUploading})`) + '...' - - return ( -
- -
- { text } -
- -
- { humanize.filesize(totalLoaded) } ({ percent.toFixed(2) } %) -
-
- ) - } -} - -export default connect(state => { - return { - uploads: state.uploads, - showAbortModal: state.showAbortModal - } -})(UploadModal) diff --git a/browser/app/js/objects/ObjectsListContainer.js b/browser/app/js/objects/ObjectsListContainer.js index 9c1f037dc..4901062d0 100644 --- a/browser/app/js/objects/ObjectsListContainer.js +++ b/browser/app/js/objects/ObjectsListContainer.js @@ -22,15 +22,6 @@ import * as actionsObjects from "./actions" 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 ( diff --git a/browser/app/js/objects/__tests__/ObjectsListContainer.test.js b/browser/app/js/objects/__tests__/ObjectsListContainer.test.js index cea608c1c..63f736dc5 100644 --- a/browser/app/js/objects/__tests__/ObjectsListContainer.test.js +++ b/browser/app/js/objects/__tests__/ObjectsListContainer.test.js @@ -36,22 +36,4 @@ describe("ObjectsList", () => { { 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/objects/__tests__/actions.test.js b/browser/app/js/objects/__tests__/actions.test.js index a539b26a4..a5d52b3b1 100644 --- a/browser/app/js/objects/__tests__/actions.test.js +++ b/browser/app/js/objects/__tests__/actions.test.js @@ -158,7 +158,8 @@ describe("Objects actions", () => { it("should update browser url and creates objects/SET_CURRENT_PREFIX action when selectPrefix is called", () => { const store = mockStore({ - buckets: { currentBucket: "test" } + buckets: { currentBucket: "test" }, + objects: { currentPrefix: "" } }) const expectedActions = [ { type: "objects/SET_CURRENT_PREFIX", prefix: "abc/" } diff --git a/browser/app/js/objects/actions.js b/browser/app/js/objects/actions.js index 736a192e1..a8f29c44c 100644 --- a/browser/app/js/objects/actions.js +++ b/browser/app/js/objects/actions.js @@ -21,9 +21,11 @@ import { sortObjectsBySize, sortObjectsByDate } from "../utils" +import { getCurrentBucket } from "../buckets/selectors" export const SET_LIST = "objects/SET_LIST" export const APPEND_LIST = "objects/APPEND_LIST" +export const RESET = "objects/RESET" 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" @@ -52,7 +54,7 @@ export const fetchObjects = append => { .ListObjects({ bucketName: currentBucket, prefix: currentPrefix, - marker: marker + marker: append ? marker : "" }) .then(res => { let objects = [] @@ -113,7 +115,8 @@ export const setSortOrder = sortOrder => ({ export const selectPrefix = prefix => { return function(dispatch, getState) { dispatch(setCurrentPrefix(prefix)) - const currentBucket = getState().buckets.currentBucket + dispatch(fetchObjects()) + const currentBucket = getCurrentBucket(getState()) history.replace(`/${currentBucket}/${prefix}`) } } diff --git a/browser/app/js/objects/selectors.js b/browser/app/js/objects/selectors.js new file mode 100644 index 000000000..45809c582 --- /dev/null +++ b/browser/app/js/objects/selectors.js @@ -0,0 +1,19 @@ +/* + * 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 { createSelector } from "reselect" + +export const getCurrentPrefix = state => state.objects.currentPrefix diff --git a/browser/app/js/reducers.js b/browser/app/js/reducers.js index 5ab902209..60aac6bd8 100644 --- a/browser/app/js/reducers.js +++ b/browser/app/js/reducers.js @@ -19,12 +19,14 @@ import browser from "./browser/reducer" import alert from "./alert/reducer" import buckets from "./buckets/reducer" import objects from "./objects/reducer" +import uploads from "./uploads/reducer" const rootReducer = combineReducers({ browser, alert, buckets, - objects + objects, + uploads }) export default rootReducer diff --git a/browser/app/js/uploads/AbortConfirmModal.js b/browser/app/js/uploads/AbortConfirmModal.js new file mode 100644 index 000000000..9958d8fb5 --- /dev/null +++ b/browser/app/js/uploads/AbortConfirmModal.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 ConfirmModal from "../browser/ConfirmModal" +import * as uploadsActions from "./actions" + +export class AbortConfirmModal extends React.Component { + abortUploads() { + const { abort, uploads } = this.props + for (var slug in uploads) { + abort(slug) + } + } + render() { + const { hideAbort } = this.props + let baseClass = classNames({ + "abort-upload": true + }) + let okIcon = classNames({ + fa: true, + "fa-times": true + }) + let cancelIcon = classNames({ + fa: true, + "fa-cloud-upload": true + }) + + return ( + + ) + } +} + +const mapStateToProps = state => { + return { + uploads: state.uploads.files + } +} + +const mapDispatchToProps = dispatch => { + return { + abort: slug => dispatch(uploadsActions.abortUpload(slug)), + hideAbort: () => dispatch(uploadsActions.hideAbortModal()) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AbortConfirmModal) diff --git a/browser/app/js/uploads/UploadModal.js b/browser/app/js/uploads/UploadModal.js new file mode 100644 index 000000000..df4c36841 --- /dev/null +++ b/browser/app/js/uploads/UploadModal.js @@ -0,0 +1,91 @@ +/* + * 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 classNames from "classnames" +import { connect } from "react-redux" + +import { ProgressBar } from "react-bootstrap" +import AbortConfirmModal from "./AbortConfirmModal" +import * as uploadsActions from "./actions" + +export class UploadModal extends React.Component { + render() { + const { uploads, showAbort, showAbortModal } = this.props + if (showAbort) { + return + } + + // If we don't have any files uploading, don't show anything. + let numberUploading = Object.keys(uploads).length + if (numberUploading == 0) return