Skip to content

Commit

Permalink
feat(dashboard): update UI (closes #460)
Browse files Browse the repository at this point in the history
* add task type filter
* animate tasks that are in process
* improve text rendering inside task graph node
* replace spinner
  • Loading branch information
eysi09 committed Jan 25, 2019
1 parent e0970f0 commit e59897c
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 114 deletions.
14 changes: 14 additions & 0 deletions dashboard/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ import "flexboxgrid/dist/flexboxgrid.min.css"
import "./styles/padding-margin-mixin.scss"
import { EventProvider } from "./context/events"
import { DataProvider } from "./context/data"
import { NavLink } from "./components/links"

import logo from "./assets/logo.png"

// Style and align properly
const Logo = styled.img`
height: auto;
width: 80%;
`

const SidebarWrapper = styled.div`
border-right: 1px solid ${colors.border};
Expand All @@ -44,6 +53,11 @@ const App = () => (
overflow-y: hidden;
`}>
<SidebarWrapper>
<div className={"ml-1"}>
<NavLink to="/">
<Logo src={logo} alt="Home" />
</NavLink>
</div>
<Sidebar />
</SidebarWrapper>
<div className={css`
Expand Down
25 changes: 25 additions & 0 deletions dashboard/src/components/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2018 Garden Technologies, Inc. <[email protected]>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import React, { ChangeEvent } from "react"

interface Props {
name: string
checked?: boolean
onChange: (event: ChangeEvent<HTMLInputElement>) => void
}

const CheckBox: React.SFC<Props> = ({ name, onChange, checked = false }) => {
return (
<label>
<input type={"checkbox"} name={name} checked={checked} onChange={onChange} />
</label>
)
}

export default CheckBox
20 changes: 20 additions & 0 deletions dashboard/src/components/graph/graph.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
g.taskPending > rect {
stroke: #f17fcd;
stroke-dasharray: 8;
animation: dash 15s linear infinite;
}

@keyframes dash {
to {
stroke-dashoffset: 1000;
}
}

g.taskComplete > rect {
Expand All @@ -27,4 +34,17 @@
stroke: rgba(0,0,0,0.32);
stroke-width: 1.5px;
}

div.label-wrap {
text-align: center;

span.name {
font-weight: bold;
}
span.type {
display: inline-block;
margin-top: 0.5em
}
}

}
102 changes: 77 additions & 25 deletions dashboard/src/components/graph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

import cls from "classnames"
import { css } from "emotion/macro"
import React, { Component } from "react"
import React, { Component, ChangeEvent } from "react"
import styled from "@emotion/styled/macro"
import { capitalize, uniq } from "lodash"
import * as d3 from "d3"
import dagreD3 from "dagre-d3"

Expand Down Expand Up @@ -59,6 +60,7 @@ function drawChart(graph: Graph, width: number, height: number) {
label: node.label,
class: "",
id: node.id,
labelType: "html",
})
}

Expand Down Expand Up @@ -129,6 +131,7 @@ interface Props {
}

interface State {
filters: { [key: string]: boolean }
nodes: Node[]
edges: Edge[]
}
Expand All @@ -146,13 +149,15 @@ const getIdFromTaskKey = (key: string) => {
return makeId(name, type)
}

// Renders as HTML
const makeLabel = (name: string, type: string) => {
const nameParts = name.split(".")
// test names look like: name.test-name.type
if (type === "test") {
type += ` (${nameParts[1]})`
}
return `${nameParts[0]}\n${type}`
return "<div class='label-wrap'><span class='name'>" +
nameParts[0] + "</span><br /><span class='type'>" + type + "</span></div>"
}

const Span = styled.span`
Expand All @@ -175,17 +180,34 @@ class Chart extends Component<Props, State> {
_edges: Edge[]
_chartRef: React.RefObject<any>

state = {
nodes: [],
edges: [],
filters: {},
}

constructor(props) {
super(props)

this._chartRef = React.createRef()
this.onCheckboxChange = this.onCheckboxChange.bind(this)

const taskTypes = uniq(this.props.graph.nodes.map(n => n.type))
const filters = taskTypes.reduce((acc, type) => {
acc[type] = false
return acc
}, {})
this.state = {
...this.state,
filters,
}
}

componentDidMount() {
this.drawChart()

// Re-draw graph on **end** of window resize event (hence the timer)
let resizeTimer
let resizeTimer: NodeJS.Timeout
window.onresize = () => {
clearTimeout(resizeTimer)
resizeTimer = setTimeout(() => {
Expand All @@ -194,6 +216,12 @@ class Chart extends Component<Props, State> {
}
}

onCheckboxChange({ target }: ChangeEvent<HTMLInputElement>) {
this.setState({
filters: { ...this.state.filters, [target.name]: !target.checked },
})
}

drawChart() {
const graph = this.makeGraph()
this._nodes = graph.nodes
Expand All @@ -204,30 +232,38 @@ class Chart extends Component<Props, State> {
}

makeGraph() {
const nodes: Node[] = this.props.graph.nodes.map(n => {
return {
id: makeId(n.name, n.type),
name: n.name,
label: makeLabel(n.name, n.type),
}
})
const edges: Edge[] = this.props.graph.relationships.map(r => {
const source = r.dependency
const target = r.dependant
return {
source: makeId(source.name, source.type),
target: makeId(target.name, target.type),
type: source.type,
}
})
const { filters } = this.state
const nodes: Node[] = this.props.graph.nodes
.filter(n => !filters[n.type])
.map(n => {
return {
id: makeId(n.name, n.type),
name: n.name,
label: makeLabel(n.name, n.type),
}
})
const edges: Edge[] = this.props.graph.relationships
.filter(n => !filters[n.dependant.type] && !filters[n.dependency.type])
.map(r => {
const source = r.dependency
const target = r.dependant
return {
source: makeId(source.name, source.type),
target: makeId(target.name, target.type),
type: source.type,
}
})
return { edges, nodes }
}

componentDidUpdate(_prevProps: Props) {
const { message } = this.props
componentDidUpdate(_prevProps, prevState: State) {
const message = this.props.message
if (message && message.type === "event") {
this.updateNodeClass(message)
}
if (prevState.filters !== this.state.filters) {
this.drawChart()
}
}

clearClasses(el: HTMLElement) {
Expand Down Expand Up @@ -256,13 +292,16 @@ class Chart extends Component<Props, State> {

render() {
const { message } = this.props
const taskTypes = uniq(this.props.graph.nodes.map(n => n.type))
const chartHeightEstimate = `100vh - 15rem`

let spinner = null
let status = "Ready"
if (message && message.name !== "taskGraphComplete") {
status = "Processing..."
spinner = <ProcessSpinner />
}

return (
<Card>
<div>
Expand All @@ -272,14 +311,27 @@ class Chart extends Component<Props, State> {
<Span><span className={css`color: ${colors.gardenPink};`}>-- </span>Pending</Span>
<Span><span className={css`color: red;`}></span>Error</Span>
</p>
<div>
{taskTypes.map(type => (
<label className="ml-1" key={type}>
{capitalize(type)}
<input
type={"checkbox"}
name={type}
checked={!this.state.filters[type]}
onChange={this.onCheckboxChange}
/>
</label>
))}
</div>
</div>
<div className={css`
height: calc(${chartHeightEstimate});
`} ref={this._chartRef} id="chart">
height: calc(${chartHeightEstimate});
`} ref={this._chartRef} id="chart">
</div>
<div className={cls(css`
display: flex;
`, "ml-1 pb-1")}>
display: flex;
`, "ml-1 pb-1")}>
<Status>{status}</Status>
{spinner}
</div>
Expand Down
12 changes: 0 additions & 12 deletions dashboard/src/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { NavLink } from "./links"
import { Page } from "../containers/sidebar"

import { colors, fontMedium } from "../styles/variables"
import logo from "../assets/logo.png"

interface Props {
pages: Page[]
Expand Down Expand Up @@ -48,12 +47,6 @@ const linkStyle = `
const A = styled.a(linkStyle)
const Link = styled(NavLink)(linkStyle)

// Style and align properly
const Logo = styled.img`
height: auto;
width: 80%;
`

class Sidebar extends Component<Props, State> {

constructor(props) {
Expand All @@ -68,11 +61,6 @@ class Sidebar extends Component<Props, State> {
render() {
return (
<div className="pb-1">
<div className={"ml-1"}>
<NavLink to="/">
<Logo src={logo} alt="Home" />
</NavLink>
</div>
<nav>
<ul className="pt-2">
{this.props.pages.map(page => {
Expand Down
Loading

0 comments on commit e59897c

Please sign in to comment.