Compare commits
1 Commits
dependabot
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88dc87db05 |
31445
package-lock.json
generated
Normal file
31445
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"babel-loader": "8.0.4",
|
||||
"bootstrap": "^5.1.0",
|
||||
"moment": "^2.24.0",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
@@ -11,7 +12,7 @@
|
||||
"react-scripts": "2.1.3",
|
||||
"react-timeago": "^4.3.0",
|
||||
"redux": "^4.0.1",
|
||||
"redux-observable": "^1.0.0",
|
||||
"redux-observable": "^1.2.0",
|
||||
"rxjs": "^6.4.0",
|
||||
"typesafe-actions": "^3.0.0",
|
||||
"typescript": "3.2.4"
|
||||
|
||||
@@ -1 +1,71 @@
|
||||
/* .App {} */
|
||||
|
||||
.app-root {
|
||||
color: rgb(82, 1, 245);
|
||||
}
|
||||
|
||||
.header {
|
||||
padding-bottom: 2rem;
|
||||
border-bottom: 3px solid rgb(82, 1, 245)
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 3px solid rgb(82, 1, 245) !important;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: rgb(82, 1, 245);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
margin-top: 3.375rem;
|
||||
max-height: 21rem;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
margin-right: 5rem;
|
||||
}
|
||||
|
||||
input[type=range]{
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
width: 300px;
|
||||
height: 5px;
|
||||
background: rgb(82, 1, 245);
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
/* border-radius: 50%; */
|
||||
background: rgb(82, 1, 245);
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: rgb(82, 1, 245);
|
||||
}
|
||||
|
||||
.primary-details {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.WorkloadItem-secondaryButton {
|
||||
margin-left: 1.5rem;
|
||||
background: rgb(82, 1, 245, 0.4);
|
||||
color: rgb(82, 1, 245);
|
||||
}
|
||||
@@ -6,20 +6,21 @@ import './App.css';
|
||||
|
||||
|
||||
class App extends PureComponent {
|
||||
|
||||
nowHandler () : number {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>CloudWork</h1>
|
||||
<hr />
|
||||
|
||||
<div >
|
||||
<WorkloadFormContainer />
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<h2>Workloads</h2>
|
||||
<WorkloadListContainer />
|
||||
<div className="app-root container mt-5 pt-5 d-flex flex-column">
|
||||
<h1 className="header">CloudWork</h1>
|
||||
<div className="d-flex flex-row-reverse mt-5">
|
||||
<div className="form-container border p-5" style={{flex: 2}}><WorkloadFormContainer /></div>
|
||||
<div className="list-container" style={{flex: 5}}>
|
||||
<h2 className="mb-3">Workloads</h2>
|
||||
<WorkloadListContainer nowHandler={this.nowHandler}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -31,13 +31,13 @@ class WorkloadForm extends React.PureComponent<WorkloadFormProps, WorkloadFormSt
|
||||
render() {
|
||||
return (
|
||||
<form>
|
||||
<h2>Create workload</h2>
|
||||
<h2 className="form-header mb-5">Create workload</h2>
|
||||
|
||||
<div>
|
||||
<label>
|
||||
Complexity: {this.state.complexity}
|
||||
<br />
|
||||
<label className="d-block w-100">
|
||||
<span className="mb-2 d-block">Complexity: {this.state.complexity}</span>
|
||||
<input
|
||||
className="w-100"
|
||||
value={this.state.complexity}
|
||||
onChange={(e) => this.setState({ complexity: Number(e.target.value) })}
|
||||
type="range"
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import TimeAgo from 'react-timeago';
|
||||
import { Status } from '../../state/workloads'
|
||||
import { Dispatch } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { RootAction } from '../../state';
|
||||
import { updateStatus } from '../../state/workloads/actions';
|
||||
|
||||
|
||||
const statusMap = {CANCELED: "Cancelled", SUCCESS: "Successful", FAILURE: "Failed"};
|
||||
export interface WorkloadItemStateProps {
|
||||
id: number;
|
||||
complexity: number;
|
||||
@@ -12,6 +16,7 @@ export interface WorkloadItemStateProps {
|
||||
|
||||
export interface WorkloadItemMethodProps {
|
||||
onCancel: () => void;
|
||||
nowHandler: () => number;
|
||||
}
|
||||
|
||||
export interface WorkloadItemProps extends
|
||||
@@ -19,19 +24,21 @@ export interface WorkloadItemProps extends
|
||||
WorkloadItemMethodProps {}
|
||||
|
||||
|
||||
const WorkloadItem: React.SFC<WorkloadItemProps> = (props) => (
|
||||
<div className="WorkloadItem">
|
||||
<div>
|
||||
const WorkloadItem: React.SFC<WorkloadItemProps> = (props) => {
|
||||
// console.log(props.completeDate)
|
||||
|
||||
return <div className="WorkloadItem border p-4 mb-4 d-flex">
|
||||
<div className="primary-details">
|
||||
<h3 className="WorkloadItem-heading">Workload #{props.id}</h3>
|
||||
<span className="WorkloadItem-subHeading">Complexity: {props.complexity}</span>
|
||||
<span className="WorkloadItem-subHeading">Complexity {props.complexity}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="d-flex">
|
||||
{props.status === 'WORKING'
|
||||
? (
|
||||
<>
|
||||
<span><TimeAgo date={props.completeDate} /></span>
|
||||
<span className="my-auto"><TimeAgo date={props.completeDate} now={props.nowHandler} /></span>
|
||||
<button
|
||||
className="WorkloadItem-secondaryButton"
|
||||
className="WorkloadItem-secondaryButton ml-3 my-auto"
|
||||
onClick={props.onCancel}
|
||||
>
|
||||
Cancel
|
||||
@@ -39,16 +46,23 @@ const WorkloadItem: React.SFC<WorkloadItemProps> = (props) => (
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<span className="WorkloadItem-statusText">{props.status.toLowerCase()}</span>
|
||||
<span className="WorkloadItem-statusText my-auto">{statusMap[props.status]}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch<RootAction>) => ({
|
||||
udpateWorkloadStatus: (id: number, status: Status) => dispatch(updateStatus({ id, status })),
|
||||
})
|
||||
|
||||
const WorkloadItemContainer = connect(null, mapDispatchToProps)(WorkloadItem);
|
||||
|
||||
export {
|
||||
WorkloadItem,
|
||||
WorkloadItemContainer
|
||||
};
|
||||
|
||||
export default WorkloadItem;
|
||||
@@ -12,6 +12,7 @@ export interface WorkloadListStateProps {
|
||||
|
||||
export interface WorkloadListDispatchProps {
|
||||
cancelWorkload: (id: number) => void;
|
||||
nowHandler: () => number;
|
||||
}
|
||||
|
||||
export interface WorkloadListProps extends
|
||||
@@ -19,19 +20,19 @@ export interface WorkloadListProps extends
|
||||
WorkloadListDispatchProps {}
|
||||
|
||||
|
||||
const WorkloadList: React.SFC<WorkloadListProps> = ({ workloads, cancelWorkload }) => (
|
||||
const WorkloadList: React.SFC<WorkloadListProps> = ({ workloads, cancelWorkload, nowHandler }) => (
|
||||
!workloads.length
|
||||
? (
|
||||
<span>No workloads to display</span>
|
||||
)
|
||||
: (
|
||||
<ol>
|
||||
<ul className="list-unstyled">
|
||||
{workloads.map((workload) => (
|
||||
<li key={workload.id}>
|
||||
<WorkloadItem {...workload} onCancel={() => cancelWorkload(workload.id)} />
|
||||
<WorkloadItem {...workload} onCancel={() => cancelWorkload(workload.id)} nowHandler={nowHandler} />
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</ul>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -40,7 +41,7 @@ const mapStateToProps = (state: RootState): WorkloadListStateProps => ({
|
||||
workloads: Object.values(state.workloads),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch<RootAction>): WorkloadListDispatchProps => ({
|
||||
const mapDispatchToProps = (dispatch: Dispatch<RootAction>) => ({
|
||||
cancelWorkload: (id: number) => dispatch(cancel({ id })),
|
||||
})
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ import ReactDOM from 'react-dom';
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createEpicMiddleware } from 'redux-observable';
|
||||
import moment from 'moment';
|
||||
// import moment from 'moment';
|
||||
|
||||
import { reducer, epics, RootAction, RootState } from './state';
|
||||
import * as WorkloadActions from './state/workloads/actions';
|
||||
import "bootstrap/dist/css/bootstrap.min.css"
|
||||
import './index.css';
|
||||
import App from './components/App';
|
||||
|
||||
@@ -20,7 +21,7 @@ epicMiddleware.run(epics);
|
||||
|
||||
// demo actions
|
||||
store.dispatch(WorkloadActions.submit({ complexity: 10 }));
|
||||
store.dispatch(WorkloadActions.created({ id: 999, complexity: 10, completeDate: moment().add(10, 'second').toDate(), status: 'WORKING' }));
|
||||
// store.dispatch(WorkloadActions.created({ id: 999, complexity: 10, completeDate: moment().add(10, 'second').toDate(), status: 'WORKING' }));
|
||||
|
||||
|
||||
ReactDOM.render(
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { createAction } from 'typesafe-actions';
|
||||
|
||||
import { Status } from './types';
|
||||
import { SUBMIT, CREATED, CANCEL, UPDATE_STATUS } from './constants';
|
||||
|
||||
import { SUBMIT, CREATED, CANCEL, FAILED, UPDATE_STATUS } from './constants';
|
||||
|
||||
export const submit = createAction(SUBMIT, resolve => (params: { complexity: number }) => resolve({ complexity: params.complexity }));
|
||||
|
||||
export const created = createAction(CREATED, resolve =>
|
||||
(params: { id: number, status: Status, complexity: number, completeDate: Date }) => resolve({
|
||||
id: params.id,
|
||||
status: params.status,
|
||||
completeDate: params.completeDate,
|
||||
complexity: params.complexity,
|
||||
}));
|
||||
export const created = createAction(CREATED, resolve => {
|
||||
return (params: { id: number, status: Status, complexity: number, completeDate: Date }) => {
|
||||
return resolve({
|
||||
id: params.id,
|
||||
status: params.status,
|
||||
completeDate: params.completeDate,
|
||||
complexity: params.complexity,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
export const failed = createAction(FAILED, resolve => (params: { message: string }) => resolve({ message: params.message }));
|
||||
|
||||
export const cancel = createAction(CANCEL, resolve => (params: { id: number }) => resolve({ id: params.id }));
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@ export const SUBMIT = 'workload/SUBMIT';
|
||||
export const CREATED = 'workload/CREATED';
|
||||
export const CANCEL = 'workload/CANCEL';
|
||||
export const UPDATE_STATUS = 'workload/UPDATE_STATUS';
|
||||
export const FAILED = 'workload/FAILED';
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
import { from, timer } from 'rxjs';
|
||||
import { combineEpics, Epic } from 'redux-observable';
|
||||
import { filter, map, tap, ignoreElements } from 'rxjs/operators';
|
||||
import { filter, map, switchMap, delayWhen } from 'rxjs/operators';
|
||||
import { isActionOf } from 'typesafe-actions';
|
||||
|
||||
import { RootAction, RootState } from '../reducer';
|
||||
import * as workloadsActions from './actions';
|
||||
import {service} from "../workloads/services";
|
||||
|
||||
|
||||
type AppEpic = Epic<RootAction, RootAction, RootState>;
|
||||
|
||||
const logWorkloadSubmissions: AppEpic = (action$, state$) => (
|
||||
const createWorkload: AppEpic = (action$, state$) => (
|
||||
action$.pipe(
|
||||
filter(isActionOf(workloadsActions.submit)),
|
||||
map(action => action.payload),
|
||||
tap((payload) => console.log('Workload submitted', payload)),
|
||||
ignoreElements(),
|
||||
switchMap(action => from(service.create(action.payload))),
|
||||
map(res => workloadsActions.created(res)),
|
||||
)
|
||||
);
|
||||
|
||||
const cancelWorkload: AppEpic = (action$, state$) => (
|
||||
action$.pipe(
|
||||
filter(isActionOf(workloadsActions.cancel)),
|
||||
switchMap(action => from(service.cancel(action.payload))),
|
||||
map(res => workloadsActions.updateStatus(res))
|
||||
)
|
||||
);
|
||||
|
||||
const updateWorkload: AppEpic = action$ => (
|
||||
action$.pipe(
|
||||
filter(isActionOf(workloadsActions.created)),
|
||||
delayWhen(action => timer(action.payload.complexity * 1000)),
|
||||
switchMap(action => from(service.checkStatus(action.payload))),
|
||||
map(res => workloadsActions.updateStatus(res))
|
||||
)
|
||||
);
|
||||
|
||||
export const epics = combineEpics(
|
||||
logWorkloadSubmissions,
|
||||
createWorkload,
|
||||
cancelWorkload,
|
||||
updateWorkload
|
||||
);
|
||||
|
||||
export default epics;
|
||||
|
||||
@@ -2,8 +2,7 @@ import moment from 'moment';
|
||||
|
||||
import { Status } from './types';
|
||||
|
||||
|
||||
export class WorkloadService {
|
||||
class WorkloadService {
|
||||
|
||||
private workLoads: { [key in number]: Work } = {};
|
||||
private counter = 0;
|
||||
@@ -65,3 +64,6 @@ interface Work {
|
||||
status: Status;
|
||||
timer: NodeJS.Timeout;
|
||||
}
|
||||
|
||||
const service = new WorkloadService();
|
||||
export {WorkloadService, service}
|
||||
|
||||
Reference in New Issue
Block a user