From 8c3865259e5a33697e43662e12c22c52480ef0be Mon Sep 17 00:00:00 2001 From: Maria Khrustaleva Date: Thu, 14 Oct 2021 13:57:36 +0300 Subject: [PATCH] Optimizing getting cloud storage (#3776) * Init * Update getting preview * Fix updating * Remove excess * Apply comments * Remove excess * Fix --- cvat-ui/src/actions/cloud-storage-actions.ts | 59 +++---- .../cloud-storage-item.tsx | 32 ++-- .../cloud-storage-preview.tsx | 51 ++++++ .../cloud-storage-status.tsx | 34 ++-- .../cloud-storages-list.tsx | 23 +-- .../cloud-storages-page.tsx | 11 +- .../cloud-storages-page/styles.scss | 5 + .../update-cloud-storage-page.tsx | 3 +- .../src/reducers/cloud-storages-reducer.ts | 158 ++++++++++-------- cvat-ui/src/reducers/interfaces.ts | 22 ++- 10 files changed, 230 insertions(+), 168 deletions(-) create mode 100644 cvat-ui/src/components/cloud-storages-page/cloud-storage-preview.tsx diff --git a/cvat-ui/src/actions/cloud-storage-actions.ts b/cvat-ui/src/actions/cloud-storage-actions.ts index 439bab75..a95b6ad8 100644 --- a/cvat-ui/src/actions/cloud-storage-actions.ts +++ b/cvat-ui/src/actions/cloud-storage-actions.ts @@ -17,6 +17,8 @@ export enum CloudStorageActionTypes { GET_CLOUD_STORAGE_STATUS = 'GET_CLOUD_STORAGE_STATUS', GET_CLOUD_STORAGE_STATUS_SUCCESS = 'GET_CLOUD_STORAGE_STATUS_SUCCESS', GET_CLOUD_STORAGE_STATUS_FAILED = 'GET_CLOUD_STORAGE_STATUS_FAILED', + GET_CLOUD_STORAGE_PREVIEW = 'GET_CLOUD_STORAGE_PREVIEW', + GET_CLOUD_STORAGE_PREVIEW_SUCCESS = 'GET_CLOUD_STORAGE_PREVIEW_SUCCESS', GET_CLOUD_STORAGE_PREVIEW_FAILED = 'GET_CLOUD_STORAGE_PREVIEW_FAILED', CREATE_CLOUD_STORAGE = 'CREATE_CLOUD_STORAGE', CREATE_CLOUD_STORAGE_SUCCESS = 'CREATE_CLOUD_STORAGE_SUCCESS', @@ -38,15 +40,11 @@ const cloudStoragesActions = { getCloudStorages: () => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGES), getCloudStoragesSuccess: ( array: any[], - previews: string[], - statuses: string[], count: number, query: Partial, ) => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_SUCCESS, { array, - previews, - statuses, count, query, }), @@ -73,11 +71,15 @@ const cloudStoragesActions = { createAction(CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT_SUCCESS, { cloudStorageID, content }), loadCloudStorageContentFailed: (cloudStorageID: number, error: any) => createAction(CloudStorageActionTypes.LOAD_CLOUD_STORAGE_CONTENT_FAILED, { cloudStorageID, error }), - getCloudStorageStatus: () => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS), + getCloudStorageStatus: (id: number) => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS, { id }), getCloudStorageStatusSuccess: (cloudStorageID: number, status: string) => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_SUCCESS, { cloudStorageID, status }), getCloudStorageStatusFailed: (cloudStorageID: number, error: any) => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_FAILED, { cloudStorageID, error }), + getCloudStoragePreiew: (cloudStorageID: number) => + createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW, { cloudStorageID }), + getCloudStoragePreiewSuccess: (cloudStorageID: number, preview: string) => + createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW_SUCCESS, { cloudStorageID, preview }), getCloudStoragePreiewFailed: (cloudStorageID: number, error: any) => createAction(CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW_FAILED, { cloudStorageID, error }), }; @@ -105,22 +107,9 @@ export function getCloudStoragesAsync(query: Partial): Thunk } const array = Array.from(result); - const promises = array.map((cloudStorage: CloudStorage): string => - (cloudStorage as any).getPreview().catch((error: any) => { - dispatch(cloudStoragesActions.getCloudStoragePreiewFailed(cloudStorage.id, error)); - return ''; - })); - - const statusPromises = array.map((cloudStorage: CloudStorage): string => - (cloudStorage as any).getStatus().catch((error: any) => { - dispatch(cloudStoragesActions.getCloudStorageStatusFailed(cloudStorage.id, error)); - return ''; - })); dispatch(cloudStoragesActions.getCloudStoragesSuccess( array, - await Promise.all(promises), - await Promise.all(statusPromises), result.count, query, )); @@ -181,14 +170,26 @@ export function loadCloudStorageContentAsync(cloudStorage: CloudStorage): ThunkA }; } -// export function getCloudStorageStatusAsync(cloudStorage: CloudStorage): ThunkAction { -// return async (dispatch: ActionCreator): Promise => { -// dispatch(cloudStoragesActions.getCloudStorageStatus()); -// try { -// const result = await cloudStorage.getStatus(); -// dispatch(cloudStoragesActions.getCloudStorageStatusSuccess(cloudStorage.id, result)); -// } catch (error) { -// dispatch(cloudStoragesActions.getCloudStorageStatusFailed(cloudStorage.id, error)); -// } -// }; -// } +export function getCloudStorageStatusAsync(cloudStorage: CloudStorage): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + dispatch(cloudStoragesActions.getCloudStorageStatus(cloudStorage.id)); + try { + const result = await cloudStorage.getStatus(); + dispatch(cloudStoragesActions.getCloudStorageStatusSuccess(cloudStorage.id, result)); + } catch (error) { + dispatch(cloudStoragesActions.getCloudStorageStatusFailed(cloudStorage.id, error)); + } + }; +} + +export function getCloudStoragePreviewAsync(cloudStorage: CloudStorage): ThunkAction { + return async (dispatch: ActionCreator): Promise => { + dispatch(cloudStoragesActions.getCloudStoragePreiew(cloudStorage.id)); + try { + const result = await cloudStorage.getPreview(); + dispatch(cloudStoragesActions.getCloudStoragePreiewSuccess(cloudStorage.id, result)); + } catch (error) { + dispatch(cloudStoragesActions.getCloudStoragePreiewFailed(cloudStorage.id, error)); + } + }; +} diff --git a/cvat-ui/src/components/cloud-storages-page/cloud-storage-item.tsx b/cvat-ui/src/components/cloud-storages-page/cloud-storage-item.tsx index 3c14bcae..ee415b3e 100644 --- a/cvat-ui/src/components/cloud-storages-page/cloud-storage-item.tsx +++ b/cvat-ui/src/components/cloud-storages-page/cloud-storage-item.tsx @@ -5,7 +5,7 @@ import React, { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router'; -import { CloudSyncOutlined, MoreOutlined, QuestionCircleOutlined } from '@ant-design/icons'; +import { MoreOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import Card from 'antd/lib/card'; import Meta from 'antd/lib/card/Meta'; import Paragraph from 'antd/lib/typography/Paragraph'; @@ -20,17 +20,17 @@ import { CloudStorage, CombinedState } from 'reducers/interfaces'; import { deleteCloudStorageAsync } from 'actions/cloud-storage-actions'; import CVATTooltip from 'components/common/cvat-tooltip'; import Status from './cloud-storage-status'; +import Preview from './cloud-storage-preview'; interface Props { - cloudStorageInstance: CloudStorage; + cloudStorage: CloudStorage; } export default function CloudStorageItemComponent(props: Props): JSX.Element { const history = useHistory(); const dispatch = useDispatch(); - // cloudStorageInstance: {storage, preview, status} - const { cloudStorageInstance } = props; + const { cloudStorage } = props; const { id, displayName, @@ -39,10 +39,9 @@ export default function CloudStorageItemComponent(props: Props): JSX.Element { createdDate, updatedDate, description, - } = cloudStorageInstance.storage; - const { preview, status } = cloudStorageInstance; + } = cloudStorage; const deletes = useSelector((state: CombinedState) => state.cloudStorages.activities.deletes); - const deleted = cloudStorageInstance.storage.id in deletes ? deletes[cloudStorageInstance.storage.id] : false; + const deleted = cloudStorage.id in deletes ? deletes[cloudStorage.id] : false; const style: React.CSSProperties = {}; @@ -61,7 +60,7 @@ export default function CloudStorageItemComponent(props: Props): JSX.Element { content: `You are going to remove the cloudstorage "${displayName}". Continue?`, className: 'cvat-delete-cloud-storage-modal', onOk: () => { - dispatch(deleteCloudStorageAsync(cloudStorageInstance.storage)); + dispatch(deleteCloudStorageAsync(cloudStorage)); }, okButtonProps: { type: 'primary', @@ -69,24 +68,13 @@ export default function CloudStorageItemComponent(props: Props): JSX.Element { }, okText: 'Delete', }); - }, [cloudStorageInstance.storage.id]); + }, [cloudStorage.id]); return ( - {preview ? ( - Preview image - ) : ( -
- -
- )} + {description ? ( @@ -121,7 +109,7 @@ export default function CloudStorageItemComponent(props: Props): JSX.Element { Last updated {moment(updatedDate).fromNow()} - + diff --git a/cvat-ui/src/components/cloud-storages-page/cloud-storage-preview.tsx b/cvat-ui/src/components/cloud-storages-page/cloud-storage-preview.tsx new file mode 100644 index 00000000..fe45098e --- /dev/null +++ b/cvat-ui/src/components/cloud-storages-page/cloud-storage-preview.tsx @@ -0,0 +1,51 @@ +// Copyright (C) 2021 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useEffect } from 'react'; + +import { useDispatch, useSelector } from 'react-redux'; +import { CloudSyncOutlined } from '@ant-design/icons'; +import Spin from 'antd/lib/spin'; +import { getCloudStoragePreviewAsync } from 'actions/cloud-storage-actions'; +import { CombinedState, CloudStorage } from 'reducers/interfaces'; + +interface Props { + cloudStorage: CloudStorage; +} + +export default function Preview({ cloudStorage }: Props): JSX.Element { + const dispatch = useDispatch(); + const preview = useSelector((state: CombinedState) => state.cloudStorages.previews[cloudStorage.id]); + + useEffect(() => { + if (preview === undefined) { + dispatch(getCloudStoragePreviewAsync(cloudStorage)); + } + }, [preview]); + + if (!preview || (preview && preview.fetching)) { + return ( +
+ +
+ ); + } + + if (preview.initialized && !preview.preview) { + return ( +
+ +
+ ); + } + + return ( + Preview image + ); +} diff --git a/cvat-ui/src/components/cloud-storages-page/cloud-storage-status.tsx b/cvat-ui/src/components/cloud-storages-page/cloud-storage-status.tsx index bc8f4fef..6669379b 100644 --- a/cvat-ui/src/components/cloud-storages-page/cloud-storage-status.tsx +++ b/cvat-ui/src/components/cloud-storages-page/cloud-storage-status.tsx @@ -2,27 +2,41 @@ // // SPDX-License-Identifier: MIT -import React from 'react'; +import React, { useEffect } from 'react'; import Paragraph from 'antd/lib/typography/Paragraph'; import Text from 'antd/lib/typography/Text'; +import { useDispatch, useSelector } from 'react-redux'; +import { getCloudStorageStatusAsync } from 'actions/cloud-storage-actions'; +import { CombinedState, CloudStorage } from 'reducers/interfaces'; import { StorageStatuses } from '../../utils/enums'; interface Props { - status: string; + cloudStorage: CloudStorage; } -export default function Status(props: Props): JSX.Element { - const { status } = props; - // TODO: make dynamic loading of statuses separately in the future +export default function Status({ cloudStorage }: Props): JSX.Element { + const dispatch = useDispatch(); + const status = useSelector((state: CombinedState) => state.cloudStorages.statuses[cloudStorage.id]); + + useEffect(() => { + if (status === undefined) { + dispatch(getCloudStorageStatusAsync(cloudStorage)); + } + }, [status]); + + let message: JSX.Element; + if (!status || (status && status.fetching)) { + message = Loading ...; + } else if (status.initialized && !status.status) { + message = Error; + } else { + message = {status.status}; + } return ( Status: - {status ? ( - {status} - ) : ( - Loading ... - )} + {message} ); } diff --git a/cvat-ui/src/components/cloud-storages-page/cloud-storages-list.tsx b/cvat-ui/src/components/cloud-storages-page/cloud-storages-list.tsx index c1616b40..85917431 100644 --- a/cvat-ui/src/components/cloud-storages-page/cloud-storages-list.tsx +++ b/cvat-ui/src/components/cloud-storages-page/cloud-storages-list.tsx @@ -11,8 +11,6 @@ import CloudStorageItemComponent from './cloud-storage-item'; interface Props { storages: CloudStorage[]; - previews: string[]; - statuses: string[]; totalCount: number; page: number; onChangePage(page: number): void; @@ -20,25 +18,16 @@ interface Props { export default function StoragesList(props: Props): JSX.Element { const { - storages, previews, statuses, totalCount, page, onChangePage, + storages, totalCount, page, onChangePage, } = props; const groupedStorages = storages.reduce( (acc: CloudStorage[][], storage: CloudStorage, index: number): CloudStorage[][] => { if (index && index % 4) { - acc[acc.length - 1].push({ - storage, - preview: previews[index], - status: statuses[index], - }); + acc[acc.length - 1].push(storage); } else { - acc.push([{ - storage, - preview: previews[index], - status: statuses[index], - }]); + acc.push([storage]); } - return acc; }, [], @@ -50,10 +39,10 @@ export default function StoragesList(props: Props): JSX.Element { {groupedStorages.map( (instances: CloudStorage[]): JSX.Element => ( - + {instances.map((instance: CloudStorage) => ( - - + + ))} diff --git a/cvat-ui/src/components/cloud-storages-page/cloud-storages-page.tsx b/cvat-ui/src/components/cloud-storages-page/cloud-storages-page.tsx index 0a02a9f2..bd2517e1 100644 --- a/cvat-ui/src/components/cloud-storages-page/cloud-storages-page.tsx +++ b/cvat-ui/src/components/cloud-storages-page/cloud-storages-page.tsx @@ -9,7 +9,7 @@ import { useHistory } from 'react-router'; import { Row, Col } from 'antd/lib/grid'; import Spin from 'antd/lib/spin'; -import { CloudStorage, CloudStoragesQuery, CombinedState } from 'reducers/interfaces'; +import { CloudStoragesQuery, CombinedState } from 'reducers/interfaces'; import { getCloudStoragesAsync } from 'actions/cloud-storage-actions'; import CloudStoragesListComponent from './cloud-storages-list'; import EmptyCloudStorageListComponent from './empty-cloud-storages-list'; @@ -21,12 +21,7 @@ export default function StoragesPageComponent(): JSX.Element { const { search } = history.location; const totalCount = useSelector((state: CombinedState) => state.cloudStorages.count); const isFetching = useSelector((state: CombinedState) => state.cloudStorages.fetching); - const current = useSelector((state: CombinedState) => state.cloudStorages.current) - .map((cloudStrage: CloudStorage) => cloudStrage.instance); - const previews = useSelector((state: CombinedState) => state.cloudStorages.current) - .map((cloudStrage: CloudStorage) => cloudStrage.preview as string); - const statuses = useSelector((state: CombinedState) => state.cloudStorages.current) - .map((cloudStrage: CloudStorage) => cloudStrage.status as string); + const current = useSelector((state: CombinedState) => state.cloudStorages.current); const query = useSelector((state: CombinedState) => state.cloudStorages.gettingQuery); const onSearch = useCallback( (_query: CloudStoragesQuery) => { @@ -98,8 +93,6 @@ export default function StoragesPageComponent(): JSX.Element { totalCount={totalCount} page={query.page} storages={current} - previews={previews} - statuses={statuses} onChangePage={onChangePage} /> ) : ( diff --git a/cvat-ui/src/components/cloud-storages-page/styles.scss b/cvat-ui/src/components/cloud-storages-page/styles.scss index d1daa278..06e654bb 100644 --- a/cvat-ui/src/components/cloud-storages-page/styles.scss +++ b/cvat-ui/src/components/cloud-storages-page/styles.scss @@ -48,7 +48,12 @@ margin-bottom: 0; } + .cvat-cloud-storage-item-loading-preview, .cvat-cloud-storage-item-empty-preview { + .ant-spin { + position: inherit; + } + font-size: $grid-unit-size * 15; text-align: center; height: $grid-unit-size * 24; diff --git a/cvat-ui/src/components/update-cloud-storage-page/update-cloud-storage-page.tsx b/cvat-ui/src/components/update-cloud-storage-page/update-cloud-storage-page.tsx index 465d86b7..9b13bd93 100644 --- a/cvat-ui/src/components/update-cloud-storage-page/update-cloud-storage-page.tsx +++ b/cvat-ui/src/components/update-cloud-storage-page/update-cloud-storage-page.tsx @@ -24,8 +24,7 @@ export default function UpdateCloudStoragePageComponent(): JSX.Element { const cloudStorageId = +useParams().id; const isFetching = useSelector((state: CombinedState) => state.cloudStorages.fetching); const isInitialized = useSelector((state: CombinedState) => state.cloudStorages.initialized); - const cloudStorages = useSelector((state: CombinedState) => state.cloudStorages.current) - .map((cloudStrage) => cloudStrage.instance); + const cloudStorages = useSelector((state: CombinedState) => state.cloudStorages.current); const [cloudStorage] = cloudStorages.filter((_cloudStorage) => _cloudStorage.id === cloudStorageId); useEffect(() => { diff --git a/cvat-ui/src/reducers/cloud-storages-reducer.ts b/cvat-ui/src/reducers/cloud-storages-reducer.ts index 6cbf5e35..5e5ecd6f 100644 --- a/cvat-ui/src/reducers/cloud-storages-reducer.ts +++ b/cvat-ui/src/reducers/cloud-storages-reducer.ts @@ -11,7 +11,8 @@ const defaultState: CloudStoragesState = { fetching: false, count: 0, current: [], - // currentStatuses: [], + statuses: {}, + previews: {}, gettingQuery: { page: 1, id: null, @@ -42,12 +43,6 @@ const defaultState: CloudStoragesState = { fetching: false, error: '', }, - // getsStatus: { - // cloudStorageID: null, - // status: null, - // fetching: false, - // error: '', - // }, }, }; @@ -71,18 +66,12 @@ export default ( fetching: true, count: 0, current: [], - // currentStatuses: [], + statuses: {}, + previews: {}, }; case CloudStorageActionTypes.GET_CLOUD_STORAGE_SUCCESS: { - const { count, query } = action.payload; + const { count, query, array } = action.payload; - const combined = action.payload.array.map( - (cloudStorage: any, index: number): CloudStorage => ({ - instance: cloudStorage, - preview: action.payload.previews[index], - status: action.payload.statuses[index], - }), - ); return { ...state, initialized: true, @@ -92,7 +81,7 @@ export default ( ...defaultState.gettingQuery, ...query, }, - current: combined, + current: array, }; } case CloudStorageActionTypes.GET_CLOUD_STORAGE_FAILED: { @@ -279,61 +268,86 @@ export default ( }, }; } - // case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS: - // return { - // ...state, - // activities: { - // ...state.activities, - // getsStatus: { - // cloudStorageID: null, - // status: null, - // error: '', - // fetching: true, - // }, - // }, - // }; - // case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_SUCCESS: { - // const { cloudStorageID, status } = action.payload; - // const statuses = state.currentStatuses; - // const index = statuses.findIndex((item) => item.id === cloudStorageID); - // if (index !== -1) { - // statuses[index] = { - // ...statuses[index], - // status, - // }; - // } else { - // statuses.push({ - // id: cloudStorageID, - // status, - // }); - // } - // return { - // ...state, - // currentStatuses: statuses, - // activities: { - // ...state.activities, - // getsStatus: { - // cloudStorageID, - // status, - // error: '', - // fetching: false, - // }, - // }, - // }; - // } - // case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_FAILED: { - // return { - // ...state, - // activities: { - // ...state.activities, - // getsStatus: { - // ...state.activities.getsStatus, - // error: action.payload.error.toString(), - // fetching: false, - // }, - // }, - // }; - // } + case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS: { + const { id } = action.payload; + const { statuses } = state; + statuses[id] = { + status: null, + fetching: true, + initialized: false, + }; + return { + ...state, + statuses, + }; + } + case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_SUCCESS: { + const { cloudStorageID, status } = action.payload; + const { statuses } = state; + statuses[cloudStorageID] = { + ...statuses[cloudStorageID], + status, + initialized: true, + fetching: false, + }; + return { + ...state, + statuses, + }; + } + case CloudStorageActionTypes.GET_CLOUD_STORAGE_STATUS_FAILED: { + const { cloudStorageID } = action.payload; + const { statuses } = state; + statuses[cloudStorageID] = { + ...statuses[cloudStorageID], + initialized: true, + fetching: false, + }; + return { + ...state, + statuses, + }; + } + case CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW: { + const { cloudStorageID } = action.payload; + const { previews } = state; + previews[cloudStorageID] = { + preview: '', + fetching: true, + initialized: false, + }; + return { + ...state, + previews, + }; + } + case CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW_SUCCESS: { + const { cloudStorageID, preview } = action.payload; + const { previews } = state; + previews[cloudStorageID] = { + ...previews[cloudStorageID], + preview, + initialized: true, + fetching: false, + }; + return { + ...state, + previews, + }; + } + case CloudStorageActionTypes.GET_CLOUD_STORAGE_PREVIEW_FAILED: { + const { cloudStorageID } = action.payload; + const { previews } = state; + previews[cloudStorageID] = { + ...previews[cloudStorageID], + initialized: true, + fetching: false, + }; + return { + ...state, + previews, + }; + } case AuthActionTypes.LOGOUT_SUCCESS: { return { ...defaultState }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index f804fcd9..49c05190 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -134,6 +134,15 @@ export interface CloudStoragesQuery { [key: string]: string | number | null | undefined; } +interface CloudStorageAdditional { + fetching: boolean; + initialized: boolean; + status: string | null; + preview: string; +} +type CloudStorageStatus = Pick; +type CloudStoragePreview = Pick; + export type CloudStorage = any; export interface CloudStoragesState { @@ -141,7 +150,12 @@ export interface CloudStoragesState { fetching: boolean; count: number; current: CloudStorage[]; - // currentStatuses: any[]; + statuses: { + [index: number]: CloudStorageStatus; + }; + previews: { + [index: number]: CloudStoragePreview; + }; gettingQuery: CloudStoragesQuery; activities: { creates: { @@ -163,12 +177,6 @@ export interface CloudStoragesState { fetching: boolean; error: string; }; - // getsStatus: { - // cloudStorageID: number | null; - // status: string | null; - // fetching: boolean; - // error: string; - // }; }; }