From f06decd70a0cd1a79f90e9b4e36e6eb87f511636 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 13 Jul 2017 18:12:13 +1200 Subject: [PATCH 01/19] feat(db): add task migration --- db/migrations/20170713153018_add-tasks.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 db/migrations/20170713153018_add-tasks.js 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') + ]) +} From 6d536d28693b5124fa5566c59d4a870ac3c21e9d Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 13 Jul 2017 18:12:25 +1200 Subject: [PATCH 02/19] feat(db): add dev seed --- db/seeds/dev.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 db/seeds/dev.js 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 }) + }) +} From 212efc394812c893b61922cf8cb7305d50cd9135 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 13 Jul 2017 18:12:56 +1200 Subject: [PATCH 03/19] chore(babel): add babel-plugin to transform class properties --- package-lock.json | 4 +--- package.json | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index cd03ba5..ee257e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1279,8 +1279,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", @@ -1374,7 +1373,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", diff --git a/package.json b/package.json index 1239f4d..5ae8e1c 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "react" ], "plugins": [ + "transform-class-properties", "ramda" ] }, @@ -52,6 +53,10 @@ "dependencies": { "@ahdinosaur/react-fela": "^4.3.5", "babel-plugin-ramda": "^1.2.0", + "babel-plugin-transform-class-properties": "^6.24.1", + "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", @@ -89,9 +94,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", From 772564f777f1820f087ec19dff5075574157a5b4 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Thu, 13 Jul 2017 18:13:47 +1200 Subject: [PATCH 04/19] chore(tasks): fixup rename subTaskPlans to childTaskPlans to match migration --- tasks/components/TaskWorker.js | 8 ++++---- tasks/components/TaskWorkerTree.js | 32 +++++++++++++++--------------- tasks/data/recipes.js | 2 +- tasks/stories/TaskWorker.js | 6 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) 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/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', () => ( ( Date: Thu, 13 Jul 2017 18:14:12 +1200 Subject: [PATCH 05/19] feat(tasks): add helper to create simple or nested task plan, with tests! --- tasks/util/createTaskPlan.js | 23 ++++++++++++ tasks/util/createTaskPlan.test.js | 59 +++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 tasks/util/createTaskPlan.js create mode 100644 tasks/util/createTaskPlan.test.js diff --git a/tasks/util/createTaskPlan.js b/tasks/util/createTaskPlan.js new file mode 100644 index 0000000..e4ceb8f --- /dev/null +++ b/tasks/util/createTaskPlan.js @@ -0,0 +1,23 @@ +import { unnest } from 'ramda' +export default function createTaskPlan (api, options) { + const { assignee, taskRecipe } = options + const { childTaskRecipes = [] } = taskRecipe + + const taskPlansService = api.service('taskPlans') + + const parentTaskPlan = taskPlansService.create({ + assignee, + taskRecipeId: taskRecipe.id + }) + const childTaskPlans = childTaskRecipes.map(childTaskRecipe => { + 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) + }) +}) From 09113a4b6af03080801d42f49491a2c7a8906898 Mon Sep 17 00:00:00 2001 From: Michael Williams Date: Fri, 14 Jul 2017 14:02:21 +1200 Subject: [PATCH 06/19] feat(nav): integrate new Navigation component with app --- agents/components/LogOut.js | 15 ++++-- agents/containers/LogOut.js | 2 +- app/components/{layout.js => Layout.js} | 4 +- app/components/Navigation.js | 72 +++++++++++++++++-------- app/components/nav.js | 38 ------------- app/containers/{layout.js => Layout.js} | 2 +- app/locales/en.json | 6 +-- app/styles/{layout.js => Layout.js} | 0 app/styles/nav.js | 8 --- layout.js | 6 ++- package-lock.json | 4 +- package.json | 2 + routes.js | 18 ++++--- 13 files changed, 85 insertions(+), 92 deletions(-) rename app/components/{layout.js => Layout.js} (96%) delete mode 100644 app/components/nav.js rename app/containers/{layout.js => Layout.js} (76%) rename app/styles/{layout.js => Layout.js} (100%) delete mode 100644 app/styles/nav.js diff --git a/agents/components/LogOut.js b/agents/components/LogOut.js index 23ca56d..c235fe7 100644 --- a/agents/components/LogOut.js +++ b/agents/components/LogOut.js @@ -7,18 +7,23 @@ import { FormattedMessage } from '../../lib/Intl' import styles from '../styles/LogOut' function LogOut (props) { - const { styles, actions } = props + const { + styles, + actions, + as: Component = FlatButton, + ...moreProps + } = props return ( - - + ) } diff --git a/agents/containers/LogOut.js b/agents/containers/LogOut.js index ebd6fb2..232dad4 100644 --- a/agents/containers/LogOut.js +++ b/agents/containers/LogOut.js @@ -10,7 +10,7 @@ import LogOut from '../components/LogOut' export default flow( connectFeathers({ - selector: (state) => ({}), + selector: (state, ownProps) => ownProps, actions: { authentication: { logOut } }, query: [] }) diff --git a/app/components/layout.js b/app/components/Layout.js similarity index 96% rename from app/components/layout.js rename to app/components/Layout.js index 181574b..5b530b6 100644 --- a/app/components/layout.js +++ b/app/components/Layout.js @@ -3,9 +3,9 @@ import { createComponent } from '@ahdinosaur/react-fela' import { Route, Switch } from 'react-router-dom' import { pipe, map, values, isNil } from 'ramda' -import styles from '../styles/layout' +import styles from '../styles/Layout' -import Nav from './nav' +import Nav from './Navigation' const Container = createComponent(styles.container, 'div') diff --git a/app/components/Navigation.js b/app/components/Navigation.js index 79f8c14..12e805e 100644 --- a/app/components/Navigation.js +++ b/app/components/Navigation.js @@ -1,17 +1,63 @@ import React from 'react' import { connect as connectFela } from 'react-fela' -import { not } from 'ramda' +import { not, pipe, map, values, isNil } from 'ramda' import AppBar from 'material-ui/AppBar' import Drawer from 'material-ui/Drawer' import MenuItem from 'material-ui/MenuItem' import Divider from 'material-ui/Divider' import { withState, withHandlers, compose } from 'recompose' +import { NavLink } from 'react-router-dom' import styles from '../styles/Navigation' import { FormattedMessage } from '../../lib/Intl' +import LogOut from '../../agents/containers/LogOut' function Navigation (props) { - const { styles, isDrawerOpen, toggleDrawer } = props + const { styles, isDrawerOpen, toggleDrawer, navigationRoutes } = props + + const mapRouteItems = pipe( + map(route => { + const { + path, + name = path, + navigation + } = route + + const { + Component, + title = name, + icon + } = navigation + + if (Component) { + return ( +