console.log('start the machine')} />
+
))
diff --git a/app/stories/Navigation.js b/app/stories/Navigation.js
index d95d473..4c37765 100644
--- a/app/stories/Navigation.js
+++ b/app/stories/Navigation.js
@@ -5,5 +5,5 @@ import Navigation from '../components/Navigation'
storiesOf('app.Navigation', module)
.add('default', () => (
-
+
))
diff --git a/app/styles/layout.js b/app/styles/Layout.js
similarity index 100%
rename from app/styles/layout.js
rename to app/styles/Layout.js
diff --git a/app/styles/nav.js b/app/styles/nav.js
deleted file mode 100644
index 1bf3c0c..0000000
--- a/app/styles/nav.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export default {
- container: (props) => {
- return {
- display: 'flex',
- justifyContent: 'space-around'
- }
- }
-}
diff --git a/db/migrations/20170713153018_add-tasks.js b/db/migrations/20170713153018_add-tasks.js
new file mode 100644
index 0000000..70e7500
--- /dev/null
+++ b/db/migrations/20170713153018_add-tasks.js
@@ -0,0 +1,21 @@
+exports.up = function (knex, Promise) {
+ return Promise.all([
+ knex.schema.createTableIfNotExists('taskPlans', function (table) {
+ table.increments('id')
+ table.string('taskRecipeId').notNullable()
+ table.string('assignee').notNullable()
+ }),
+ knex.schema.createTableIfNotExists('taskWorks', function (table) {
+ table.increments('id')
+ table.integer('taskPlanId').references('taskPlans.id').notNullable()
+ table.integer('workerAgentId').references('agents.id').notNullable()
+ })
+ ])
+}
+
+exports.down = function (knex, Promise) {
+ return Promise.all([
+ knex.schema.dropTableIfExists('taskPlans'),
+ knex.schema.dropTableIfExists('taskWorks')
+ ])
+}
diff --git a/db/seeds/dev.js b/db/seeds/dev.js
new file mode 100644
index 0000000..a332372
--- /dev/null
+++ b/db/seeds/dev.js
@@ -0,0 +1,46 @@
+const hasher = require('feathers-authentication-local/lib/utils/hash')
+
+exports.seed = function (knex, Promise) {
+ // delete ALL existing entries
+ return Promise.all([
+ knex('agents').del(),
+ knex('credentials').del(),
+ knex('profiles').del()
+ ])
+ .then(() => {
+ // insert person agent
+ const devPersonAgent = {}
+ return knex('agents').insert(devPersonAgent).returning('*')
+ })
+ .then(([agentId]) => {
+ // insert person profile
+ const devPersonProfile = {
+ agentId,
+ name: 'Alice',
+ description: 'Hey, This is only a test.',
+ avatar: 'https://i.imgur.com/9p2dC14.gif' // source: https://imgur.com/gallery/OlVVE
+ }
+ return Promise.all([
+ Promise.resolve(agentId),
+ knex('profiles').insert(devPersonProfile)
+ ])
+ })
+ .then(([agentId]) => {
+ // insert person credential
+ const devPersonCredential = {
+ agentId,
+ email: 'test@test.nz',
+ password: 'password'
+ }
+ return hashCredential(devPersonCredential)
+ }).then(credential => {
+ return knex('credentials').insert(credential)
+ })
+}
+
+function hashCredential (credential) {
+ return hasher(credential.password)
+ .then(hashedPassword => {
+ return Object.assign(credential, { password: hashedPassword })
+ })
+}
diff --git a/epic.js b/epic.js
index 36b1392..a3ac308 100644
--- a/epic.js
+++ b/epic.js
@@ -1,7 +1,13 @@
import { combineEpics } from 'redux-observable'
import { epic as agents } from 'dogstack-agents'
+import ordering from './ordering/epic'
+import { epic as taskPlans } from './tasks/dux/plans'
+import { epic as taskWorks } from './tasks/dux/works'
export default combineEpics(
- agents
+ agents,
+ ordering,
+ taskPlans,
+ taskWorks
)
diff --git a/layout.js b/layout.js
index bfd10b9..bfc60ed 100644
--- a/layout.js
+++ b/layout.js
@@ -1,2 +1,6 @@
-import Layout from './app/containers/layout'
+// Needed for onTouchTap
+import injectTapEventPlugin from 'react-tap-event-plugin'
+injectTapEventPlugin()
+
+import Layout from './app/containers/Layout'
export default Layout
diff --git a/ordering/actions.js b/ordering/actions.js
new file mode 100644
index 0000000..f294de9
--- /dev/null
+++ b/ordering/actions.js
@@ -0,0 +1,6 @@
+import createAction from '@f/create-action'
+
+const payloadCtor = (cid, payload) => payload
+const metaCtor = (cid) => ({ cid })
+
+export const startOrder = createAction('ORDERING_START', payloadCtor, metaCtor)
diff --git a/ordering/epic.js b/ordering/epic.js
new file mode 100644
index 0000000..c57bd58
--- /dev/null
+++ b/ordering/epic.js
@@ -0,0 +1,15 @@
+import { combineEpics } from 'redux-observable'
+import Rx from 'rxjs'
+
+import { startOrder } from './actions'
+
+export default combineEpics(startOrderEpic)
+
+export function startOrderEpic (action$, store, { feathers }) {
+ return action$.ofType(startOrder.type)
+ .switchMap(({ payload, meta: { cid }}) => {
+ return Rx.Observable.concat(
+ Rx.Observable.of({ type: 'TEST_TEST_TEST' })
+ )
+ })
+}
diff --git a/package-lock.json b/package-lock.json
index cd03ba5..3a1b397 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1035,6 +1035,13 @@
"lodash": "4.17.4",
"source-map": "0.5.6",
"trim-right": "1.0.1"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+ "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s="
+ }
}
},
"babel-helper-bindify-decorators": {
@@ -1279,8 +1286,7 @@
"babel-plugin-syntax-class-properties": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
- "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=",
- "dev": true
+ "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94="
},
"babel-plugin-syntax-decorators": {
"version": "6.13.0",
@@ -1330,8 +1336,7 @@
"babel-plugin-syntax-object-rest-spread": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
- "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=",
- "dev": true
+ "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U="
},
"babel-plugin-syntax-trailing-function-commas": {
"version": "6.22.0",
@@ -1374,7 +1379,6 @@
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz",
"integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=",
- "dev": true,
"requires": {
"babel-helper-function-name": "6.24.1",
"babel-plugin-syntax-class-properties": "6.13.0",
@@ -1660,7 +1664,6 @@
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz",
"integrity": "sha1-h11ryb52HFiirj/u5dxIldjH+SE=",
- "dev": true,
"requires": {
"babel-plugin-syntax-object-rest-spread": "6.13.0",
"babel-runtime": "6.23.0"
@@ -4332,9 +4335,9 @@
}
},
"dogstack-agents": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/dogstack-agents/-/dogstack-agents-0.2.2.tgz",
- "integrity": "sha512-HxKYRGHdnKFBCe3TQRo5i0rQR0W9eJoIZJp5NBw6Pz47W1DzQMP/9d+TjOnt2/6MCjcgBj39e0geLtgucNHVtw==",
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/dogstack-agents/-/dogstack-agents-0.3.3.tgz",
+ "integrity": "sha512-m68oLPiq8uJVKwM+KCVrAOjM1VqJ4bID7NDmWYhrj2IG2Fpw6DAR6zZY8WafSkeAhzuCrs0+oUfyoikIhtNZaA==",
"requires": {
"@f/create-action": "1.1.1",
"babel-plugin-ramda": "1.2.0",
@@ -7056,9 +7059,9 @@
"optional": true
},
"jsesc": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
- "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s="
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
},
"json-loader": {
"version": "0.5.4",
@@ -8752,9 +8755,9 @@
"dev": true
},
"pg": {
- "version": "6.4.0",
- "resolved": "https://registry.npmjs.org/pg/-/pg-6.4.0.tgz",
- "integrity": "sha1-y3a6Lnwuq4n8ZL96n+ZIztckNtw=",
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-6.4.1.tgz",
+ "integrity": "sha1-PqvYygVoFEN8dp8X/3oMNqxwI8U=",
"requires": {
"buffer-writer": "1.0.1",
"packet-reader": "0.3.1",
@@ -10605,6 +10608,11 @@
"prop-types": "15.5.10"
}
},
+ "react-hyperscript": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/react-hyperscript/-/react-hyperscript-3.0.0.tgz",
+ "integrity": "sha1-PBYBCzMXXea8Af0eutChapptyas="
+ },
"react-intl": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.3.0.tgz",
@@ -11085,13 +11093,6 @@
"integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
"requires": {
"jsesc": "0.5.0"
- },
- "dependencies": {
- "jsesc": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
- "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
- }
}
},
"release-zalgo": {
diff --git a/package.json b/package.json
index 1239f4d..e9c3ac4 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,8 @@
"react"
],
"plugins": [
+ "transform-object-rest-spread",
+ "transform-class-properties",
"ramda"
]
},
@@ -51,11 +53,17 @@
"homepage": "https://github.com/enspiral-root-systems/cobuy#readme",
"dependencies": {
"@ahdinosaur/react-fela": "^4.3.5",
+ "@f/create-action": "^1.1.1",
"babel-plugin-ramda": "^1.2.0",
+ "babel-plugin-transform-class-properties": "^6.24.1",
+ "babel-plugin-transform-object-rest-spread": "^6.23.0",
+ "babel-preset-es2015": "^6.9.0",
+ "babel-preset-react": "^6.5.0",
+ "babelify": "^7.3.0",
"bigmath": "^1.0.3",
"dog-names": "^1.0.2",
"dogstack": "^0.4.0",
- "dogstack-agents": "^0.2.1",
+ "dogstack-agents": "^0.3.3",
"feathers-action": "^2.2.0",
"feathers-action-react": "github:ahdinosaur/feathers-action-react#cancel",
"feathers-errors": "^2.6.2",
@@ -71,6 +79,7 @@
"react-debounce-input": "^3.0.0",
"react-dom": "^15.6.1",
"react-fela": "^5.0.3",
+ "react-hyperscript": "^3.0.0",
"react-intl": "^2.3.0",
"react-redux": "^4.4.5",
"react-router-dom": "^4.1.1",
@@ -89,9 +98,6 @@
"@storybook/addon-actions": "^3.1.8",
"@storybook/addon-links": "^3.1.6",
"@storybook/react": "^3.1.3",
- "babel-preset-es2015": "^6.9.0",
- "babel-preset-react": "^6.5.0",
- "babelify": "^7.3.0",
"deep-freeze": "^0.0.1",
"dependency-check": "^2.8.0",
"precinct": "^3.6.0",
diff --git a/routes.js b/routes.js
index 95d3180..43b4c87 100644
--- a/routes.js
+++ b/routes.js
@@ -3,6 +3,7 @@ import React from 'react'
// Top Level Containers
import Home from './app/containers/home'
+import Dashboard from './app/containers/Dashboard'
import Register from './agents/containers/Register'
import SignIn from './agents/containers/SignIn'
@@ -27,8 +28,21 @@ export default [
path: '/',
exact: true,
Component: Home,
+ selector: getIsNotAuthenticated,
navigation: {
- title: 'Home'
+ title: 'app.home',
+ icon: 'fa fa-home'
+ }
+ },
+ {
+ name: 'dashboard',
+ path: '/',
+ exact: true,
+ Component: Dashboard,
+ selector: getIsAuthenticated,
+ navigation: {
+ title: 'app.dashboard',
+ icon: 'fa fa-dashboard'
}
},
{
@@ -36,15 +50,17 @@ export default [
path: '/sign-in',
Component: UserIsNotAuthenticated(SignIn),
navigation: {
- title: 'Sign in',
- selector: getIsNotAuthenticated
+ title: 'agents.signIn',
+ selector: getIsNotAuthenticated,
+ icon: 'fa fa-sign-in'
}
},
{
name: 'logOut',
navigation: {
- Link: LogOut,
- selector: getIsAuthenticated
+ Component: LogOut,
+ selector: getIsAuthenticated,
+ icon: 'fa fa-sign-out'
}
},
{
@@ -52,8 +68,9 @@ export default [
path: '/register',
Component: UserIsNotAuthenticated(Register),
navigation: {
- title: 'Register',
- selector: getIsNotAuthenticated
+ title: 'agents.register',
+ selector: getIsNotAuthenticated,
+ icon: 'fa fa-heart'
}
}
]
diff --git a/server.js b/server.js
index fc7ebf9..1e9de2f 100644
--- a/server.js
+++ b/server.js
@@ -1,5 +1,7 @@
const services = [
- require('dogstack-agents/service')
+ require('dogstack-agents/service'),
+ require('./tasks/services/plans'),
+ require('./tasks/services/works')
]
export default {
diff --git a/tasks/components/TaskWorker.js b/tasks/components/TaskWorker.js
index feab733..5d79f73 100644
--- a/tasks/components/TaskWorker.js
+++ b/tasks/components/TaskWorker.js
@@ -11,10 +11,10 @@ import { FormattedMessage } from '../../lib/Intl'
import styles from '../styles/TaskWorker'
import TaskWorkerTree from './TaskWorkerTree'
-const getSubTaskPlans = propOr([], 'subTaskPlans')
+const getSubTaskPlans = propOr([], 'childTaskPlans')
const getIsTaskComplete = pipe(prop('taskWork'), Boolean)
const getAreAllSubTasksComplete = pipe(getSubTaskPlans, all(getIsTaskComplete))
-const getHasSubTasks = pipe(getSubTaskPlans, subTaskPlans => gt(length(subTaskPlans), 0))
+const getHasSubTasks = pipe(getSubTaskPlans, childTaskPlans => gt(length(childTaskPlans), 0))
const getIsTaskReadyToComplete = either(complement(getHasSubTasks), getAreAllSubTasksComplete)
const getTaskComponent = path(['taskRecipe', 'Component'])
@@ -22,7 +22,7 @@ function TaskWorker (props) {
const { styles, taskPlan, onNavigate, onComplete, onCancel } = props
const { taskRecipe } = taskPlan
const { id: taskRecipeId } = taskRecipe
- const subTaskPlans = getSubTaskPlans(taskPlan)
+ const childTaskPlans = getSubTaskPlans(taskPlan)
const hasSubTasks = getHasSubTasks(taskPlan)
const isTaskComplete = getIsTaskComplete(taskPlan)
@@ -77,7 +77,7 @@ function TaskWorker (props) {
{
- const { taskRecipe: subTaskRecipe, taskWork: subTaskWork } = subTaskPlan
- const { id: subTaskRecipeId } = subTaskRecipe
+ const mapSubTasks = map(childTaskPlan => {
+ const { taskRecipe: childTaskRecipe, taskWork: childTaskWork } = childTaskPlan
+ const { id: childTaskRecipeId } = childTaskRecipe
return (
-
+
}
- checked={not(isNil(subTaskWork))}
- className={styles.subTaskCheckbox}
- onCheck={handleNavigate(subTaskPlan)}
+ checked={not(isNil(childTaskWork))}
+ className={styles.childTaskCheckbox}
+ onCheck={handleNavigate(childTaskPlan)}
/>
)
})
return (
-
-
+
+
- {mapSubTasks(subTaskPlans)}
+ {mapSubTasks(childTaskPlans)}
)
- function handleNavigate (subTaskPlan) {
+ function handleNavigate (childTaskPlan) {
return (ev) => {
- onNavigate(subTaskPlan)
+ onNavigate(childTaskPlan)
}
}
}
diff --git a/tasks/data/recipes.js b/tasks/data/recipes.js
index eaac5a6..1c9cc45 100644
--- a/tasks/data/recipes.js
+++ b/tasks/data/recipes.js
@@ -13,7 +13,7 @@ export const setupSupplier = {
export const finishPrereqs = {
id: 'finishPrereqs',
- taskRecipes: [
+ childTaskRecipes: [
setupGroup,
setupSupplier
]
diff --git a/tasks/dux/plans.js b/tasks/dux/plans.js
new file mode 100644
index 0000000..844ae5e
--- /dev/null
+++ b/tasks/dux/plans.js
@@ -0,0 +1,7 @@
+import createModule from 'feathers-action'
+
+const module = createModule('taskPlans')
+
+export const actions = module.actions
+export const updater = module.updater
+export const epic = module.epic
diff --git a/tasks/dux/works.js b/tasks/dux/works.js
new file mode 100644
index 0000000..484b7ce
--- /dev/null
+++ b/tasks/dux/works.js
@@ -0,0 +1,7 @@
+import createModule from 'feathers-action'
+
+const module = createModule('taskWorks')
+
+export const actions = module.actions
+export const updater = module.updater
+export const epic = module.epic
diff --git a/tasks/services/plans.js b/tasks/services/plans.js
new file mode 100644
index 0000000..e2946df
--- /dev/null
+++ b/tasks/services/plans.js
@@ -0,0 +1,18 @@
+const feathersKnex = require('feathers-knex')
+
+module.exports = function () {
+ const app = this
+ const db = app.get('db')
+
+ const name = 'taskPlans'
+ const options = { Model: db, name }
+
+ app.use(name, feathersKnex(options))
+ app.service(name).hooks(hooks)
+}
+
+const hooks = {
+ before: {},
+ after: {},
+ error: {}
+}
diff --git a/tasks/services/works.js b/tasks/services/works.js
new file mode 100644
index 0000000..12b0e0a
--- /dev/null
+++ b/tasks/services/works.js
@@ -0,0 +1,18 @@
+const feathersKnex = require('feathers-knex')
+
+module.exports = function () {
+ const app = this
+ const db = app.get('db')
+
+ const name = 'taskWorks'
+ const options = { Model: db, name }
+
+ app.use(name, feathersKnex(options))
+ app.service(name).hooks(hooks)
+}
+
+const hooks = {
+ before: {},
+ after: {},
+ error: {}
+}
diff --git a/tasks/stories/TaskWorker.js b/tasks/stories/TaskWorker.js
index 4db9888..8752635 100644
--- a/tasks/stories/TaskWorker.js
+++ b/tasks/stories/TaskWorker.js
@@ -18,7 +18,7 @@ const taskPlan = {
id: 1,
agent,
taskRecipe: finishPrereqs,
- subTaskPlans: [
+ childTaskPlans: [
{
id: 2,
agent,
@@ -47,7 +47,7 @@ storiesOf('tasks.TaskWorker', module)
))
.add('leaf task, not complete', () => (
(
{
+ return createTaskPlan(api, {
+ assignee,
+ taskRecipe: childTaskRecipe
+ })
+ })
+
+ return Promise.all([
+ parentTaskPlan,
+ ...childTaskPlans
+ ]).then(unnest)
+}
diff --git a/tasks/util/createTaskPlan.test.js b/tasks/util/createTaskPlan.test.js
new file mode 100644
index 0000000..3267ade
--- /dev/null
+++ b/tasks/util/createTaskPlan.test.js
@@ -0,0 +1,59 @@
+import test from 'ava'
+
+import createTaskPlan from './createTaskPlan'
+import * as taskRecipes from '../data/recipes'
+
+test('create simple task', (t) => {
+ t.plan(3)
+
+ const assignee = Symbol('assignee')
+ const taskRecipe = taskRecipes.setupGroup
+ const expected = { assignee, taskRecipeId: taskRecipe.id }
+
+ const api = {
+ service: () => {
+ return {
+ create: (data, params) => {
+ t.deepEqual(data, expected)
+ return data
+ }
+ }
+ }
+ }
+ const options = { assignee, taskRecipe }
+ return createTaskPlan(api, options)
+ .then(taskPlans => {
+ t.is(taskPlans.length, 1)
+ t.deepEqual(taskPlans[0], expected)
+ })
+})
+
+test('create nested task', (t) => {
+ t.plan(5)
+
+ const assignee = Symbol('assignee')
+ const taskRecipe = taskRecipes.finishPrereqs
+ const expecteds = [
+ { assignee, taskRecipeId: taskRecipe.id },
+ { assignee, taskRecipeId: taskRecipe.childTaskRecipes[0].id },
+ { assignee, taskRecipeId: taskRecipe.childTaskRecipes[1].id }
+ ]
+ var i = 0
+
+ const api = {
+ service: () => {
+ return {
+ create: (data, params) => {
+ t.deepEqual(data, expecteds[i++])
+ return data
+ }
+ }
+ }
+ }
+ const options = { assignee, taskRecipe }
+ return createTaskPlan(api, options)
+ .then(taskPlans => {
+ t.is(taskPlans.length, 3)
+ t.deepEqual(taskPlans, expecteds)
+ })
+})
diff --git a/updater.js b/updater.js
index 8da3923..9957f4b 100644
--- a/updater.js
+++ b/updater.js
@@ -3,12 +3,16 @@ import { routerReducer } from 'react-router-redux'
import { reducer as formReducer } from 'redux-form'
import { updater as agents } from 'dogstack-agents'
+import { updater as taskPlans } from './tasks/dux/plans'
+import { updater as taskWorks } from './tasks/dux/works'
const router = updateStateAt('router', reducerToUpdater(routerReducer))
const form = updateStateAt('form', reducerToUpdater(formReducer))
export default concat(
agents,
+ taskPlans,
+ taskWorks,
router,
form
)