Skip to content

Commit

Permalink
Merge pull request #305 from briefercloud/onboarding-tutorial
Browse files Browse the repository at this point in the history
Onboarding tutorial
  • Loading branch information
lucasfcosta authored Jan 6, 2025
2 parents f686980 + b5705fe commit 9cfc4c0
Show file tree
Hide file tree
Showing 36 changed files with 1,534 additions and 892 deletions.
182 changes: 182 additions & 0 deletions apps/api/src/tutorials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import prisma from '@briefer/database'
import {
OnboardingTutorialStep,
StepStates,
TutorialState,
} from '@briefer/types'
import { logger } from './logger.js'

const ONBOARDING_STEP_ORDER: OnboardingTutorialStep[] = [
'connectDataSource',
'runQuery',
'runPython',
'createVisualization',
'publishDashboard',
'inviteTeamMembers',
]

export const stepStatesFromStep = (
stepIds: OnboardingTutorialStep[],
currentStep: OnboardingTutorialStep,
isComplete: boolean
): StepStates => {
const currentStepIndex = stepIds.indexOf(currentStep)

return stepIds.reduce<StepStates>((acc, stepId, index) => {
if (isComplete) {
return { ...acc, [stepId]: 'completed' }
}

if (index < currentStepIndex) {
return { ...acc, [stepId]: 'completed' }
} else if (index === currentStepIndex) {
return { ...acc, [stepId]: 'current' }
} else {
return { ...acc, [stepId]: 'upcoming' }
}
}, {} as StepStates)
}

export const getTutorialState = async (
workspaceId: string,
_tutorialType: 'onboarding'
): Promise<TutorialState | null> => {
const tutorial = await prisma().onboardingTutorial.findUnique({
where: {
workspaceId,
},
})

if (!tutorial) {
return null
}

const stepStates = stepStatesFromStep(
ONBOARDING_STEP_ORDER,
tutorial.currentStep,
tutorial.isComplete
)

return {
id: tutorial.id,
isCompleted: tutorial.isComplete,
isDismissed: tutorial.isDismissed,
stepStates,
}
}

export const advanceTutorial = async (
workspaceId: string,
tutorialType: 'onboarding',
// TODO don't allow null here - this is just for testing
ifCurrentStep: OnboardingTutorialStep | null
): Promise<TutorialState | null> => {
const tutorial = await prisma().onboardingTutorial.findUnique({
where: {
workspaceId,
},
})

if (!tutorial) {
logger().error(
{ workspaceId, tutorialType },
'Trying to advance tutorial that does not exist'
)
return null
}

if (
ifCurrentStep &&
(tutorial.isComplete || tutorial.currentStep !== ifCurrentStep)
) {
return {
id: tutorial.id,
isCompleted: tutorial.isComplete,
isDismissed: tutorial.isDismissed,
stepStates: stepStatesFromStep(
ONBOARDING_STEP_ORDER,
tutorial.currentStep,
tutorial.isComplete
),
}
}

const currentIndex = ONBOARDING_STEP_ORDER.indexOf(tutorial.currentStep)
const nextStepIndex = currentIndex + 1
const nextStep = ONBOARDING_STEP_ORDER[nextStepIndex]

if (nextStepIndex === ONBOARDING_STEP_ORDER.length) {
await prisma().onboardingTutorial.update({
where: {
workspaceId,
},
data: {
isComplete: true,
},
})

return {
id: tutorial.id,
isCompleted: true,
isDismissed: tutorial.isDismissed,
stepStates: stepStatesFromStep(
ONBOARDING_STEP_ORDER,
tutorial.currentStep,
true
),
}
}

if (!nextStep) {
logger().error(
{ workspaceId, tutorialType },
'Trying to advance tutorial to a step that does not exist'
)
return null
}

await prisma().onboardingTutorial.update({
where: {
workspaceId,
},
data: {
currentStep: nextStep,
},
})

return {
id: tutorial.id,
isCompleted: tutorial.isComplete,
isDismissed: tutorial.isDismissed,
stepStates: stepStatesFromStep(
ONBOARDING_STEP_ORDER,
nextStep,
tutorial.isComplete
),
}
}

export const dismissTutorial = async (
workspaceId: string,
_tutorialType: 'onboarding'
): Promise<TutorialState | null> => {
const tutorial = await prisma().onboardingTutorial.update({
where: {
workspaceId,
},
data: {
isDismissed: true,
},
})

return {
id: tutorial.id,
isCompleted: true,
isDismissed: tutorial.isDismissed,
stepStates: stepStatesFromStep(
ONBOARDING_STEP_ORDER,
tutorial.currentStep,
tutorial.isComplete
),
}
}
5 changes: 5 additions & 0 deletions apps/api/src/v1/workspaces/workspace/data-sources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { fetchDataSourceStructure } from '../../../../datasources/structure.js'
import { canUpdateWorkspace } from '../../../../auth/token.js'
import { captureDatasourceCreated } from '../../../../events/posthog.js'
import { IOServer } from '../../../../websocket/index.js'
import { advanceTutorial } from '../../../../tutorials.js'
import { broadcastTutorialStepStates } from '../../../../websocket/workspace/tutorial.js'

const dataSourcePayload = z.union([
z.object({
Expand Down Expand Up @@ -393,6 +395,9 @@ const dataSourcesRouter = (socketServer: IOServer) => {
})

res.status(201).json(datasource)

await advanceTutorial(workspaceId, 'onboarding', 'connectDataSource')
broadcastTutorialStepStates(socketServer, workspaceId, 'onboarding')
} catch (err) {
req.log.error(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { DocumentPersistor } from '../../../../../yjs/v2/persistors.js'
import { getYDocWithoutHistory } from '../../../../../yjs/v2/documents.js'
import { getDocId, getYDocForUpdate } from '../../../../../yjs/v2/index.js'
import { broadcastDocument } from '../../../../../websocket/workspace/documents.js'
import { advanceTutorial } from '../../../../../tutorials.js'
import { broadcastTutorialStepStates } from '../../../../../websocket/workspace/tutorial.js'

export default function publishRouter(socketServer: IOServer) {
const publishRouter = Router({ mergeParams: true })
Expand All @@ -22,6 +24,7 @@ export default function publishRouter(socketServer: IOServer) {
return
}

let hasDashboard = false
try {
await prisma().$transaction(
async (tx) => {
Expand All @@ -41,13 +44,14 @@ export default function publishRouter(socketServer: IOServer) {
doc.id,
doc.workspaceId,
async (yDoc) => {
hasDashboard = yDoc.dashboard.size > 0
await tx.yjsAppDocument.create({
data: {
documentId,
state: Buffer.from(
Y.encodeStateAsUpdate(getYDocWithoutHistory(yDoc))
),
hasDashboard: yDoc.dashboard.size > 0,
hasDashboard,
},
})
setPristine(yDoc.ydoc)
Expand All @@ -65,6 +69,9 @@ export default function publishRouter(socketServer: IOServer) {
await broadcastDocument(socketServer, workspaceId, documentId)

res.sendStatus(204)

await advanceTutorial(workspaceId, 'onboarding', 'publishDashboard')
broadcastTutorialStepStates(socketServer, workspaceId, 'onboarding')
} catch (err) {
req.log.error(
{
Expand Down
6 changes: 3 additions & 3 deletions apps/api/src/v1/workspaces/workspace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import { IOServer } from '../../../websocket/index.js'
import { getParam } from '../../../utils/express.js'
import { broadcastDocuments } from '../../../websocket/workspace/documents.js'
import { UserWorkspaceRole, updateWorkspace } from '@briefer/database'
import onboardingRouter from './onboarding.js'
import filesRouter from './files.js'
import componentsRouter from './components.js'
import { canUpdateWorkspace, hasWorkspaceRoles } from '../../../auth/token.js'
import { WorkspaceEditFormValues } from '@briefer/types'
import { encrypt } from '@briefer/database'
import { config } from '../../../config/index.js'
import { isWorkspaceNameValid } from '../../../utils/validation.js'
import tutorialsRouter from './tutorials.js'

const isAdmin = hasWorkspaceRoles([UserWorkspaceRole.admin])

Expand Down Expand Up @@ -84,10 +84,10 @@ export default function workspaceRouter(socketServer: IOServer) {
res.status(200).json(updatedWorkspace)
})

router.use('/onboarding', onboardingRouter)
router.use('/tutorials', tutorialsRouter(socketServer))
router.use('/data-sources', dataSourcesRouter(socketServer))
router.use('/documents', documentsRouter(socketServer))
router.use('/users', usersRouter)
router.use('/users', usersRouter(socketServer))
router.use('/favorites', favoritesRouter)
router.use(
'/environment-variables',
Expand Down
29 changes: 0 additions & 29 deletions apps/api/src/v1/workspaces/workspace/onboarding.ts

This file was deleted.

Loading

0 comments on commit 9cfc4c0

Please sign in to comment.