Skip to content

Refactor editor #396

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 8 commits into from
Jul 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export { default as onStartup } from './onStartup'
export { default as onTutorialConfig } from './onTutorialConfig'
export { default as onTutorialContinueConfig } from './onTutorialContinueConfig'
export { default as onValidateSetup } from './onValidateSetup'
export { default as onRunReset } from './onRunReset'
export { default as onErrorPage } from './onErrorPage'
export { default as onTestPass } from './onTestPass'
export { onSetupActions, onSolutionActions } from './onActions'
6 changes: 3 additions & 3 deletions src/actions/setupActions.ts → src/actions/onActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface SetupActions {
dir?: string
}

export const setupActions = async ({ actions, send, dir }: SetupActions): Promise<void> => {
export const onSetupActions = async ({ actions, send, dir }: SetupActions): Promise<void> => {
if (!actions) {
return
}
Expand Down Expand Up @@ -49,7 +49,7 @@ export const setupActions = async ({ actions, send, dir }: SetupActions): Promis
}
}

export const solutionActions = async (params: SetupActions): Promise<void> => {
export const onSolutionActions = async (params: SetupActions): Promise<void> => {
await git.clear()
return setupActions(params).catch(onError)
return onSetupActions(params).catch(onError)
}
26 changes: 26 additions & 0 deletions src/actions/onErrorPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as T from 'typings'
import { readFile } from '../services/node'
import logger from '../services/logger'

const onErrorPage = async (action: T.Action) => {
// Error middleware
if (action?.payload?.error?.type) {
// load error markdown message
const error = action.payload.error
const errorMarkdown = await readFile(__dirname, '..', '..', 'errors', `${action.payload.error.type}.md`).catch(
() => {
// onError(new Error(`Error Markdown file not found for ${action.type}`))
},
)

// log error to console for safe keeping
logger(`ERROR:\n ${errorMarkdown}`)

if (errorMarkdown) {
// add a clearer error message for the user
error.message = `${errorMarkdown}\n\n${error.message}`
}
}
}

export default onErrorPage
32 changes: 32 additions & 0 deletions src/actions/onRunReset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as T from 'typings'
import * as TT from 'typings/tutorial'
import Context from '../services/context/context'
import { exec } from '../services/node'
import reset from '../services/reset'
import getLastCommitHash from '../services/reset/lastHash'

const onRunReset = async (context: Context) => {
// reset to timeline
const tutorial: TT.Tutorial | null = context.tutorial.get()
const position: T.Position = context.position.get()

// get last pass commit
const hash = getLastCommitHash(position, tutorial?.levels || [])

const branch = tutorial?.config.repo.branch

if (!branch) {
console.error('No repo branch found for tutorial')
return
}

// load timeline until last pass commit
reset({ branch, hash })

// if tutorial.config.reset.command, run it
if (tutorial?.config?.reset?.command) {
await exec({ command: tutorial.config.reset.command })
}
}

export default onRunReset
79 changes: 79 additions & 0 deletions src/actions/onStartup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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 { 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>,
) => {
try {
// check if a workspace is open, otherwise nothing works
const noActiveWorkspace = !WORKSPACE_ROOT.length
if (noActiveWorkspace) {
const error: E.ErrorMessage = {
type: 'NoWorkspaceFound',
message: '',
actions: [
{
label: 'Open Workspace',
transition: 'REQUEST_WORKSPACE',
},
],
}
send({ type: 'NO_WORKSPACE', payload: { error } })
return
}

const env = {
machineId: vscode.env.machineId,
sessionId: vscode.env.sessionId,
}

// load tutorial from url
if (TUTORIAL_URL) {
try {
const tutorialRes = await fetch(TUTORIAL_URL)
const tutorial = await tutorialRes.json()
send({ type: 'START_TUTORIAL_FROM_URL', payload: { tutorial } })
return
} catch (e) {
console.log(`Failed to load tutorial from url ${TUTORIAL_URL} with error "${e.message}"`)
}
}

// continue from tutorial from local storage
const tutorial: TT.Tutorial | null = context.tutorial.get()

// no stored tutorial, must start new tutorial
if (!tutorial || !tutorial.id) {
send({ type: 'START_NEW_TUTORIAL', payload: { env } })
return
}

// load continued tutorial position & progress
const { position, progress } = await context.setTutorial(workspaceState, tutorial)
logger('CONTINUE STATE', position, progress)

if (progress.complete) {
// tutorial is already complete
send({ type: 'TUTORIAL_ALREADY_COMPLETE', payload: { env } })
return
}
// communicate to client the tutorial & stepProgress state
send({ type: 'LOAD_STORED_TUTORIAL', payload: { env, tutorial, progress, position } })
} catch (e) {
const error = {
type: 'UnknownError',
message: `Location: Editor startup\n\n${e.message}`,
}
send({ type: 'EDITOR_STARTUP_FAILED', payload: { error } })
}
}

export default onStartup
16 changes: 16 additions & 0 deletions src/actions/onTestPass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as git from '../services/git'
import * as T from 'typings'
import Context from '../services/context/context'

const onTestPass = (action: T.Action, context: Context) => {
const tutorial = context.tutorial.get()
if (!tutorial) {
throw new Error('Error with current tutorial. Tutorial may be missing an id.')
}
// update local storage stepProgress
const progress = context.progress.setStepComplete(tutorial, action.payload.position.stepId)
context.position.setPositionFromProgress(tutorial, progress)
git.saveCommit('Save progress')
}

export default onTestPass
121 changes: 121 additions & 0 deletions src/actions/onTutorialConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as vscode from 'vscode'
import * as T from 'typings'
import * as TT from 'typings/tutorial'
import * as E from 'typings/error'
import { satisfies } from 'semver'
import { onEvent } from '../services/telemetry'
import { version, compareVersions } from '../services/dependencies'
import Context from '../services/context/context'
import tutorialConfig from './utils/tutorialConfig'

const onTutorialConfig = async (action: T.Action, context: Context, workspaceState: vscode.Memento, send: any) => {
try {
const data: TT.Tutorial = action.payload.tutorial

onEvent('tutorial_start', {
tutorial_id: data.id,
tutorial_version: data.version,
tutorial_title: data.summary.title,
})

// validate extension version
const expectedAppVersion = data.config?.appVersions?.vscode
if (expectedAppVersion) {
const extension = vscode.extensions.getExtension('coderoad.coderoad')
if (extension) {
const currentAppVersion = extension.packageJSON.version
const satisfied = satisfies(currentAppVersion, expectedAppVersion)
if (!satisfied) {
const error: E.ErrorMessage = {
type: 'UnmetExtensionVersion',
message: `Expected CodeRoad v${expectedAppVersion}, but found v${currentAppVersion}`,
}
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
return
}
}
}

// setup tutorial config (save watcher, test runner, etc)
await context.setTutorial(workspaceState, data)

// validate dependencies
const dependencies = data.config.dependencies
if (dependencies && dependencies.length) {
for (const dep of dependencies) {
// check dependency is installed
const currentVersion: string | null = await version(dep.name)
if (!currentVersion) {
// use a custom error message
const error: E.ErrorMessage = {
type: 'MissingTutorialDependency',
message: dep.message || `Process "${dep.name}" is required but not found. It may need to be installed`,
actions: [
{
label: 'Check Again',
transition: 'TRY_AGAIN',
},
],
}
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
return
}

// check dependency version
const satisfiedDependency = await compareVersions(currentVersion, dep.version)

if (!satisfiedDependency) {
const error: E.ErrorMessage = {
type: 'UnmetTutorialDependency',
message: `Expected ${dep.name} to have version ${dep.version}, but found version ${currentVersion}`,
actions: [
{
label: 'Check Again',
transition: 'TRY_AGAIN',
},
],
}
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
return
}

if (satisfiedDependency !== true) {
const error: E.ErrorMessage = satisfiedDependency || {
type: 'UnknownError',
message: `Something went wrong comparing dependency for ${name}`,
actions: [
{
label: 'Try Again',
transition: 'TRY_AGAIN',
},
],
}
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
return
}
}
}

const error: E.ErrorMessage | void = await tutorialConfig({ data }).catch((error: Error) => ({
type: 'UnknownError',
message: `Location: tutorial config.\n\n${error.message}`,
}))

// has error
if (error && error.type) {
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
return
}

// report back to the webview that setup is complete
send({ type: 'TUTORIAL_CONFIGURED' })
} catch (e) {
const error = {
type: 'UnknownError',
message: `Location: EditorTutorialConfig.\n\n ${e.message}`,
}
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
}
}

export default onTutorialConfig
29 changes: 29 additions & 0 deletions src/actions/onTutorialContinueConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as vscode from 'vscode'
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'

const onTutorialContinueConfig = async (action: T.Action, context: Context, send: any) => {
try {
const tutorialContinue: TT.Tutorial | null = context.tutorial.get()
if (!tutorialContinue) {
throw new Error('Invalid tutorial to continue')
}
await tutorialConfig({
data: tutorialContinue,
alreadyConfigured: true,
})
// update the current stepId on startup
vscode.commands.executeCommand(COMMANDS.SET_CURRENT_POSITION, action.payload.position)
} catch (e) {
const error = {
type: 'UnknownError',
message: `Location: Editor tutorial continue config.\n\n ${e.message}`,
}
send({ type: 'CONTINUE_FAILED', payload: { error } })
}
}

export default onTutorialContinueConfig
54 changes: 54 additions & 0 deletions src/actions/onValidateSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as E from 'typings/error'
import { version } from '../services/dependencies'
import { checkWorkspaceEmpty } from '../services/workspace'

const onValidateSetup = async (send: any) => {
try {
// check workspace is selected
const isEmptyWorkspace = await checkWorkspaceEmpty()
if (!isEmptyWorkspace) {
const error: E.ErrorMessage = {
type: 'WorkspaceNotEmpty',
message: '',
actions: [
{
label: 'Open Workspace',
transition: 'REQUEST_WORKSPACE',
},
{
label: 'Check Again',
transition: 'RETRY',
},
],
}
send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
return
}
// check Git is installed.
// Should wait for workspace before running otherwise requires access to root folder
const isGitInstalled = await version('git')
if (!isGitInstalled) {
const error: E.ErrorMessage = {
type: 'GitNotFound',
message: '',
actions: [
{
label: 'Check Again',
transition: 'RETRY',
},
],
}
send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
return
}
send({ type: 'SETUP_VALIDATED' })
} catch (e) {
const error = {
type: 'UknownError',
message: e.message,
}
send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
}
}

export default onValidateSetup
7 changes: 0 additions & 7 deletions src/actions/saveCommit.ts

This file was deleted.

Loading