[Code] Implement the clone/index/delete progress design for admin page project lists (#27952)

This commit is contained in:
Yulong 2019-01-11 01:14:18 +08:00 committed by GitHub
parent e7f9090060
commit 3e0cf0518e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 185 additions and 37 deletions

View file

@ -15,6 +15,7 @@ export const fetchReposFailed = createAction<Error>('FETCH REPOS FAILED');
export const deleteRepo = createAction<string>('DELETE REPOS');
export const deleteRepoSuccess = createAction<string>('DELETE REPOS SUCCESS');
export const deleteRepoFinished = createAction<string>('DELETE REPOS FINISHED');
export const deleteRepoFailed = createAction<Error>('DELETE REPOS FAILED');
export const indexRepo = createAction<string>('INDEX REPOS');

View file

@ -13,3 +13,13 @@ export const loadStatusFailed = createAction<string>('LOAD STATUS FAILED');
export const loadRepo = createAction<string>('LOAD REPO');
export const loadRepoSuccess = createAction<any>('LOAD REPO SUCCESS');
export const loadRepoFailed = createAction<any>('LOAD REPO FAILED');
export interface RepoProgress {
repoUri: string;
progress: number;
cloneProgress?: any;
}
export const updateCloneProgress = createAction<RepoProgress>('UPDATE CLONE PROGRESS');
export const updateIndexProgress = createAction<RepoProgress>('UPDATE INDEX PROGRESS');
export const updateDeleteProgress = createAction<RepoProgress>('UPDATE DELETE PROGRESS');

View file

@ -4,7 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiPanel, EuiText, EuiTextColor } from '@elastic/eui';
import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiProgress,
EuiText,
EuiTextColor,
} from '@elastic/eui';
import moment from 'moment';
import React from 'react';
import { connect } from 'react-redux';
@ -12,37 +20,42 @@ import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { Repository } from 'x-pack/plugins/code/model';
import { deleteRepo, indexRepo, initRepoCommand } from '../../actions';
import { RepoState, RepoStatus } from '../../reducers/status';
const Footer = styled.div``;
const stateColor = {
[RepoState.CLONING]: 'secondary',
[RepoState.DELETING]: 'accent',
[RepoState.INDEXING]: 'primary',
};
class CodeProjectItem extends React.PureComponent<{
project: Repository;
isDeleting?: boolean;
isIndexing?: boolean;
isCloning?: boolean;
isAdmin: boolean;
status: any;
status: RepoStatus;
deleteRepo: (uri: string) => void;
indexRepo: (uri: string) => void;
initRepoCommand: (uri: string) => void;
}> {
public render() {
const { isDeleting, project, isIndexing, isCloning } = this.props;
const { project, status } = this.props;
const { name, org, nextUpdateTimestamp, uri } = project;
const onClickDelete = () => this.props.deleteRepo(uri);
const onClickIndex = () => this.props.indexRepo(uri);
let footer = null;
if (isDeleting) {
footer = <Footer>DELETING...</Footer>;
} else if (isIndexing) {
footer = <Footer>INDEXING...</Footer>;
} else if (isCloning) {
footer = <Footer />;
} else {
if (!status || status.state === RepoState.READY) {
footer = <Footer>LAST UPDATED: {moment(nextUpdateTimestamp).fromNow()}</Footer>;
} else if (status.state === RepoState.DELETING) {
footer = <Footer>DELETING...</Footer>;
} else if (status.state === RepoState.INDEXING) {
footer = <Footer>INDEXING...</Footer>;
} else if (status.state === RepoState.CLONING) {
footer = <Footer>CLONING...</Footer>;
}
return (
<EuiPanel>
<EuiPanel style={{ position: 'relative', marginBottom: '8px' }}>
{this.renderProgress()}
<EuiFlexGroup alignItems="center" justifyContent="flexStart">
<EuiFlexItem grow={false}>
<EuiIcon type="starEmpty" color="subdued" />
@ -98,6 +111,26 @@ class CodeProjectItem extends React.PureComponent<{
</EuiPanel>
);
}
private renderProgress() {
const { status } = this.props;
if (status && status.state !== RepoState.READY) {
const color = stateColor[status.state] as 'primary' | 'secondary' | 'accent';
if (status.progress! > 0) {
return (
<EuiProgress
max={100}
value={status.progress}
size="xs"
color={color}
position="absolute"
/>
);
} else {
return <EuiProgress size="xs" color={color} position="absolute" />;
}
}
}
}
const mapDispatchToProps = {

View file

@ -53,7 +53,7 @@ class CodeProjectTab extends React.PureComponent<
};
public render() {
const { projects, isAdmin } = this.props;
const { projects, isAdmin, status } = this.props;
const projectsCount = projects.length;
const modal = this.state.showImportProjectModal && (
<EuiModal onClose={this.closeModal}>

View file

@ -87,4 +87,5 @@ const mapStateToProps = (state: RootState) => ({
isNotFound: state.file.isNotFound,
});
// @ts-ignore
export const Main = connect(mapStateToProps)(CodeMain);

View file

@ -10,7 +10,7 @@ import { Repository } from '../../model';
import { RepoConfigs } from '../../model/workspace';
import {
deleteRepoSuccess,
deleteRepoFinished,
fetchRepoConfigSuccess,
fetchRepos,
fetchReposFailed,
@ -68,6 +68,7 @@ export const repository = handleActions(
},
[String(deleteRepoSuccess)]: (state: RepositoryState, action: Action<any>) =>
produce<RepositoryState>(state, (draft: RepositoryState) => {
feature/merge-code
draft.repositories = state.repositories.filter(repo => repo.uri !== action.payload);
}),
[String(importRepo)]: (state: RepositoryState) =>

View file

@ -6,10 +6,30 @@
import produce from 'immer';
import { handleActions } from 'redux-actions';
import { loadStatus, loadStatusFailed, loadStatusSuccess } from '../actions/status';
import {
loadStatus,
loadStatusFailed,
loadStatusSuccess,
updateCloneProgress,
updateDeleteProgress,
updateIndexProgress,
} from '../actions/status';
export enum RepoState {
CLONING,
DELETING,
INDEXING,
READY,
}
export interface RepoStatus {
state: RepoState;
progress?: number;
cloneProgress?: any;
}
export interface StatusState {
status: { [key: string]: any };
status: { [key: string]: RepoStatus };
loading: boolean;
error?: Error;
}
@ -27,7 +47,10 @@ export const status = handleActions(
}),
[String(loadStatusSuccess)]: (state: StatusState, action: any) =>
produce<StatusState>(state, draft => {
draft.status[action.payload.repoUri] = action.payload.status;
draft.status[action.payload.repoUri] = {
...action.payload.status,
state: RepoState.READY,
};
draft.loading = false;
}),
[String(loadStatusFailed)]: (state: StatusState, action: any) =>
@ -35,6 +58,28 @@ export const status = handleActions(
draft.loading = false;
draft.error = action.payload;
}),
[String(updateCloneProgress)]: (state: StatusState, action: any) =>
produce<StatusState>(state, draft => {
draft.status[action.payload.repoUri] = {
...action.payload,
state: RepoState.CLONING,
};
}),
[String(updateIndexProgress)]: (state: StatusState, action: any) =>
produce<StatusState>(state, draft => {
const progress = action.payload.progress;
draft.status[action.payload.repoUri] = {
...action.payload,
state: progress < 100 ? RepoState.INDEXING : RepoState.READY,
};
}),
[String(updateDeleteProgress)]: (state: StatusState, action: any) =>
produce<StatusState>(state, draft => {
draft.status[action.payload.repoUri] = {
...action.payload,
state: RepoState.DELETING,
};
}),
},
initialState
);

View file

@ -27,7 +27,7 @@ import {
watchInitRepoCmd,
} from './repository';
import { watchDocumentSearch, watchRepositorySearch, watchSearchRouteChange } from './search';
import { watchRepoCloneSuccess } from './status';
import { watchRepoCloneSuccess, watchRepoDeleteFinished } from './status';
import { watchLoadStructure } from './structure';
import { watchLoadUserConfig } from './user';
@ -57,6 +57,7 @@ export function* rootSaga() {
yield fork(watchLoadBlame);
yield fork(watchBlame);
yield fork(watchRepoCloneSuccess);
yield fork(watchRepoDeleteFinished);
yield fork(watchLoadLanguageServers);
yield fork(watchInstallLanguageServer);
}

View file

@ -26,6 +26,9 @@ import {
indexRepoSuccess,
initRepoCommand,
loadUserConfig,
updateCloneProgress,
updateDeleteProgress,
updateIndexProgress,
} from '../actions';
import { loadLanguageServers } from '../actions/language_server';
import { history } from '../utils/url';
@ -56,6 +59,12 @@ function* handleDeleteRepo(action: Action<string>) {
try {
yield call(requestDeleteRepo, action.payload || '');
yield put(deleteRepoSuccess(action.payload || ''));
yield put(
updateDeleteProgress({
repoUri: action.payload as string,
progress: 0,
})
);
} catch (err) {
yield put(deleteRepoFailed(err));
}
@ -65,6 +74,12 @@ function* handleIndexRepo(action: Action<string>) {
try {
yield call(requestIndexRepo, action.payload || '');
yield put(indexRepoSuccess(action.payload || ''));
yield put(
updateIndexProgress({
repoUri: action.payload as string,
progress: 0,
})
);
} catch (err) {
yield put(indexRepoFailed(err));
}
@ -82,6 +97,12 @@ function* handleImportRepo(action: Action<string>) {
try {
const data = yield call(requestImportRepo, action.payload || '');
yield put(importRepoSuccess(data));
yield put(
updateCloneProgress({
repoUri: action.payload as string,
progress: 0,
})
);
} catch (err) {
yield put(importRepoFailed(err));
}

View file

@ -6,10 +6,9 @@
import { Action } from 'redux-actions';
import { put, select, takeEvery } from 'redux-saga/effects';
import { WorkerReservedProgress } from '../../model';
import { Match, routeChange } from '../actions';
import { loadStatusSuccess } from '../actions/status';
import { deleteRepoFinished, Match, routeChange, updateDeleteProgress } from '../actions';
import { loadStatusSuccess } from '../actions';
import * as ROUTES from '../components/routes';
import { RootState } from '../reducers';
@ -19,6 +18,9 @@ const pattern = (action: Action<any>) =>
action.type === String(loadStatusSuccess) &&
action.payload!.status.progress === WorkerReservedProgress.COMPLETED;
const deletePattern = (action: Action<any>) =>
action.type === String(updateDeleteProgress) && action.payload.progress === 100;
function* handleRepoCloneSuccess() {
const match: Match = yield select(matchSelector);
if (match.path === ROUTES.MAIN || match.path === ROUTES.MAIN_ROOT) {
@ -29,3 +31,11 @@ function* handleRepoCloneSuccess() {
export function* watchRepoCloneSuccess() {
yield takeEvery(pattern, handleRepoCloneSuccess);
}
function* handleRepoDeleteFinished(action: any) {
yield put(deleteRepoFinished(action.payload.repoUri));
}
export function* watchRepoDeleteFinished() {
yield takeEvery(deletePattern, handleRepoDeleteFinished);
}

View file

@ -9,7 +9,12 @@ import io from 'socket.io-client';
import chrome from 'ui/chrome';
import { SocketKind } from '../model';
import { loadStatusSuccess } from './actions';
import {
loadStatusSuccess,
updateCloneProgress,
updateDeleteProgress,
updateIndexProgress,
} from './actions';
import { installLanguageServerSuccess } from './actions/language_server';
export function bindSocket(store: Store) {
@ -18,26 +23,46 @@ export function bindSocket(store: Store) {
socket.on(SocketKind.CLONE_PROGRESS, (data: any) => {
const { repoUri, progress, cloneProgress } = data;
store.dispatch(
loadStatusSuccess({
repoUri,
status: {
uri: repoUri,
if (progress === 100) {
store.dispatch(
loadStatusSuccess({
repoUri,
status: {
uri: repoUri,
progress,
cloneProgress,
},
})
);
} else {
store.dispatch(
updateCloneProgress({
repoUri,
progress,
cloneProgress,
},
})
);
}
});
socket.on(SocketKind.INDEX_PROGRESS, (data: any) => {
const { repoUri, progress } = data;
store.dispatch(
updateIndexProgress({
progress,
repoUri,
})
);
});
socket.on(SocketKind.INDEX_PROGRESS, (data: any) => {
// const { repoUri, progress } = data;
// TODO(qianliang): distribute index progress update actions to store.
});
socket.on(SocketKind.DELETE_PROGRESS, (data: any) => {
// const { repoUri, progress } = data;
// TODO(qianliang): distribute delete progress update actions to store.
const { repoUri, progress } = data;
store.dispatch(
updateDeleteProgress({
progress,
repoUri,
})
);
});
socket.on(SocketKind.INSTALL_PROGRESS, (data: any) => {