Refactor UI catalog page, Fix pagination 35/57335/2
authorMalek <malek.zoabi@amdocs.com>
Wed, 25 Jul 2018 05:51:50 +0000 (08:51 +0300)
committerMalek <malek.zoabi@amdocs.com>
Wed, 25 Jul 2018 05:51:59 +0000 (08:51 +0300)
Issue-ID: SDC-1558
Change-Id: I80e5ded1b91f86b696fa9b29eb8364febfe0ebba
Signed-off-by: Malek <malek.zoabi@amdocs.com>
14 files changed:
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSelectors-test.js [deleted file]
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSelectors.js [deleted file]
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx
workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx

index 503423f..5eeaaa2 100644 (file)
 import { connect } from 'react-redux';
 
 import CatalogView from 'features/catalog/CatalogView';
-import { sort, scroll } from 'features/catalog/catalogActions';
-import { getSort, getWorkflows } from 'features/catalog/catalogSelectors';
+import { fetchWorkflow, resetWorkflow } from 'features/catalog/catalogActions';
 
 import { showCustomModalAction } from 'shared/modal/modalWrapperActions';
 import { NEW_WORKFLOW_MODAL } from 'shared/modal/modalWrapperComponents';
 import { clearWorkflowAction } from 'features/workflow/workflowConstants';
 
 const mapStateToProps = state => ({
-    sort: getSort(state),
-    workflows: getWorkflows(state)
+    catalog: state.catalog
 });
 
 const mapDispatchToProps = dispatch => ({
-    handleSort: payload => dispatch(sort(payload)),
-    handleScroll: (page, sort) => dispatch(scroll(page, sort)),
+    handleFetchWorkflow: (sort, page) => dispatch(fetchWorkflow(sort, page)),
+    handleResetWorkflow: () => dispatch(resetWorkflow()),
     clearWorkflow: () => dispatch(clearWorkflowAction),
     showNewWorkflowModal: () =>
         dispatch(
index 0af791d..99cee75 100644 (file)
@@ -32,23 +32,32 @@ class CatalogView extends React.Component {
         clearWorkflow();
     }
 
+    componentWillUnmount() {
+        this.props.handleResetWorkflow();
+    }
+
     handleAlphabeticalOrderByClick = e => {
         e.preventDefault();
 
-        const { handleSort, sort } = this.props;
+        const {
+            handleFetchWorkflow,
+            catalog: { sort }
+        } = this.props;
 
         const payload = { ...sort };
 
         payload[NAME] = payload[NAME] === ASC ? DESC : ASC;
 
-        handleSort(payload);
+        handleFetchWorkflow(payload);
     };
 
     handleScroll = () => {
-        const { workflows, sort, handleScroll } = this.props;
-        const { page } = workflows;
+        const {
+            catalog: { page, sort },
+            handleFetchWorkflow
+        } = this.props;
 
-        handleScroll(page, sort);
+        handleFetchWorkflow(sort, page);
     };
 
     goToOverviewPage = id => {
@@ -57,14 +66,9 @@ class CatalogView extends React.Component {
     };
 
     render() {
-        const { workflows, sort, showNewWorkflowModal } = this.props;
-
+        const { catalog, showNewWorkflowModal } = this.props;
+        const { sort, hasMore, total, results } = catalog;
         const alphabeticalOrder = sort[NAME];
-        // TODO remove offset, fix hasMore, use size
-        const { total, results = [], /*size,*/ page, offset } = workflows;
-
-        // const hasMore = total === 0 || size * (page + 1) < total;
-        const hasMore = offset !== undefined ? offset < 0 : page < 0;
 
         return (
             <div className="wf-catalog">
@@ -81,7 +85,7 @@ class CatalogView extends React.Component {
                         <div className="main__content">
                             <AddWorkflow onClick={showNewWorkflowModal} />
                             <Workflows
-                                workflows={results}
+                                results={results}
                                 onWorkflowClick={this.goToOverviewPage}
                             />
                         </div>
@@ -94,18 +98,14 @@ class CatalogView extends React.Component {
 
 CatalogView.propTypes = {
     history: PropTypes.object,
-    workflows: PropTypes.object,
-    sort: PropTypes.object,
-    handleScroll: PropTypes.func,
-    handleSort: PropTypes.func,
+    catalog: PropTypes.object,
+    handleResetWorkflow: PropTypes.func,
+    handleFetchWorkflow: PropTypes.func,
     showNewWorkflowModal: PropTypes.func,
     clearWorkflow: PropTypes.func
 };
 
 CatalogView.defaultProps = {
-    workflows: {},
-    sort: {},
-    handleScroll: () => {},
     showNewWorkflowModal: () => {},
     clearWorkflow: () => {}
 };
index 15a9c8c..f49c2fd 100644 (file)
 * limitations under the License.
 */
 
+'use strict';
+
 import {
-    UPDATE,
-    SCROLL,
-    SORT,
-    LIMIT,
+    FETCH_WORKFLOW,
+    UPDATE_WORKFLOW,
+    RESET_WORKFLOW,
+    PAGE_SIZE,
     NAME,
     ASC
 } from 'features/catalog/catalogConstants';
-import { update, scroll, sort } from 'features/catalog/catalogActions';
+import {
+    fetchWorkflow,
+    updateWorkflow,
+    resetWorkflow
+} from 'features/catalog/catalogActions';
 
 describe('Catalog Actions', () => {
-    it('show have `update` action', () => {
-        expect(update()).toEqual({
-            type: UPDATE
-        });
-    });
+    it('should have `fetchWorkflow` action', () => {
+        const sort = { [NAME]: ASC };
+        const page = 0;
 
-    it('show have `scroll` action', () => {
-        const page = 1;
-        const sort = {};
-
-        const expected = {
-            type: SCROLL,
+        expect(fetchWorkflow(sort, page)).toEqual({
+            type: FETCH_WORKFLOW,
             payload: {
-                page,
                 sort,
-                size: LIMIT
+                size: PAGE_SIZE,
+                page
             }
-        };
-
-        expect(scroll(page, sort)).toEqual(expected);
+        });
     });
 
-    it('show have `sort` action', () => {
-        const sortPayload = {
-            [NAME]: ASC
-        };
-
-        const expected = {
-            type: SORT,
-            payload: {
-                sort: sortPayload
+    it('should have `updateWorkflow` action', () => {
+        const payload = {
+            results: [],
+            total: 0,
+            page: 0,
+            size: 0,
+            sort: {
+                name: 'asc'
             }
         };
 
-        expect(sort(sortPayload)).toEqual(expected);
+        expect(updateWorkflow(payload)).toEqual({
+            type: UPDATE_WORKFLOW,
+            payload
+        });
+    });
+
+    it('should have `resetWorkflow` action', () => {
+        expect(resetWorkflow()).toEqual({
+            type: RESET_WORKFLOW
+        });
     });
 });
index cebddb3..c4f34e7 100644 (file)
 
 'use strict';
 
+import { NAME, ASC, DESC } from 'features/catalog/catalogConstants';
 import catalogReducer, { initialState } from 'features/catalog/catalogReducer';
-import { update } from 'features/catalog/catalogActions';
+import { updateWorkflow, resetWorkflow } from 'features/catalog/catalogActions';
 
 describe('Catalog Reducer', () => {
+    const state = {
+        hasMore: true,
+        results: [
+            {
+                id: '755eab7752374a2380544065b59b082d',
+                name: 'Workflow 1',
+                description: 'description description 1'
+            },
+            {
+                id: 'ef8159204dac4c10a85b29ec30b4bd56',
+                name: 'Workflow 2',
+                description: 'description description 2'
+            }
+        ],
+        total: 0,
+        sort: {
+            [NAME]: ASC
+        }
+    };
+    const sort = {
+        [NAME]: DESC
+    };
+    const page = 0;
+    const data = {
+        total: 20,
+        size: 100,
+        page,
+        sort,
+        results: [
+            {
+                id: '755eab7752374a2380544065b59b082d',
+                name: 'Workflow 11',
+                description: 'description description 11'
+            },
+            {
+                id: 'ef8159204dac4c10a85b29ec30b4bd56',
+                name: 'Workflow 22',
+                description: 'description description 22'
+            }
+        ]
+    };
+
     it('returns the initial state', () => {
         expect(catalogReducer(undefined, {})).toEqual(initialState);
     });
 
-    it('returns correct state for workflows update action', () => {
-        const payload = {
-            total: 2,
-            size: 100,
-            page: 0,
-            results: [
-                {
-                    id: '755eab7752374a2380544065b59b082d',
-                    name: 'Alfa',
-                    description: 'description description 1',
-                    category: null
-                },
-                {
-                    id: 'ef8159204dac4c10a85b29ec30b4bd56',
-                    name: 'Bravo',
-                    description: 'description description 2',
-                    category: null
-                }
-            ]
-        };
+    it('should replace results when page is first', () => {
+        expect(catalogReducer(state, updateWorkflow({ ...data }))).toEqual({
+            ...initialState,
+            ...data,
+            hasMore: data.results.length < data.total,
+            page,
+            sort
+        });
+    });
 
-        const action = update(payload);
+    it('should add results when page is not first', () => {
+        expect(
+            catalogReducer(state, updateWorkflow({ ...data, page: 1 })).results
+        ).toEqual(expect.arrayContaining([...data.results, ...state.results]));
+    });
 
-        expect(catalogReducer(initialState, action).workflows).toEqual(payload);
+    it('should reset state', () => {
+        expect(catalogReducer({ ...state, sort }, resetWorkflow())).toEqual({
+            ...initialState,
+            sort
+        });
     });
 });
index f06d240..cdea344 100644 (file)
 
 'use strict';
 
+import { runSaga } from 'redux-saga';
+import { takeLatest } from 'redux-saga/effects';
+
+import { NAME, DESC, PAGE_SIZE } from 'features/catalog/catalogConstants';
+import catalogApi from '../catalogApi';
+import { fetchWorkflow, updateWorkflow } from 'features/catalog/catalogActions';
+import catalogSaga, { fetchWorkflowSaga } from 'features/catalog/catalogSagas';
+
+jest.mock('../catalogApi');
+
 describe('Catalog Sagas', () => {
-    it('Write test', () => {});
+    it('should watch for `fetchWorkflow` action', () => {
+        const gen = catalogSaga();
+
+        expect(gen.next().value).toEqual(
+            takeLatest(fetchWorkflow, fetchWorkflowSaga)
+        );
+
+        expect(gen.next().done).toBe(true);
+    });
+
+    it('should get workflows and put `updateWorkflow` action', async () => {
+        const sort = {
+            [NAME]: DESC
+        };
+        const page = 0;
+        const data = {
+            total: 2,
+            size: 100,
+            page,
+            results: [
+                {
+                    id: '755eab7752374a2380544065b59b082d',
+                    name: 'Workflow 11',
+                    description: 'description description 11'
+                },
+                {
+                    id: 'ef8159204dac4c10a85b29ec30b4bd56',
+                    name: 'Workflow 22',
+                    description: 'description description 22'
+                }
+            ]
+        };
+        const dispatched = [];
+
+        catalogApi.getWorkflows.mockReturnValue(data);
+
+        await runSaga(
+            {
+                dispatch: action => dispatched.push(action)
+            },
+            fetchWorkflowSaga,
+            fetchWorkflow(sort, page)
+        ).done;
+
+        expect(dispatched).toEqual(
+            expect.arrayContaining([updateWorkflow({ ...data, sort })])
+        );
+        expect(catalogApi.getWorkflows).toBeCalledWith(
+            sort,
+            PAGE_SIZE,
+            page + 1
+        );
+    });
 });
diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSelectors-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSelectors-test.js
deleted file mode 100644 (file)
index 0aa73ce..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-* Copyright © 2018 European Support Limited
-*
-* 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.
-*/
-
-'use strict';
-
-import {
-    getCatalog,
-    getWorkflows,
-    getSort,
-    getQueryString
-} from 'features/catalog/catalogSelectors';
-
-describe('Catalog Selectors', () => {
-    const catalog = {
-        page: -1,
-        workflows: {
-            total: 2,
-            limit: 0,
-            offset: 0,
-            results: [
-                {
-                    id: '755eab7752374a2380544065b59b082d',
-                    name: 'Alfa',
-                    description: 'description description 1',
-                    category: null
-                },
-                {
-                    id: 'ef8159204dac4c10a85b29ec30b4bd56',
-                    name: 'Bravo',
-                    description: 'description description 2',
-                    category: null
-                }
-            ]
-        },
-        sort: {
-            name: 'ASC',
-            date: 'DESC'
-        }
-    };
-
-    it('returns catalog', () => {
-        const state = { catalog };
-
-        expect(getCatalog(state)).toEqual(catalog);
-    });
-
-    it('returns catalog workflows', () => {
-        const state = { catalog };
-
-        expect(getWorkflows(state)).toEqual(catalog.workflows);
-    });
-
-    it('returns catalog sort', () => {
-        const state = { catalog };
-
-        expect(getSort(state)).toEqual(catalog.sort);
-    });
-});
index 5be2788..89f53e4 100644 (file)
 
 import { createActions } from 'redux-actions';
 
-import { NAMESPACE, LIMIT } from 'features/catalog/catalogConstants';
+import { NAMESPACE, PAGE_SIZE } from 'features/catalog/catalogConstants';
 
 export const {
-    [NAMESPACE]: { update, sort, scroll }
+    [NAMESPACE]: { fetchWorkflow, updateWorkflow, resetWorkflow }
 } = createActions({
     [NAMESPACE]: {
-        UPDATE: undefined,
-        SORT: sort => ({
-            sort
+        FETCH_WORKFLOW: (sort, page) => ({
+            sort,
+            size: PAGE_SIZE,
+            page
         }),
-        SCROLL: (page, sort) => ({
-            page,
-            size: LIMIT,
-            sort
-        })
+        UPDATE_WORKFLOW: undefined,
+        RESET_WORKFLOW: undefined
     }
 });
index 7273d21..79b271c 100644 (file)
@@ -14,6 +14,8 @@
 * limitations under the License.
 */
 
+import qs from 'qs';
+
 import RestfulAPIUtil from 'services/restAPIUtil';
 import Configuration from 'config/Configuration.js';
 
@@ -23,7 +25,19 @@ function baseUrl() {
 }
 
 const Api = {
-    getWorkflows: queryString => {
+    getWorkflows: (sort, size, page) => {
+        const queryString = qs.stringify(
+            {
+                sort: Object.keys(sort).map(key => `${key},${sort[key]}`),
+                size,
+                page
+            },
+            {
+                indices: false,
+                addQueryPrefix: true
+            }
+        );
+
         return RestfulAPIUtil.fetch(`${baseUrl()}${queryString}`);
     }
 };
index 39f90dd..3120ad7 100644 (file)
 */
 
 export const NAMESPACE = 'catalog';
+
+export const NAME = 'name';
 export const ASC = 'asc';
 export const DESC = 'desc';
-export const NAME = 'name';
-export const LIMIT = 50;
 
-export const UPDATE = `${NAMESPACE}/UPDATE`;
-export const SORT = `${NAMESPACE}/SORT`;
-export const SCROLL = `${NAMESPACE}/SCROLL`;
+export const PAGE_SIZE = 1000;
+
+export const FETCH_WORKFLOW = `${NAMESPACE}/FETCH_WORKFLOW`;
+export const UPDATE_WORKFLOW = `${NAMESPACE}/UPDATE_WORKFLOW`;
+export const RESET_WORKFLOW = `${NAMESPACE}/RESET_WORKFLOW`;
index 9816a96..cbd7ab7 100644 (file)
 */
 
 import {
-    LIMIT,
     NAME,
     ASC,
-    UPDATE,
-    SORT
+    UPDATE_WORKFLOW,
+    RESET_WORKFLOW
 } from 'features/catalog/catalogConstants';
 
 export const initialState = {
-    workflows: {
-        size: LIMIT,
-        page: -1,
-        results: [],
-        total: 0
-    },
+    hasMore: true,
+    results: [],
+    total: 0,
     sort: {
         [NAME]: ASC
     }
@@ -37,21 +33,23 @@ export const initialState = {
 const catalogReducer = (state = initialState, action) => {
     const { type, payload } = action;
 
+    let results;
+
     switch (type) {
-        case UPDATE:
-            return {
-                ...state,
-                workflows: {
-                    ...state.workflows,
-                    ...payload,
-                    results: [...state.workflows.results, ...payload.results]
-                }
-            };
+        case RESET_WORKFLOW:
+            return { ...initialState, sort: state.sort };
+
+        case UPDATE_WORKFLOW:
+            results =
+                payload.page === 0
+                    ? [...payload.results]
+                    : [...state.results, ...payload.results];
 
-        case SORT:
             return {
-                ...initialState,
-                sort: { ...payload.sort }
+                ...state,
+                ...payload,
+                results,
+                hasMore: results.length < payload.total
             };
 
         default:
index ffc6d18..8dcfc61 100644 (file)
 * limitations under the License.
 */
 
-import qs from 'qs';
 import { call, put, takeLatest } from 'redux-saga/effects';
 
 import catalogApi from 'features/catalog/catalogApi';
-import { update, scroll } from 'features/catalog/catalogActions';
+import { fetchWorkflow, updateWorkflow } from 'features/catalog/catalogActions';
 
 const noOp = () => {};
 
-export function* fetchWorkflows({ payload }) {
-    const { page, size, sort } = payload;
+export function* fetchWorkflowSaga({ payload }) {
+    const { sort, size, page } = payload;
 
-    const queryString = qs.stringify(
-        {
-            sort: Object.keys(sort).map(key => `${key},${sort[key]}`),
+    try {
+        const data = yield call(
+            catalogApi.getWorkflows,
+            sort,
             size,
-            page: page + 1
-        },
-        {
-            indices: false,
-            addQueryPrefix: true
-        }
-    );
+            page === undefined ? 0 : page + 1
+        );
 
-    try {
-        const data = yield call(catalogApi.getWorkflows, queryString);
-        yield put(update(data));
+        yield put(updateWorkflow({ ...data, sort }));
     } catch (e) {
         noOp();
     }
 }
 
 function* catalogSaga() {
-    yield takeLatest(scroll, fetchWorkflows);
+    yield takeLatest(fetchWorkflow, fetchWorkflowSaga);
 }
 
 export default catalogSaga;
diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSelectors.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSelectors.js
deleted file mode 100644 (file)
index f4fe18e..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-* Copyright © 2018 European Support Limited
-*
-* 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 getCatalog = state => state.catalog;
-
-export const getWorkflows = createSelector(
-    getCatalog,
-    catalog => catalog.workflows
-);
-
-export const getSort = createSelector(getCatalog, catalog => catalog.sort);
index 42bd57c..b120929 100644 (file)
@@ -19,8 +19,8 @@ import PropTypes from 'prop-types';
 
 import { Tile, TileInfo, TileInfoLine } from 'sdc-ui/lib/react';
 
-const Workflows = ({ workflows, onWorkflowClick }) =>
-    workflows.map((workflow, index) => (
+const Workflows = ({ results, onWorkflowClick }) =>
+    results.map((workflow, index) => (
         <Tile
             key={`workflow.${index}`}
             headerText="WF"
@@ -35,8 +35,12 @@ const Workflows = ({ workflows, onWorkflowClick }) =>
     ));
 
 Workflows.propTypes = {
-    workflows: PropTypes.array,
+    results: PropTypes.array,
     onWorkflowClick: PropTypes.func
 };
 
+Workflows.defaultProps = {
+    results: []
+};
+
 export default Workflows;