Optimizing getting cloud storage (#3776)

* Init

* Update getting preview

* Fix updating

* Remove excess

* Apply comments

* Remove excess

* Fix
main
Maria Khrustaleva 4 years ago committed by GitHub
parent 68d412b134
commit 8c3865259e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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<CloudStoragesQuery>,
) =>
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<CloudStoragesQuery>): 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<Dispatch>): Promise<void> => {
// 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<Dispatch>): Promise<void> => {
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<Dispatch>): Promise<void> => {
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));
}
};
}

@ -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 (
<Card
cover={(
<>
{preview ? (
<img
className='cvat-cloud-storage-item-preview'
src={preview}
alt='Preview image'
aria-hidden
/>
) : (
<div className='cvat-cloud-storage-item-empty-preview' aria-hidden>
<CloudSyncOutlined />
</div>
)}
<Preview cloudStorage={cloudStorage} />
{description ? (
<CVATTooltip overlay={description}>
<QuestionCircleOutlined className='cvat-cloud-storage-description-icon' />
@ -121,7 +109,7 @@ export default function CloudStorageItemComponent(props: Props): JSX.Element {
<Text type='secondary'>Last updated </Text>
<Text type='secondary'>{moment(updatedDate).fromNow()}</Text>
</Paragraph>
<Status status={status} />
<Status cloudStorage={cloudStorage} />
<Dropdown
overlay={(
<Menu className='cvat-project-actions-menu'>

@ -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 (
<div className='cvat-cloud-storage-item-loading-preview' aria-hidden>
<Spin size='default' />
</div>
);
}
if (preview.initialized && !preview.preview) {
return (
<div className='cvat-cloud-storage-item-empty-preview' aria-hidden>
<CloudSyncOutlined />
</div>
);
}
return (
<img
className='cvat-cloud-storage-item-preview'
src={preview.preview}
alt='Preview image'
aria-hidden
/>
);
}

@ -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 = <Text type='warning'>Loading ...</Text>;
} else if (status.initialized && !status.status) {
message = <Text type='danger'>Error</Text>;
} else {
message = <Text type={status.status === StorageStatuses.AVAILABLE ? 'success' : 'danger'}>{status.status}</Text>;
}
return (
<Paragraph>
<Text type='secondary'>Status: </Text>
{status ? (
<Text type={status === StorageStatuses.AVAILABLE ? 'success' : 'danger'}>{status}</Text>
) : (
<Text type='warning'>Loading ...</Text>
)}
{message}
</Paragraph>
);
}

@ -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 {
<Col span={24} className='cvat-cloud-storages-list'>
{groupedStorages.map(
(instances: CloudStorage[]): JSX.Element => (
<Row key={instances[0].storage.id} gutter={[8, 8]}>
<Row key={instances[0].id} gutter={[8, 8]}>
{instances.map((instance: CloudStorage) => (
<Col span={6} key={instance.storage.id}>
<CloudStorageItemComponent cloudStorageInstance={instance} />
<Col span={6} key={instance.id}>
<CloudStorageItemComponent cloudStorage={instance} />
</Col>
))}
</Row>

@ -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}
/>
) : (

@ -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;

@ -24,8 +24,7 @@ export default function UpdateCloudStoragePageComponent(): JSX.Element {
const cloudStorageId = +useParams<ParamType>().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(() => {

@ -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 };
}

@ -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<CloudStorageAdditional, 'fetching' | 'initialized' | 'status'>;
type CloudStoragePreview = Pick<CloudStorageAdditional, 'fetching' | 'initialized' | 'preview'>;
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;
// };
};
}

Loading…
Cancel
Save