1
0
mirror of synced 2026-01-06 06:02:35 -05:00

Support Portal & Docs Collaboration: Virtual Assistant in Docs (#35772)

Co-authored-by: Peter Bengtsson <peterbe@github.com>
Co-authored-by: Peter Bengtsson <mail@peterbe.com>
This commit is contained in:
Zen van Riel
2023-03-29 14:38:47 +02:00
committed by GitHub
parent b79a19abf3
commit e66e2ab5ac
7 changed files with 187 additions and 0 deletions

View File

@@ -23,6 +23,7 @@ import { Breadcrumbs } from 'components/page-header/Breadcrumbs'
import { Link } from 'components/Link'
import { useTranslation } from 'components/hooks/useTranslation'
import { LinkPreviewPopover } from 'components/LinkPreviewPopover'
import { SupportPortalVaIframe } from 'components/article/SupportPortalVaIframe'
const ClientSideRefresh = dynamic(() => import('components/ClientSideRefresh'), {
ssr: false,
@@ -43,12 +44,17 @@ export const ArticlePage = () => {
productVideoUrl,
miniTocItems,
currentLearningTrack,
supportPortalVaIframeProps,
} = useArticleContext()
const isLearningPath = !!currentLearningTrack?.trackName
const { t } = useTranslation(['pages'])
return (
<DefaultLayout>
{supportPortalVaIframeProps.supportPortalUrl &&
supportPortalVaIframeProps.vaFlowUrlParameter && (
<SupportPortalVaIframe supportPortalVaIframeProps={supportPortalVaIframeProps} />
)}
<LinkPreviewPopover />
{isDev && <ClientSideRefresh />}
<ClientSideHighlight />

View File

@@ -0,0 +1,7 @@
.supportPortalIframe {
height: 95px;
width: 100%;
overflow: hidden;
border: none;
display: none;
}

View File

@@ -0,0 +1,95 @@
import { useRouter } from 'next/router'
import { useState, useRef, useEffect } from 'react'
import styles from './SupportPortalVaIframe.module.scss'
export interface SupportPortalVaIframeProps {
supportPortalUrl: string
vaFlowUrlParameter: string
}
const fullIframeHeight = '750px'
export function SupportPortalVaIframe({
supportPortalVaIframeProps,
}: {
supportPortalVaIframeProps: SupportPortalVaIframeProps
}) {
const [autoStartVa, setAutoStartVa] = useState(false)
const router = useRouter()
enum vaIframeMessageType {
OPEN = 'open',
START = 'start',
STOP = 'stop',
}
const [showIframe, setIframe] = useState(true)
const iframeRef = useRef<HTMLIFrameElement>(null)
useEffect(() => {
setAutoStartVa(router.query.autoStartVa === 'true')
}, [router.query])
useEffect(() => {
function eventHandler(event: MessageEvent<{ type: vaIframeMessageType }>) {
// An extra security measure which double checks that the events originate from the Support Portal domain
if (event.origin !== supportPortalVaIframeProps.supportPortalUrl) return
const message = event.data
switch (message.type) {
case vaIframeMessageType.OPEN:
// We need to set the display to inline from a ref explicitly to prevent the component from rerendering
// The iframe is hidden by default to allow Support Portal to disable the iframe remotely
if (iframeRef.current) {
iframeRef.current.style.display = 'inline'
}
break
case vaIframeMessageType.START:
// We need to set the height explicitly from a ref to prevent the component from rerendering
if (iframeRef.current) {
iframeRef.current.style.height = fullIframeHeight
}
break
case vaIframeMessageType.STOP:
// Effectively hide iframe. If preferred the element can also be deleted or display set to hidden.
setIframe(false)
break
default:
}
}
window.addEventListener('message', eventHandler)
return () => {
window.removeEventListener('message', eventHandler)
}
}, [])
useEffect(() => {
// Communicate to Support Portal that the iframe is ready to receive messages
// If the iframe feature is disabled on the Support Portal, no message will be sent back iframe will remain hidden
if (iframeRef.current?.contentWindow) {
iframeRef.current.contentWindow.postMessage(
'ready',
supportPortalVaIframeProps.supportPortalUrl
)
}
}, [iframeRef.current?.contentWindow])
if (!showIframe) {
return null
}
const usp = new URLSearchParams({ flow: supportPortalVaIframeProps.vaFlowUrlParameter })
usp.set('flow', supportPortalVaIframeProps.vaFlowUrlParameter)
if (autoStartVa) {
usp.set('autoStartVa', 'true')
}
const src = `${supportPortalVaIframeProps.supportPortalUrl}/iframe/docs_va?${usp}`
return (
<iframe
className={styles.supportPortalIframe}
ref={iframeRef}
title="support-portal-va"
id="support-portal-iframe"
src={src}
/>
)
}

View File

@@ -1,3 +1,4 @@
import { SupportPortalVaIframeProps } from 'components/article/SupportPortalVaIframe'
import { createContext, useContext } from 'react'
export type LearningTrack = {
@@ -36,6 +37,7 @@ export type ArticleContextT = {
detectedPlatforms: Array<string>
detectedTools: Array<string>
allTools: Record<string, string>
supportPortalVaIframeProps: SupportPortalVaIframeProps
}
export const ArticleContext = createContext<ArticleContextT | null>(null)
@@ -50,6 +52,11 @@ export const useArticleContext = (): ArticleContextT => {
return context
}
const PagePathToVaFlowMapping: Record<string, string> = {
'content/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/why-are-my-contributions-not-showing-up-on-my-profile.md':
'contribution_troubleshooting',
}
export const getArticleContextFromRequest = (req: any): ArticleContextT => {
const page = req.context.page
@@ -61,6 +68,17 @@ export const getArticleContextFromRequest = (req: any): ArticleContextT => {
}
}
const supportPortalUrl =
process.env.NODE_ENV === 'production'
? 'https://support.github.com'
: // Assume that a developer is not testing the VA iframe locally if this env var is not set
process.env.SUPPORT_PORTAL_URL || ''
const supportPortalVaIframeProps = {
supportPortalUrl,
vaFlowUrlParameter: PagePathToVaFlowMapping[req.context.page.fullPath] || '',
}
return {
title: page.title,
intro: page.intro,
@@ -78,5 +96,6 @@ export const getArticleContextFromRequest = (req: any): ArticleContextT => {
detectedPlatforms: page.detectedPlatforms || [],
detectedTools: page.detectedTools || [],
allTools: page.allToolsParsed || [], // this is set at the page level, see lib/page.js
supportPortalVaIframeProps,
}
}