Skip to content

WIP - Split state machines #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Prev Previous commit
Next Next commit
update events as typings
  • Loading branch information
ShMcK committed Jan 26, 2020
commit b482ef8571644b7a53d494eedbebca2c60495b95
17 changes: 9 additions & 8 deletions src/channel/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as T from 'typings'
import * as CR from 'typings'
import * as G from 'typings/graphql'
import { EditorEvent, ClientEvent } from 'typings/events'
import * as vscode from 'vscode'
import saveCommit from '../actions/saveCommit'
import setupActions from '../actions/setupActions'
Expand All @@ -10,18 +11,18 @@ import logger from '../services/logger'
import Context from './context'

interface Channel {
receive(action: T.Action): Promise<void>
send(action: T.Action): Promise<void>
receive(action: EditorEvent): Promise<void>
send(action: ClientEvent): Promise<void>
}

interface ChannelProps {
postMessage: (action: T.Action) => Thenable<boolean>
postMessage: (action: ClientEvent) => Thenable<boolean>
workspaceState: vscode.Memento
workspaceRoot: vscode.WorkspaceFolder
}

class Channel implements Channel {
private postMessage: (action: T.Action) => Thenable<boolean>
private postMessage: (action: ClientEvent) => Thenable<boolean>
private workspaceState: vscode.Memento
private workspaceRoot: vscode.WorkspaceFolder
private context: Context
Expand All @@ -34,10 +35,10 @@ class Channel implements Channel {
}

// receive from webview
public receive = async (action: T.Action) => {
public receive = async (action: EditorEvent) => {
// action may be an object.type or plain string
const actionType: string = typeof action === 'string' ? action : action.type
const onError = (error: T.ErrorMessage) => this.send({ type: 'ERROR', payload: { error } })
const onError = (error: CR.ErrorMessage) => this.send({ type: 'ERROR', payload: { error } })

switch (actionType) {
case 'ENV_GET':
Expand Down Expand Up @@ -126,7 +127,7 @@ class Channel implements Channel {
}
}
// send to webview
public send = async (action: T.Action) => {
public send = async (action: ClientEvent) => {
// action may be an object.type or plain string
const actionType: string = typeof action === 'string' ? action : action.type
switch (actionType) {
Expand Down
71 changes: 71 additions & 0 deletions typings/events.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as CR from './index'
import * as G from './graphql'

/* --- Editor Events --- */

export type EnvGetEvent = { type: 'ENV_LOAD'; payload: { env: CR.Environment } }
export type EditorTutorialConfigEvent = { type: 'EDITOR_TUTORIAL_CONFIG'; payload: { tutorial: G.Tutorial } }
export type StepActionsEvent = { type: 'SETUP_ACTIONS'; payload: CR.StepActions }
export type SolutionActionsEvent = { type: 'SOLUTION_ACTIONS'; payload: CR.StepActions }

export type EditorEvents =
| EnvGetEvent
| { type: 'EDITOR_TUTORIAL_LOAD' }
| { type: 'TUTORIAL_CLEAR' }
| EditorTutorialConfigEvent
| { type: 'EDITOR_TUTORIAL_CONTINUE_CONFIG' }
| StepActionsEvent
| SolutionActionsEvent

/* --- Client Events --- */

export type EventLoadEvent = { type: 'ENV_LOAD'; payload: { env: CR.Environment } }
export type ErrorEvent = { type: 'ERROR'; payload: { error: string } }
export type CommandStartEvent = { type: 'COMMAND_START'; payload: { process: CR.ProcessEvent } }
export type CommandSuccessEvent = { type: 'COMMAND_SUCCESS'; payload: { process: CR.ProcessEvent } }
export type CommandFailEvent = { type: 'COMMAND_FAIL'; payload: { process: CR.ProcessEvent } }
export type TestPassEvent = { type: 'TEST_PASS'; payload: { stepId: string } }
export type TestFailEvent = { type: 'TEST_FAIL'; payload: { stepId: string } }
export type NextStepEvent = { type: 'NEXT_STEP'; payload: { position: CR.Position } }
export type NextLevelEvent = { type: 'NEXT_LEVEL'; payload: { position: CR.Position } }
export type TestRunningEvent = { type: 'TEST_RUNNING'; payload: { stepId: string } }
export type TestErrorEvent = { type: 'TEST_ERROR'; payload: { stepId: string } }
export type LoadNextStepEvent = { type: 'LOAD_NEXT_STEP'; payload: { step: string } }
export type ContinueTutorialEvent = {
type: 'CONTINUE_TUTORIAL'
payload: { tutorial: G.Tutorial; progress: CR.Progress; position: CR.Position }
}
export type LoadTutorialEvent = { type: 'LOAD_TUTORIAL'; payload: { tutorial: G.Tutorial } }
export type TutorialSelectedEvent = { type: 'TUTORIAL_SELECTED'; payload: { tutorial: G.Tutorial } }

export type AuthenticateEvents = EventLoadEvent | { type: 'AUTHENTICATED' } | ErrorEvent

export type PlayTutorialEvents =
| CommandStartEvent
| CommandSuccessEvent
| CommandFailEvent
| ErrorEvent
| NextStepEvent
| NextLevelEvent
| { type: 'COMPLETED' }
| TestRunningEvent
| { type: 'STEP_SOLUTION_LOAD' }
| TestPassEvent
| TestFailEvent
| TestErrorEvent
| LoadNextStepEvent
| { type: 'LEVEL_COMPLETE' }
| { type: 'EXIT' }

export type SelectTutorialEvents =
| ContinueTutorialEvent
| LoadTutorialEvent
| { type: 'NEW_TUTORIAL' }
| { type: 'BACK' }
| TutorialSelectedEvent
| { type: 'TUTORIAL_CONFIGURED' }
| { type: 'SELECT_NEW_TUTORIAL' }
| { type: 'TUTORIAL_START' }
| ErrorEvent

type ClientEvents = MachineEvent | AuthenticateEvents | SelectTutorialEvents | PlayTutorialEvents
2 changes: 1 addition & 1 deletion web-app/src/containers/New/TutorialList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {
const TutorialList = (props: Props) => {
const onSelect = (tutorial: G.Tutorial) => {
channel.machineSend({
type: 'SELECT_NEW_TUTORIAL',
type: 'LOAD_TUTORIAL',
payload: {
tutorial,
},
Expand Down
15 changes: 6 additions & 9 deletions web-app/src/services/channel/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { MachineEvent } from '../state/machine'
import { MachineEvent as AuthenticateEvent } from '../state/authenticate'
import { MachineEvent as SelectTutorialEvent } from '../state/selectTutorial'
import { MachineEvent as PlayTutorialEvent } from '../state/playTutorial'
import * as CR from 'typings'
import * as G from 'typings/graphql'
import { EditorEvent, ClientEvent } from 'typings/events'

declare let acquireVsCodeApi: any

type SendEvent = MachineEvent | AuthenticateEvent | SelectTutorialEvent | PlayTutorialEvent

interface ReceivedEvent {
data: SendEvent
data: ClientEvent
}

class Channel {
Expand All @@ -26,10 +23,10 @@ class Channel {
this.editorSend = editor.postMessage
}

public machineSend = (event: SendEvent) => {
public machineSend = (event: ClientEvent) => {
/* implemented by `setMachineSend` in router on startup */
}
public editorSend = (event: SendEvent) => {
public editorSend = (event: EditorEvent) => {
/* */
}

Expand Down
9 changes: 5 additions & 4 deletions web-app/src/services/state/authenticate/actions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as CR from 'typings'
import * as G from 'typings/graphql'
import { AuthenticateEvent, EnvGetEvent } from 'typings/events'
import { assign, ActionFunctionMap } from 'xstate'
import client from '../../apollo'
import { setAuthToken } from '../../apollo/auth'
import authenticateMutation from '../../apollo/mutations/authenticate'
import channel from '../../channel'
import onError from '../../../services/sentry/onError'
import { MachineContext, MachineEvent } from './index'
import { MachineContext } from './index'

interface AuthenticateData {
editorLogin: {
Expand All @@ -21,10 +22,10 @@ interface AuthenticateVariables {
editor: 'VSCODE'
}

const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
const actions: ActionFunctionMap<MachineContext, AuthenticateEvent> = {
// @ts-ignore
setEnv: assign({
env: (context: MachineContext, event: { type: 'ENV_LOAD'; payload: { env: CR.Environment } }): CR.Environment => ({
env: (context: MachineContext, event: EnvGetEvent): CR.Environment => ({
...context.env,
...event.payload.env,
}),
Expand Down Expand Up @@ -55,7 +56,7 @@ const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
description: error.message,
}
}
channel.receive({ data: { type: 'ERROR', payload: { error: message } } })
channel.receive({ data: ErrorEvent })
return
})

Expand Down
8 changes: 2 additions & 6 deletions web-app/src/services/state/authenticate/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import * as CR from 'typings'
import { AuthenticateEvent } from 'typings/events'
import { Machine } from 'xstate'
import actions from './actions'

export type MachineEvent =
| { type: 'ENV_LOAD'; payload: { env: CR.Environment } }
| { type: 'AUTHENTICATED' }
| { type: 'ERROR'; payload: { error: Error } }

export type StateSchema = {
states: {
LoadEnvironment: {}
Expand All @@ -24,7 +20,7 @@ const options = {
actions,
}

export const authenticateMachine = Machine<MachineContext, StateSchema, MachineEvent>(
export const authenticateMachine = Machine<MachineContext, StateSchema, AuthenticateEvent>(
{
id: 'authenticate',
context: {
Expand Down
6 changes: 1 addition & 5 deletions web-app/src/services/state/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import { authenticateMachine } from './authenticate'
import { selectTutorialMachine } from './selectTutorial'
import { playTutorialMachine } from './playTutorial'

export type MachineEvent = {
type: 'NONE'
}

export type MachineContext = {
env: CR.Environment
error: CR.ErrorMessage | null
Expand All @@ -25,7 +21,7 @@ export type MachineStateSchema = {
}
}

export const machine = Machine<MachineContext, MachineStateSchema, MachineEvent>({
export const machine = Machine<MachineContext, MachineStateSchema>({
id: 'root',
initial: 'Initializing',
context: {
Expand Down
30 changes: 10 additions & 20 deletions web-app/src/services/state/playTutorial/actions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as CR from 'typings'
import * as G from 'typings/graphql'
import * as Event from 'typings/events'
import { assign, send, ActionFunctionMap } from 'xstate'
import { MachineContext, MachineEvent } from './index'
import { MachineContext } from './index'
import channel from '../../channel'
import onError from '../../sentry/onError'
import * as selectors from '../../selectors'

const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
const actions: ActionFunctionMap<MachineContext, Event.PlayTutorialEvents> = {
userTutorialComplete(context: MachineContext) {
console.log('should update user tutorial as complete')
},
Expand All @@ -29,27 +30,21 @@ const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
}),
// @ts-ignore
commandSuccess: assign({
processes: (
{ processes }: MachineContext,
event: { type: 'COMMAND_SUCCESS'; payload: { process: CR.ProcessEvent } },
): CR.ProcessEvent[] => {
processes: ({ processes }: MachineContext, event: Event.CommandSuccessEvent): CR.ProcessEvent[] => {
const { process } = event.payload
return processes.filter(p => p.title !== process.title)
},
}),
// @ts-ignore
commandFail: assign({
processes: (
{ processes }: MachineContext,
event: { type: 'COMMAND_FAIL'; payload: { process: CR.ProcessEvent } },
): CR.ProcessEvent[] => {
processes: ({ processes }: MachineContext, event: Event.CommandFailEvent): CR.ProcessEvent[] => {
const { process } = event.payload
return processes.filter(p => p.title !== process.title)
},
}),
// @ts-ignore
updateStepPosition: assign({
position: (context: MachineContext, event: MachineEvent): CR.Position => {
position: (context: MachineContext, event: Event.PlayTutorialEvents): CR.Position => {
// TODO calculate from progress

const { position } = context
Expand Down Expand Up @@ -97,7 +92,7 @@ const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
}),
// @ts-ignore
updateLevelProgress: assign({
progress: (context: MachineContext, event: MachineEvent): CR.Progress => {
progress: (context: MachineContext, event: Event.PlayTutorialEvents): CR.Progress => {
// update progress by tracking completed
const { progress, position } = context

Expand All @@ -110,7 +105,7 @@ const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
}),
// @ts-ignore
updateStepProgress: assign({
progress: (context: MachineContext, event: { type: 'TEST_PASS'; payload: { stepId: string } }): CR.Progress => {
progress: (context: MachineContext, event: Event.TestPassEvent): CR.Progress => {
// update progress by tracking completed
const currentProgress: CR.Progress = context.progress

Expand All @@ -123,12 +118,7 @@ const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
}),
// @ts-ignore
updatePosition: assign({
position: (
context: MachineContext,
event:
| { type: 'NEXT_STEP'; payload: { position: CR.Position } }
| { type: 'NEXT_LEVEL'; payload: { position: CR.Position } },
): CR.Progress => {
position: (context: MachineContext, event: Event.NextStepEvent | Event.NextLevelEvent): CR.Position => {
const { position } = event.payload
return position
},
Expand Down Expand Up @@ -210,7 +200,7 @@ const actions: ActionFunctionMap<MachineContext, MachineEvent> = {
),
// @ts-ignore
setError: assign({
error: (context: MachineContext, event: { type: 'ERROR'; payload: { error: string } }): string | null => {
error: (context: MachineContext, event: Event.ErrorEvent): string | null => {
return event.payload.error
},
}),
Expand Down
22 changes: 3 additions & 19 deletions web-app/src/services/state/playTutorial/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as CR from 'typings'
import * as G from 'typings/graphql'
import { PlayTutorialEvents } from 'typings/events'
import { Machine, MachineOptions } from 'xstate'
import actions from './actions'

Expand All @@ -23,23 +24,6 @@ export type StateSchema = {
}
}

export type MachineEvent =
| { type: 'COMMAND_START'; payload: { process: CR.ProcessEvent } }
| { type: 'COMMAND_SUCCESS'; payload: { process: CR.ProcessEvent } }
| { type: 'COMMAND_FAIL'; payload: { process: CR.ProcessEvent } }
| { type: 'ERROR'; payload: { error: string } }
| { type: 'NEXT_STEP'; payload: { position: CR.Position } }
| { type: 'NEXT_LEVEL'; payload: { position: CR.Position } }
| { type: 'COMPLETED' }
| { type: 'TEST_RUNNING'; payload: { stepId: string } }
| { type: 'STEP_SOLUTION_LOAD' }
| { type: 'TEST_PASS'; payload: { stepId: string } }
| { type: 'TEST_FAIL'; payload: { stepId: string } }
| { type: 'TEST_ERROR'; payload: { stepId: string } }
| { type: 'LOAD_NEXT_STEP'; payload: { step: string } }
| { type: 'LEVEL_COMPLETE' }
| { type: 'EXIT' }

export type MachineContext = {
error: CR.ErrorMessage | null
env: CR.Environment
Expand All @@ -49,15 +33,15 @@ export type MachineContext = {
processes: CR.ProcessEvent[]
}

const options: MachineOptions<MachineContext, MachineEvent> = {
const options: MachineOptions<MachineContext, PlayTutorialEvents> = {
activities: {},
actions,
guards: {},
services: {},
delays: {},
}

export const playTutorialMachine = Machine<MachineContext, StateSchema, MachineEvent>(
export const playTutorialMachine = Machine<MachineContext, StateSchema, PlayTutorialEvents>(
{
context: {
error: null,
Expand Down
Loading