Skip to content

Refactor #441

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

Merged
merged 4 commits into from
Aug 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ export { default as onTutorialConfigContinue } from './onTutorialConfigContinue'
export { default as onValidateSetup } from './onValidateSetup'
export { default as onRunReset } from './onRunReset'
export { default as onErrorPage } from './onErrorPage'
export { runTest, onTestPass } from './onTest'
export { runTest } from './onTest'
export { onOpenLogs } from './onOpenLogs'
15 changes: 5 additions & 10 deletions src/actions/onStartup.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import * as vscode from 'vscode'
import * as T from 'typings'
import * as TT from 'typings/tutorial'
import * as E from 'typings/error'
import Context from '../services/context/context'
import { send } from '../commands'
import { WORKSPACE_ROOT, TUTORIAL_URL } from '../environment'
import fetch from 'node-fetch'
import logger from '../services/logger'

const onStartup = async (
context: Context,
workspaceState: vscode.Memento,
send: (action: T.Action) => Promise<void>,
): Promise<void> => {
const onStartup = async (context: Context): Promise<void> => {
try {
// check if a workspace is open, otherwise nothing works
const noActiveWorkspace = !WORKSPACE_ROOT.length
Expand All @@ -38,9 +33,9 @@ const onStartup = async (
// continue from tutorial from local storage
const tutorial: TT.Tutorial | null = context.tutorial.get()

// no stored tutorial, must start new tutorial
// NEW: no stored tutorial, must start new tutorial
if (!tutorial || !tutorial.id) {
if (TUTORIAL_URL) {
if (!!TUTORIAL_URL) {
// NEW_FROM_URL
try {
const tutorialRes = await fetch(TUTORIAL_URL)
Expand All @@ -52,7 +47,7 @@ const onStartup = async (
console.log(`Failed to load tutorial from url ${TUTORIAL_URL} with error "${e.message}"`)
}
}
// NEW
// NEW from start click
send({ type: 'START_NEW_TUTORIAL', payload: { env } })
return
}
Expand Down
7 changes: 0 additions & 7 deletions src/actions/onTest.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import * as git from '../services/git'
import * as T from 'typings'
import * as vscode from 'vscode'
import { COMMANDS } from '../commands'
import Context from '../services/context/context'

export const onTestPass = (action: T.Action, context: Context): void => {
context.position.set({ ...action.payload.position, complete: true })
git.saveCommit('Save progress')
}

export const runTest = (action?: T.Action): void => {
vscode.commands.executeCommand(COMMANDS.RUN_TEST, action?.payload)
Expand Down
16 changes: 9 additions & 7 deletions src/actions/onTutorialConfigContinue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import * as T from 'typings'
import * as TT from 'typings/tutorial'
import Context from '../services/context/context'
import tutorialConfig from './utils/tutorialConfig'
import { COMMANDS } from '../commands'
import { COMMANDS, send } from '../commands'
import logger from '../services/logger'

const onTutorialConfigContinue = async (action: T.Action, context: Context, send: T.Send): Promise<void> => {
const onTutorialConfigContinue = async (action: T.Action, context: Context): Promise<void> => {
logger('onTutorialConfigContinue', action)
try {
const tutorialContinue: TT.Tutorial | null = context.tutorial.get()
if (!tutorialContinue) {
const tutorialToContinue: TT.Tutorial | null = context.tutorial.get()
if (!tutorialToContinue) {
throw new Error('Invalid tutorial to continue')
}
// update the current stepId on startup
vscode.commands.executeCommand(COMMANDS.SET_CURRENT_POSITION, action.payload.position)
await tutorialConfig({
data: tutorialContinue,
data: tutorialToContinue,
alreadyConfigured: true,
})
// update the current stepId on startup
vscode.commands.executeCommand(COMMANDS.SET_CURRENT_POSITION, action.payload.position)
} catch (e) {
const error = {
type: 'UnknownError',
Expand Down
3 changes: 2 additions & 1 deletion src/actions/onTutorialConfigNew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { onEvent } from '../services/telemetry'
import { version, compareVersions } from '../services/dependencies'
import Context from '../services/context/context'
import tutorialConfig from './utils/tutorialConfig'
import { send } from '../commands'

const onTutorialConfigNew = async (action: T.Action, context: Context, send: T.Send): Promise<void> => {
const onTutorialConfigNew = async (action: T.Action, context: Context): Promise<void> => {
try {
const data: TT.Tutorial = action.payload.tutorial

Expand Down
3 changes: 2 additions & 1 deletion src/actions/onValidateSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import * as T from 'typings'
import * as E from 'typings/error'
import { version } from '../services/dependencies'
import { checkWorkspaceEmpty } from '../services/workspace'
import { send } from '../commands'

const onValidateSetup = async (send: T.Send): Promise<void> => {
const onValidateSetup = async (): Promise<void> => {
try {
// check workspace is selected
const isEmptyWorkspace = await checkWorkspaceEmpty()
Expand Down
2 changes: 1 addition & 1 deletion src/actions/utils/tutorialConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const tutorialConfig = async ({ data, alreadyConfigured }: TutorialConfigParams)
}
}

await vscode.commands.executeCommand(COMMANDS.CONFIG_TEST_RUNNER, data)
await vscode.commands.executeCommand(COMMANDS.CONFIG_TEST_RUNNER, { data, alreadyConfigured })

if (!DISABLE_RUN_ON_SAVE) {
// verify if file test should run based on document saved
Expand Down
45 changes: 7 additions & 38 deletions src/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,36 @@ import * as hooks from './services/hooks'

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

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

class Channel implements Channel {
private postMessage: (action: T.Action) => Thenable<boolean>
private workspaceState: vscode.Memento
private context: Context
constructor({ postMessage, workspaceState }: ChannelProps) {
// workspaceState used for local storage
this.workspaceState = workspaceState
this.postMessage = postMessage
public context: Context
constructor(workspaceState: vscode.Memento) {
// workspaceState used for local storages
this.context = new Context(workspaceState)
}

// receive from webview
public receive = async (action: T.Action): Promise<void> => {
// 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 } })

logger(`EXT RECEIVED: "${actionType}"`)

switch (actionType) {
case 'EDITOR_STARTUP':
actions.onStartup(this.context, this.workspaceState, this.send)
actions.onStartup(this.context)
return
// clear tutorial local storage
// configure test runner, language, git
case 'EDITOR_TUTORIAL_CONFIG':
actions.onTutorialConfigNew(action, this.context, this.send)
actions.onTutorialConfigNew(action, this.context)
return
case 'EDITOR_TUTORIAL_CONTINUE_CONFIG':
actions.onTutorialConfigContinue(action, this.context, this.send)
actions.onTutorialConfigContinue(action, this.context)
return
case 'EDITOR_VALIDATE_SETUP':
actions.onValidateSetup(this.send)
actions.onValidateSetup()
return
case 'EDITOR_REQUEST_WORKSPACE':
openWorkspace()
Expand Down Expand Up @@ -95,26 +84,6 @@ class Channel implements Channel {
return
}
}
// send to webview
public send = async (action: T.Action): Promise<void> => {
// load error page if error action is triggered
actions.onErrorPage(action)
// action may be an object.type or plain string
const actionType: string = typeof action === 'string' ? action : action.type

logger(`EXT TO CLIENT: "${actionType}"`)

switch (actionType) {
case 'TEST_PASS':
actions.onTestPass(action, this.context)
}

// send message
const sentToClient = await this.postMessage(action)
if (!sentToClient) {
throw new Error(`Message post failure: ${JSON.stringify(action)}`)
}
}
}

export default Channel
32 changes: 25 additions & 7 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import createTestRunner from './services/testRunner'
import createWebView from './services/webview'
import * as hooks from './services/hooks'
import logger from './services/logger'
import * as actions from './actions'
import Channel from './channel'

export const COMMANDS = {
START: 'coderoad.start',
Expand All @@ -26,14 +28,20 @@ let sendToClient = (action: T.Action): void => {
// This makes it easier to pass the send
// function throughout the codebase
export const send = (action: T.Action): void => {
sendToClient(action)
// load error page if error action is triggered
actions.onErrorPage(action)

logger(`EXT TO CLIENT: "${typeof action === 'string' ? action : action.type}"`)

if (action) sendToClient(action)
}

export const createCommands = ({ extensionPath, workspaceState }: CreateCommandProps): { [key: string]: any } => {
// React panel webview
let webview: any
let currentPosition: T.Position
let testRunner: any
const channel = new Channel(workspaceState)

return {
// initialize
Expand All @@ -42,24 +50,33 @@ export const createCommands = ({ extensionPath, workspaceState }: CreateCommandP
webview.createOrShow()
} else {
// activate machine
webview = createWebView({
webview = await createWebView({
extensionPath,
workspaceState,
channel,
})
// make send to client function exportable
// as "send".
sendToClient = webview.send
}
},
[COMMANDS.CONFIG_TEST_RUNNER]: async (data: TT.Tutorial) => {
const setupActions = data.config.setup
if (setupActions) {
hooks.onInit(setupActions)
[COMMANDS.CONFIG_TEST_RUNNER]: async ({
data,
alreadyConfigured,
}: {
data: TT.Tutorial
alreadyConfigured: boolean
}) => {
if (!alreadyConfigured) {
const setupActions = data.config.setup
if (setupActions) {
hooks.onInit(setupActions)
}
}
testRunner = createTestRunner(data, {
onSuccess: (position: T.Position) => {
logger('test pass position', position)
// send test pass message back to client
channel.context.position.set({ ...position, complete: true })
send({ type: 'TEST_PASS', payload: { position: { ...position, complete: true } } })
},
onFail: (position: T.Position, failSummary: T.TestFail): void => {
Expand All @@ -83,6 +100,7 @@ export const createCommands = ({ extensionPath, workspaceState }: CreateCommandP
[COMMANDS.SET_CURRENT_POSITION]: (position: T.Position) => {
// set from last setup stepAction
currentPosition = position
channel.context.position.set(position)
},
[COMMANDS.RUN_TEST]: ({
subtasks,
Expand Down
4 changes: 3 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import * as vscode from 'vscode'
import { createCommands } from './commands'
import * as telemetry from './services/telemetry'

let onDeactivate = () => {}
let onDeactivate = () => {
/* placeholder for unsubscribing fn */
}

// activate run on vscode extension initialization
export const activate = (vscodeExt: vscode.ExtensionContext): void => {
Expand Down
31 changes: 31 additions & 0 deletions src/services/git/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,34 @@ export async function loadCommitHistory(): Promise<string[]> {
export function getShortHash(hash: string): string {
return hash.slice(0, 7)
}

export async function getCommitMessage(hash: string): Promise<string | null> {
try {
// returns an list of commit hashes
const { stdout, stderr } = await exec({ command: `git log -n 1 --pretty=format:%s ${hash}` })
if (stderr) {
return null
}
// string match on remote output
return stdout
} catch (error) {
logger('error', error)
// likely no git commit message found
return null
}
}

export async function commitsExistsByMessage(message: string): Promise<boolean> {
try {
// returns a list of commit hashes
const { stdout, stderr } = await exec({ command: `git log -g --grep='${message}'` })
if (stderr) {
return false
}
return !!stdout.length
} catch (error) {
logger('error', error)
// likely no commit found
return false
}
}
3 changes: 2 additions & 1 deletion src/services/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as TT from 'typings/tutorial'
import * as git from '../git'
import loadCommits from './utils/loadCommits'
import { loadCommits } from './utils/commits'
import { loadWatchers, resetWatchers } from './utils/watchers'
import openFiles from './utils/openFiles'
import runCommands from './utils/runCommands'
Expand Down Expand Up @@ -48,6 +48,7 @@ export const onError = async (error: Error): Promise<void> => {
}

export const onStepComplete = async ({ levelId, stepId }: { levelId: string; stepId: string }): Promise<void> => {
git.saveCommit(`Save progress: ${stepId}`)
logger(`ON STEP COMPLETE: ${JSON.stringify({ levelId, stepId })}`)
}

Expand Down
23 changes: 23 additions & 0 deletions src/services/hooks/utils/commits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as git from '../../git'

// avoid duplicate commits
const verifyCommitUnique = async (hash: string): Promise<boolean> => {
const message: string | null = await git.getCommitMessage(hash)
if (!message) {
return false
}
const exists: boolean = await git.commitsExistsByMessage(message)
return exists
}

export const loadCommits = async (commits: string[] = []): Promise<void> => {
if (commits && commits.length) {
// load the current list of commits for validation
for (const commit of commits) {
const commitExists = await verifyCommitUnique(commit)
if (!commitExists) {
await git.loadCommit(commit)
}
}
}
}
12 changes: 0 additions & 12 deletions src/services/hooks/utils/loadCommits.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/services/logger/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LOG } from '../../environment'

export type Log = string | number | object | null | undefined // eslint-disable-line
export type Log = any

const logger = (...messages: Log[]): void => {
if (!LOG) {
Expand Down
Loading