Skip to content

Commit b97db60

Browse files
committed
chore(feature-runs): add blank page
1 parent 0c4ae29 commit b97db60

File tree

6 files changed

+194
-13
lines changed

6 files changed

+194
-13
lines changed

src/app/components/Card/Card.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from "react";
2+
import { style } from "typestyle";
3+
import { Color, Spacing } from "../../constants";
4+
import { Avatar } from "../Avatar/Avatar";
5+
6+
interface IProps {
7+
description: string;
8+
featureId: string;
9+
hypothesis: string;
10+
}
11+
const styles = {
12+
body: style({
13+
flexGrow: 1,
14+
padding: Spacing.L
15+
}),
16+
container: style({
17+
border: `1px solid ${Color.GREY}`,
18+
display: "flex",
19+
flexDirection: "column",
20+
height: 250,
21+
padding: Spacing.NONE,
22+
width: 250
23+
}),
24+
featureId: style({
25+
marginLeft: Spacing.S
26+
}),
27+
footer: style({
28+
alignItems: "center",
29+
borderTop: `1px solid ${Color.GREY}`,
30+
display: "flex",
31+
paddingBottom: Spacing.M,
32+
paddingLeft: Spacing.L,
33+
paddingTop: Spacing.M
34+
})
35+
};
36+
export const Card: React.FC<IProps> = (props): JSX.Element => {
37+
return (
38+
<div className={styles.container}>
39+
<div className={styles.body}>
40+
{props.description}
41+
<hr/>
42+
{props.hypothesis}
43+
</div>
44+
<div className={styles.footer}>
45+
<Avatar label={props.featureId.split("")[0]}/>
46+
<span className={styles.featureId}>{props.featureId}</span>
47+
</div>
48+
</div>
49+
);
50+
};

src/app/containers/App.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { stylesheet } from "typestyle";
77
import { config as appConfig } from "../../../config";
88
import { setupCss } from "../helpers/setupCss";
99
import { HomePage } from "../pages/HomePage";
10+
import { RunsPage } from "../pages/RunsPage";
1011
import { IStore } from "../redux/IStore";
1112
import { RoutePageMap } from "../routes/routes";
1213
import { Header } from "./Header";
@@ -15,8 +16,9 @@ setupCss();
1516

1617
const classNames = stylesheet({
1718
container: {
18-
margin: 0,
19-
padding: 0
19+
margin: "0 auto",
20+
padding: 0,
21+
width: 1024
2022
}
2123
});
2224

@@ -30,7 +32,7 @@ interface IStateToProps {
3032
class App extends React.Component<IStateToProps> {
3133
private components: RoutePageMap = {
3234
homePage: HomePage,
33-
runsPage: null
35+
runsPage: RunsPage
3436
};
3537
public render(): JSX.Element {
3638
const { route, translations: { notFound } } = this.props;

src/app/containers/EmptyState.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@ const styles = {
88
container: style({
99
display: "flex",
1010
flexDirection: "column",
11-
margin: "0 auto",
1211
marginTop: Spacing.XXXL,
1312
padding: Spacing.L,
14-
paddingLeft: Spacing.XXXL,
15-
width: 960
13+
paddingLeft: Spacing.XXXL
1614
}),
1715
iconContainer: style({
1816
width: 200
@@ -23,6 +21,7 @@ const styles = {
2321
};
2422

2523
interface IProps {
24+
buttonLabel: string;
2625
onActionClick: () => void;
2726
}
2827

@@ -33,9 +32,9 @@ export const EmptyState: React.FC<IProps> = (props) => {
3332
<FeatureIcon/>
3433
</div>
3534
<h1 className={styles.pullDown}>No items yet</h1>
36-
<p>There are no features created yet, why don't we start by creating one?</p>
35+
<p>{props.children}</p>
3736
<div>
38-
<Button onClick={props.onActionClick} type={"primary"}>Create a Feature</Button>
37+
<Button onClick={props.onActionClick} type={"primary"}>{props.buttonLabel}</Button>
3938
</div>
4039
</div>
4140
);

src/app/pages/HomePage.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import autobind from "autobind-decorator";
22
import * as React from "react";
33
import { connect } from "react-redux";
4+
import { ConnectedLink } from "react-router5";
45
import { Dispatch } from "redux";
6+
import { style } from "typestyle";
57
import { ICreateFeatureRequest, IFeature } from "../../Sdk/nodes/Features";
8+
import { Card } from "../components/Card/Card";
69
import { EmptyState } from "../containers/EmptyState";
710
import { ErrorState } from "../containers/ErrorState";
811
import { LoadingState } from "../containers/LoadingState";
912
import { IStore } from "../redux/IStore";
1013
import { createFeatureActionCreators, featureActionCreators } from "../redux/modules/features/featureActionCreators";
14+
import { getRoutes } from "../routes/routes";
1115

1216
interface IStateToProps {
1317
error: string;
@@ -20,6 +24,13 @@ interface IDispatchToProps {
2024
loadFeatures: () => void;
2125
}
2226

27+
const styles = {
28+
container: style({
29+
display: "flex",
30+
flexDirection: "row",
31+
flexWrap: "wrap"
32+
})
33+
};
2334
class HomePage extends React.Component<IStateToProps & IDispatchToProps> {
2435
public componentDidMount(): void {
2536
if (!this.props.loaded) {
@@ -40,13 +51,14 @@ class HomePage extends React.Component<IStateToProps & IDispatchToProps> {
4051
}
4152
if (this.props.features.length === 0) {
4253
return (
43-
<EmptyState onActionClick={this.handleCreateNewFeatureClick}/>
54+
<EmptyState buttonLabel={"Create a Feature"} onActionClick={this.handleCreateNewFeatureClick}>
55+
There are no features created yet, why don't we start by creating one?
56+
</EmptyState>
4457
);
4558
}
46-
// finally return actual list
4759
return (
48-
<div>
49-
Home Page
60+
<div className={styles.container}>
61+
{this.props.features.map(this.renderFeatures)}
5062
</div>
5163
);
5264
}
@@ -59,6 +71,17 @@ class HomePage extends React.Component<IStateToProps & IDispatchToProps> {
5971
const hypothesis = prompt("enter hypothesis", "");
6072
this.props.createNewFeature({ hypothesis, featId, description });
6173
}
74+
75+
// eslint-disable-next-line @typescript-eslint/member-ordering
76+
@autobind
77+
private renderFeatures(feature: IFeature): JSX.Element {
78+
const routes = getRoutes();
79+
return (
80+
<ConnectedLink routeName={routes.runsPage.name} routeParams={{ featId: feature.featId }}>
81+
<Card description={feature.description} featureId={feature.featId} hypothesis={feature.hypothesis}/>
82+
</ConnectedLink>
83+
);
84+
}
6285
}
6386

6487
function mapStateToProps(state: Pick<IStore, "features">): IStateToProps {

src/app/pages/RunsPage.tsx

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import autobind from "autobind-decorator";
2+
import React from "react";
3+
import { connect } from "react-redux";
4+
import { Dispatch } from "redux";
5+
import { createRouteNodeSelector } from "redux-router5";
6+
import { State as IRouterState } from "router5";
7+
import { ICreateFeatureRunRequest, IFeatureRun } from "../../Sdk/nodes/FeatureRuns";
8+
import { EmptyState } from "../containers/EmptyState";
9+
import { ErrorState } from "../containers/ErrorState";
10+
import { LoadingState } from "../containers/LoadingState";
11+
import { IStore } from "../redux/IStore";
12+
import {
13+
createFeatureRunForFeature,
14+
fetchFeatureRunsForFeature
15+
} from "../redux/modules/featureRuns/fetchFeatureRunsForFeature";
16+
17+
interface IStateToProps {
18+
error: string;
19+
featureId: string;
20+
loaded: boolean;
21+
pending: boolean;
22+
runs: IFeatureRun[];
23+
}
24+
25+
interface IDispatchToProps {
26+
createRun: (run: ICreateFeatureRunRequest) => void;
27+
loadRuns: (featId: string) => void;
28+
}
29+
30+
class Page extends React.PureComponent<IStateToProps & IDispatchToProps> {
31+
public componentDidMount(): void {
32+
if (!this.props.loaded) {
33+
this.props.loadRuns(this.props.featureId);
34+
}
35+
}
36+
37+
public render(): JSX.Element {
38+
if (this.props.pending) {
39+
return (
40+
<LoadingState>Pending</LoadingState>
41+
);
42+
}
43+
if (this.props.error) {
44+
return (
45+
<ErrorState>{this.props.error}</ErrorState>
46+
);
47+
}
48+
if (this.props.runs.length === 0) {
49+
return (
50+
<EmptyState buttonLabel={"Create a run"} onActionClick={this.handleCreateNewRunClick}>
51+
No runs currently exists, let's create a run so people can start to see the feature.
52+
</EmptyState>
53+
);
54+
}
55+
// finally return actual list
56+
return (
57+
<div>
58+
Runs Page
59+
</div>
60+
);
61+
}
62+
63+
// eslint-disable-next-line @typescript-eslint/member-ordering
64+
@autobind
65+
private handleCreateNewRunClick(): void {
66+
const allocation = parseInt(prompt("enter allocation", "100"), 10);
67+
if (isNaN(allocation)) {
68+
alert("allocation must be a number");
69+
return;
70+
}
71+
const currentTime = new Date();
72+
const startAt = prompt("start time: (default selected for you in UTC timezone)", currentTime.toISOString());
73+
currentTime.setDate(currentTime.getDate() + 2);
74+
const endAt = prompt("end time: (default selected after 2 days for you in UTC timezone) (this is optional, feel free to remove it)", currentTime.toISOString());
75+
const request: ICreateFeatureRunRequest = {
76+
allocation,
77+
featId: this.props.featureId,
78+
startAt
79+
};
80+
if (endAt.trim() !== "") {
81+
request.endAt = endAt;
82+
}
83+
// todo: find a way to parse date correctly, make sure timezones are handled correctly on backend.
84+
this.props.createRun(request);
85+
}
86+
}
87+
88+
const mapStateToProps = (state: Pick<IStore, "featureRuns">): IStateToProps => {
89+
const route: IRouterState = createRouteNodeSelector("")(state).route;
90+
return {
91+
error: state.featureRuns.error,
92+
featureId: route.params.featId,
93+
loaded: state.featureRuns.loaded,
94+
pending: state.featureRuns.pending,
95+
runs: state.featureRuns.runs
96+
};
97+
};
98+
99+
const mapDispatchToProps = (dispatch: Dispatch): IDispatchToProps => {
100+
return {
101+
createRun: (run: ICreateFeatureRunRequest) => dispatch(createFeatureRunForFeature.invoke(run)),
102+
loadRuns: (featId: string) => dispatch(fetchFeatureRunsForFeature.invoke(featId))
103+
};
104+
};
105+
106+
export const RunsPage = connect(mapStateToProps, mapDispatchToProps)(Page);

src/app/sagas/FeatureRunSaga.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export class FeatureRunSaga extends BaseSaga {
5353
}
5454

5555
protected* registerListeners(): IterableIterator<ForkEffect> {
56-
return yield takeLatest(getType(fetchFeatureRunsForFeature.invoke), this.fetchRuns);
56+
yield takeLatest(getType(fetchFeatureRunsForFeature.invoke), this.fetchRuns);
57+
yield takeLatest(getType(createFeatureRunForFeature.invoke), this.createFeatureRun);
5758
}
5859
}

0 commit comments

Comments
 (0)