From d2d451214ba73d35d2b73764b74ab70f1c9ea966 Mon Sep 17 00:00:00 2001 From: Malek Date: Wed, 25 Jul 2018 08:51:50 +0300 Subject: Refactor UI catalog page, Fix pagination Issue-ID: SDC-1558 Change-Id: I80e5ded1b91f86b696fa9b29eb8364febfe0ebba Signed-off-by: Malek --- .../main/frontend/src/features/catalog/Catalog.js | 10 +-- .../frontend/src/features/catalog/CatalogView.jsx | 40 +++++----- .../catalog/__tests__/catalogActions-test.js | 68 +++++++++-------- .../catalog/__tests__/catalogReducer-test.js | 86 ++++++++++++++++------ .../catalog/__tests__/catalogSagas-test.js | 64 +++++++++++++++- .../catalog/__tests__/catalogSelectors-test.js | 71 ------------------ .../src/features/catalog/catalogActions.js | 18 ++--- .../frontend/src/features/catalog/catalogApi.js | 16 +++- .../src/features/catalog/catalogConstants.js | 12 +-- .../src/features/catalog/catalogReducer.js | 40 +++++----- .../frontend/src/features/catalog/catalogSagas.js | 29 +++----- .../src/features/catalog/catalogSelectors.js | 26 ------- .../frontend/src/features/catalog/views/Main.jsx | 4 + .../src/features/catalog/views/Workflows.jsx | 10 ++- 14 files changed, 258 insertions(+), 236 deletions(-) delete mode 100644 workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSelectors-test.js delete mode 100644 workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSelectors.js diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js index 503423f5..5eeaaa23 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/Catalog.js @@ -17,21 +17,19 @@ 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( diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx index 0af791df..99cee755 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/CatalogView.jsx @@ -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 (
@@ -81,7 +85,7 @@ class CatalogView extends React.Component {
@@ -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: () => {} }; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js index 15a9c8c5..f49c2fdc 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogActions-test.js @@ -14,51 +14,57 @@ * 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 + }); }); }); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js index cebddb35..c4f34e7f 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogReducer-test.js @@ -16,37 +16,77 @@ '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 + }); }); }); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js index f06d240f..cdea344c 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSagas-test.js @@ -16,6 +16,68 @@ '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 index 0aa73ce4..00000000 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/__tests__/catalogSelectors-test.js +++ /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); - }); -}); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js index 5be27887..89f53e4c 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogActions.js @@ -16,20 +16,18 @@ 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 } }); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js index 7273d215..79b271ce 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogApi.js @@ -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}`); } }; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js index 39f90dd0..3120ad73 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogConstants.js @@ -15,11 +15,13 @@ */ 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`; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js index 9816a96f..cbd7ab70 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogReducer.js @@ -15,20 +15,16 @@ */ 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: diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js index ffc6d18d..8dcfc610 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSagas.js @@ -14,39 +14,32 @@ * 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 index f4fe18e3..00000000 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/catalogSelectors.js +++ /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); diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx index abf58be2..5f60ebda 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Main.jsx @@ -71,4 +71,8 @@ Main.propTypes = { children: PropTypes.node }; +Main.defaultProps = { + total: 0 +}; + export default Main; diff --git a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx index 42bd57c2..b120929a 100644 --- a/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx +++ b/workflow/workflow-designer-ui/src/main/frontend/src/features/catalog/views/Workflows.jsx @@ -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) => ( )); Workflows.propTypes = { - workflows: PropTypes.array, + results: PropTypes.array, onWorkflowClick: PropTypes.func }; +Workflows.defaultProps = { + results: [] +}; + export default Workflows; -- cgit 1.2.3-korg