Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimizing getting cloud storage #3776

Merged
merged 7 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 30 additions & 29 deletions cvat-ui/src/actions/cloud-storage-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
}),
Expand All @@ -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 }),
};
Expand Down Expand Up @@ -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,
));
Expand Down Expand Up @@ -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));
}
};
}
32 changes: 10 additions & 22 deletions cvat-ui/src/components/cloud-storages-page/cloud-storage-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand All @@ -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 = {};

Expand All @@ -61,32 +60,21 @@ 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',
danger: true,
},
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' />
Expand Down Expand Up @@ -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'>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

import React, { useEffect } from 'react';

import { useDispatch, useSelector, shallowEqual } 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(props: Props): JSX.Element {
const { cloudStorage } = props;
const dispatch = useDispatch();
const previews = useSelector((state: CombinedState) => state.cloudStorages.previews, shallowEqual);
const preview = 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.error) {
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
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,43 @@
//
// 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, shallowEqual } 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
const { cloudStorage } = props;
const dispatch = useDispatch();
const statuses = useSelector((state: CombinedState) => state.cloudStorages.statuses, shallowEqual);
const status = 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.error) {
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>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,23 @@ import CloudStorageItemComponent from './cloud-storage-item';

interface Props {
storages: CloudStorage[];
previews: string[];
statuses: string[];
totalCount: number;
page: number;
onChangePage(page: number): void;
}

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;
},
[],
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) => {
Expand Down Expand Up @@ -98,8 +93,6 @@ export default function StoragesPageComponent(): JSX.Element {
totalCount={totalCount}
page={query.page}
storages={current}
previews={previews}
statuses={statuses}
onChangePage={onChangePage}
/>
) : (
Expand Down
5 changes: 5 additions & 0 deletions cvat-ui/src/components/cloud-storages-page/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading