Skip to content

Commit

Permalink
chore(feature-runs): add blank page
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberhck committed Jul 29, 2020
1 parent 0c4ae29 commit b97db60
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 13 deletions.
50 changes: 50 additions & 0 deletions src/app/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from "react";
import { style } from "typestyle";
import { Color, Spacing } from "../../constants";
import { Avatar } from "../Avatar/Avatar";

interface IProps {
description: string;
featureId: string;
hypothesis: string;
}
const styles = {
body: style({
flexGrow: 1,
padding: Spacing.L
}),
container: style({
border: `1px solid ${Color.GREY}`,
display: "flex",
flexDirection: "column",
height: 250,
padding: Spacing.NONE,
width: 250
}),
featureId: style({
marginLeft: Spacing.S
}),
footer: style({
alignItems: "center",
borderTop: `1px solid ${Color.GREY}`,
display: "flex",
paddingBottom: Spacing.M,
paddingLeft: Spacing.L,
paddingTop: Spacing.M
})
};
export const Card: React.FC<IProps> = (props): JSX.Element => {
return (
<div className={styles.container}>
<div className={styles.body}>
{props.description}
<hr/>
{props.hypothesis}
</div>
<div className={styles.footer}>
<Avatar label={props.featureId.split("")[0]}/>
<span className={styles.featureId}>{props.featureId}</span>
</div>
</div>
);
};
8 changes: 5 additions & 3 deletions src/app/containers/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { stylesheet } from "typestyle";
import { config as appConfig } from "../../../config";
import { setupCss } from "../helpers/setupCss";
import { HomePage } from "../pages/HomePage";
import { RunsPage } from "../pages/RunsPage";
import { IStore } from "../redux/IStore";
import { RoutePageMap } from "../routes/routes";
import { Header } from "./Header";
Expand All @@ -15,8 +16,9 @@ setupCss();

const classNames = stylesheet({
container: {
margin: 0,
padding: 0
margin: "0 auto",
padding: 0,
width: 1024
}
});

Expand All @@ -30,7 +32,7 @@ interface IStateToProps {
class App extends React.Component<IStateToProps> {
private components: RoutePageMap = {
homePage: HomePage,
runsPage: null
runsPage: RunsPage
};
public render(): JSX.Element {
const { route, translations: { notFound } } = this.props;
Expand Down
9 changes: 4 additions & 5 deletions src/app/containers/EmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ const styles = {
container: style({
display: "flex",
flexDirection: "column",
margin: "0 auto",
marginTop: Spacing.XXXL,
padding: Spacing.L,
paddingLeft: Spacing.XXXL,
width: 960
paddingLeft: Spacing.XXXL
}),
iconContainer: style({
width: 200
Expand All @@ -23,6 +21,7 @@ const styles = {
};

interface IProps {
buttonLabel: string;
onActionClick: () => void;
}

Expand All @@ -33,9 +32,9 @@ export const EmptyState: React.FC<IProps> = (props) => {
<FeatureIcon/>
</div>
<h1 className={styles.pullDown}>No items yet</h1>
<p>There are no features created yet, why don't we start by creating one?</p>
<p>{props.children}</p>
<div>
<Button onClick={props.onActionClick} type={"primary"}>Create a Feature</Button>
<Button onClick={props.onActionClick} type={"primary"}>{props.buttonLabel}</Button>
</div>
</div>
);
Expand Down
31 changes: 27 additions & 4 deletions src/app/pages/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import autobind from "autobind-decorator";
import * as React from "react";
import { connect } from "react-redux";
import { ConnectedLink } from "react-router5";
import { Dispatch } from "redux";
import { style } from "typestyle";
import { ICreateFeatureRequest, IFeature } from "../../Sdk/nodes/Features";
import { Card } from "../components/Card/Card";
import { EmptyState } from "../containers/EmptyState";
import { ErrorState } from "../containers/ErrorState";
import { LoadingState } from "../containers/LoadingState";
import { IStore } from "../redux/IStore";
import { createFeatureActionCreators, featureActionCreators } from "../redux/modules/features/featureActionCreators";
import { getRoutes } from "../routes/routes";

interface IStateToProps {
error: string;
Expand All @@ -20,6 +24,13 @@ interface IDispatchToProps {
loadFeatures: () => void;
}

const styles = {
container: style({
display: "flex",
flexDirection: "row",
flexWrap: "wrap"
})
};
class HomePage extends React.Component<IStateToProps & IDispatchToProps> {
public componentDidMount(): void {
if (!this.props.loaded) {
Expand All @@ -40,13 +51,14 @@ class HomePage extends React.Component<IStateToProps & IDispatchToProps> {
}
if (this.props.features.length === 0) {
return (
<EmptyState onActionClick={this.handleCreateNewFeatureClick}/>
<EmptyState buttonLabel={"Create a Feature"} onActionClick={this.handleCreateNewFeatureClick}>
There are no features created yet, why don't we start by creating one?
</EmptyState>
);
}
// finally return actual list
return (
<div>
Home Page
<div className={styles.container}>
{this.props.features.map(this.renderFeatures)}
</div>
);
}
Expand All @@ -59,6 +71,17 @@ class HomePage extends React.Component<IStateToProps & IDispatchToProps> {
const hypothesis = prompt("enter hypothesis", "");
this.props.createNewFeature({ hypothesis, featId, description });
}

// eslint-disable-next-line @typescript-eslint/member-ordering
@autobind
private renderFeatures(feature: IFeature): JSX.Element {
const routes = getRoutes();
return (
<ConnectedLink routeName={routes.runsPage.name} routeParams={{ featId: feature.featId }}>
<Card description={feature.description} featureId={feature.featId} hypothesis={feature.hypothesis}/>
</ConnectedLink>
);
}
}

function mapStateToProps(state: Pick<IStore, "features">): IStateToProps {
Expand Down
106 changes: 106 additions & 0 deletions src/app/pages/RunsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import autobind from "autobind-decorator";
import React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { createRouteNodeSelector } from "redux-router5";
import { State as IRouterState } from "router5";
import { ICreateFeatureRunRequest, IFeatureRun } from "../../Sdk/nodes/FeatureRuns";
import { EmptyState } from "../containers/EmptyState";
import { ErrorState } from "../containers/ErrorState";
import { LoadingState } from "../containers/LoadingState";
import { IStore } from "../redux/IStore";
import {
createFeatureRunForFeature,
fetchFeatureRunsForFeature
} from "../redux/modules/featureRuns/fetchFeatureRunsForFeature";

interface IStateToProps {
error: string;
featureId: string;
loaded: boolean;
pending: boolean;
runs: IFeatureRun[];
}

interface IDispatchToProps {
createRun: (run: ICreateFeatureRunRequest) => void;
loadRuns: (featId: string) => void;
}

class Page extends React.PureComponent<IStateToProps & IDispatchToProps> {
public componentDidMount(): void {
if (!this.props.loaded) {
this.props.loadRuns(this.props.featureId);
}
}

public render(): JSX.Element {
if (this.props.pending) {
return (
<LoadingState>Pending</LoadingState>
);
}
if (this.props.error) {
return (
<ErrorState>{this.props.error}</ErrorState>
);
}
if (this.props.runs.length === 0) {
return (
<EmptyState buttonLabel={"Create a run"} onActionClick={this.handleCreateNewRunClick}>
No runs currently exists, let's create a run so people can start to see the feature.
</EmptyState>
);
}
// finally return actual list
return (
<div>
Runs Page
</div>
);
}

// eslint-disable-next-line @typescript-eslint/member-ordering
@autobind
private handleCreateNewRunClick(): void {
const allocation = parseInt(prompt("enter allocation", "100"), 10);
if (isNaN(allocation)) {
alert("allocation must be a number");
return;
}
const currentTime = new Date();
const startAt = prompt("start time: (default selected for you in UTC timezone)", currentTime.toISOString());
currentTime.setDate(currentTime.getDate() + 2);
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());
const request: ICreateFeatureRunRequest = {
allocation,
featId: this.props.featureId,
startAt
};
if (endAt.trim() !== "") {
request.endAt = endAt;
}
// todo: find a way to parse date correctly, make sure timezones are handled correctly on backend.
this.props.createRun(request);
}
}

const mapStateToProps = (state: Pick<IStore, "featureRuns">): IStateToProps => {
const route: IRouterState = createRouteNodeSelector("")(state).route;
return {
error: state.featureRuns.error,
featureId: route.params.featId,
loaded: state.featureRuns.loaded,
pending: state.featureRuns.pending,
runs: state.featureRuns.runs
};
};

const mapDispatchToProps = (dispatch: Dispatch): IDispatchToProps => {
return {
createRun: (run: ICreateFeatureRunRequest) => dispatch(createFeatureRunForFeature.invoke(run)),
loadRuns: (featId: string) => dispatch(fetchFeatureRunsForFeature.invoke(featId))
};
};

export const RunsPage = connect(mapStateToProps, mapDispatchToProps)(Page);
3 changes: 2 additions & 1 deletion src/app/sagas/FeatureRunSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class FeatureRunSaga extends BaseSaga {
}

protected* registerListeners(): IterableIterator<ForkEffect> {
return yield takeLatest(getType(fetchFeatureRunsForFeature.invoke), this.fetchRuns);
yield takeLatest(getType(fetchFeatureRunsForFeature.invoke), this.fetchRuns);
yield takeLatest(getType(createFeatureRunForFeature.invoke), this.createFeatureRun);
}
}

0 comments on commit b97db60

Please sign in to comment.