Skip to content
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

Show error log if Playground fails to start #1336

Merged
merged 12 commits into from
May 7, 2024
10 changes: 5 additions & 5 deletions packages/php-wasm/logger/src/lib/handlers/log-to-memory.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { LogHandler } from '../log-handlers';
import { formatLogEntry, Log } from '../logger';

const prepareLogMessage = (message: any, ...args: any[]): string => {
return [
typeof message === 'object' ? JSON.stringify(message) : message,
...args.map((arg) => JSON.stringify(arg)),
].join(' ');
const prepareLogMessage = (logMessage: object): string => {
if (logMessage instanceof Error) {
return [logMessage.message, logMessage.stack].join('\n');
}
return JSON.stringify(logMessage, null, 2);
};

export const logs: string[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { usePlaygroundContext } from '../../playground-context';
import { Blueprint } from '@wp-playground/blueprints';

export function ErrorReportModal(props: { blueprint: Blueprint }) {
const { showErrorModal, setShowErrorModal } = usePlaygroundContext();
const { activeModal, setActiveModal } = usePlaygroundContext();
const [loading, setLoading] = useState(false);
const [text, setText] = useState('');
const [logs, setLogs] = useState('');
Expand All @@ -21,18 +21,16 @@ export function ErrorReportModal(props: { blueprint: Blueprint }) {
addCrashListener(logger, (e) => {
const error = e as CustomEvent;
if (error.detail?.source === 'php-wasm') {
setShowErrorModal(true);
setActiveModal('error-report');
}
});
}, [setShowErrorModal]);
}, [setActiveModal]);

useEffect(() => {
resetForm();
if (showErrorModal) {
setLogs(logger.getLogs().join('\n'));
setUrl(window.location.href);
}
}, [showErrorModal, setShowErrorModal, logs, setLogs]);
setLogs(logger.getLogs().join('\n'));
setUrl(window.location.href);
}, [activeModal, setActiveModal, logs, setLogs]);

function resetForm() {
setText('');
Expand All @@ -46,7 +44,7 @@ export function ErrorReportModal(props: { blueprint: Blueprint }) {
}

function onClose() {
setShowErrorModal(false);
setActiveModal(false);
resetForm();
resetSubmission();
}
Expand Down Expand Up @@ -150,7 +148,7 @@ export function ErrorReportModal(props: { blueprint: Blueprint }) {
}

return (
<Modal isOpen={showErrorModal} onRequestClose={onClose}>
<Modal isOpen={true} onRequestClose={onClose}>
<header className={css.errorReportModalHeader}>
<h2>{getTitle()}</h2>
<p>{getContent()}</p>
Expand Down
59 changes: 59 additions & 0 deletions packages/playground/website/src/components/log-modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useEffect, useState } from 'react';
import Modal from '../modal';
import { logger } from '@php-wasm/logger';

import css from './style.module.css';

import { usePlaygroundContext } from '../../playground-context';
import { TextControl } from '@wordpress/components';

export function LogModal(props: { description?: JSX.Element; title?: string }) {
const { activeModal, setActiveModal } = usePlaygroundContext();
const [logs, setLogs] = useState<string[]>([]);
const [searchTerm, setSearchTerm] = useState('');

useEffect(getLogs, [activeModal]);

function getLogs() {
setLogs(logger.getLogs());
}

function onClose() {
setActiveModal(false);
}

function logList() {
return logs
.filter((log) =>
log.toLowerCase().includes(searchTerm.toLowerCase())
)
.reverse()
.map((log, index) => (
<pre className={css.logModalLog} key={index}>
{log}
</pre>
));
}

const styles = {
content: { width: 800 },
};

return (
<Modal isOpen={true} onRequestClose={onClose} styles={styles}>
<header>
<h2>{props.title || 'Logs'}</h2>
{props.description}
<TextControl
title="Search"
placeholder="Search logs"
value={searchTerm}
onChange={setSearchTerm}
autoFocus={true}
className={css.logModalSearch}
/>
</header>
<main className={css.logModalMain}>{logList()}</main>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.log-modal__main {
padding-bottom: 20px;
}

.log-modal__search {
/* Title margin bottom + log margin top */
margin-bottom: 1.33rem;
}

.log-modal__log {
text-wrap: wrap;
word-wrap: break-word;
margin: 0.5rem 0;
}
8 changes: 6 additions & 2 deletions packages/playground/website/src/components/modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import css from './style.module.css';
ReactModal.setAppElement('#root');

interface ModalProps extends ReactModal.Props {
mergeStyles?: boolean;
styles?: ReactModal.Styles;
}
export const defaultStyles: ReactModal.Styles = {
content: {
Expand All @@ -29,8 +29,12 @@ export const defaultStyles: ReactModal.Styles = {
},
};
export default function Modal(props: ModalProps) {
const styles = {
overlay: { ...defaultStyles.overlay, ...props.styles?.overlay },
content: { ...defaultStyles.content, ...props.styles?.content },
};
return (
<ReactModal style={defaultStyles} {...props}>
<ReactModal style={styles} {...props}>
<div className={css.modalInner} id="modal-content">
<button
id="import-close-modal--btn"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Button } from '@wordpress/components';
import { LogModal } from '../log-modal';
import { useState } from '@wordpress/element';

import css from './style.module.css';

export const localStorageKey = 'playground-start-error-dont-show-again';

export function StartErrorModal() {
const [dontShowAgain, setDontShowAgain] = useState(
localStorage.getItem(localStorageKey) === 'true'
);

if (dontShowAgain) {
return null;
}

const dismiss = () => {
localStorage.setItem(localStorageKey, 'true');
setDontShowAgain(true);
};

const description = (
<>
<p>
An error occurred while starting Playground. Please read the
logs below to understand the issue. If there is Invalid
blueprint error, the error will include the step which caused
the issue. You can review your blueprint and{' '}
<a href="https://wordpress.github.io/wordpress-playground/blueprints-api/troubleshoot-and-debug-blueprints">
check out the documentation
</a>{' '}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use target="_blank"

Copy link
Collaborator

@adamziel adamziel Apr 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrite proposal:

Oops! There was a problem starting Playground. To figure out what went wrong, please take a look at the error logs provided below. If you see an "Invalid blueprint error," the logs will point out the specific step causing the issue. You can then double-check your blueprint. For more help, you can also visit our documentation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the crash modal replace this one if the error is a fatal crash?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the crash modal replace this one if the error is a fatal crash?

Yes, after fixing eab466f. But it doesn't look good. It first shows the logger and after that the error report modal. I need to find a way to not show logs in this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a bit to get to a clean solution, but this should work now 2dfec6f

for more information.
</p>
<Button
className={css.startErrorModalDismiss}
text="Don't show again"
onClick={dismiss}
variant="secondary"
isSmall={true}
/>
</>
);
return <LogModal title="Error" description={description} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.start-error-modal__dismiss {
margin-bottom: 1.33rem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { usePlaygroundContext } from '../../playground-context';

type Props = { onClose: () => void };
export function ReportError({ onClose }: Props) {
const { setShowErrorModal } = usePlaygroundContext();
const { setActiveModal } = usePlaygroundContext();
return (
<MenuItem
icon={bug}
iconPosition="left"
data-cy="report-error"
aria-label="Report an error in Playground"
onClick={() => {
setShowErrorModal(true);
setActiveModal('error-report');
onClose();
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { MenuItem } from '@wordpress/components';
import { details } from '@wordpress/icons';

import { usePlaygroundContext } from '../../playground-context';

type Props = { onClose: () => void };
export function ViewLogs({ onClose }: Props) {
const { setActiveModal } = usePlaygroundContext();
return (
<MenuItem
icon={details}
iconPosition="left"
data-cy="view-logs"
aria-label="View logs"
onClick={() => {
setActiveModal('log');
onClose();
}}
>
View logs
</MenuItem>
);
}
20 changes: 14 additions & 6 deletions packages/playground/website/src/lib/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useEffect, useRef, useState } from 'react';
import { Blueprint, startPlaygroundWeb } from '@wp-playground/client';
import type { PlaygroundClient } from '@wp-playground/client';
import { getRemoteUrl } from './config';
import { usePlaygroundContext } from '../playground-context';
import { logger } from '@php-wasm/logger';

interface UsePlaygroundOptions {
blueprint?: Blueprint;
Expand All @@ -14,6 +16,7 @@ export function usePlayground({ blueprint, storage }: UsePlaygroundOptions) {
const [url, setUrl] = useState<string>();
const [playground, setPlayground] = useState<PlaygroundClient>();
const [awaitedIframe, setAwaitedIframe] = useState(false);
const { setActiveModal } = usePlaygroundContext();

useEffect(() => {
if (started.current) {
Expand Down Expand Up @@ -45,12 +48,17 @@ export function usePlayground({ blueprint, storage }: UsePlaygroundOptions) {
playgroundTmp = playground;
(window as any)['playground'] = playground;
},
}).finally(async () => {
if (playgroundTmp) {
playgroundTmp.onNavigation((url) => setUrl(url));
setPlayground(() => playgroundTmp);
}
});
})
.catch((error) => {
logger.error(error);
setActiveModal('start-error');
})
.finally(async () => {
if (playgroundTmp) {
playgroundTmp.onNavigation((url) => setUrl(url));
setPlayground(() => playgroundTmp);
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [iframe, awaitedIframe]);

Expand Down
23 changes: 19 additions & 4 deletions packages/playground/website/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ResetSiteMenuItem } from './components/toolbar-buttons/reset-site';
import { DownloadAsZipMenuItem } from './components/toolbar-buttons/download-as-zip';
import { RestoreFromZipMenuItem } from './components/toolbar-buttons/restore-from-zip';
import { ReportError } from './components/toolbar-buttons/report-error';
import { ViewLogs } from './components/toolbar-buttons/view-logs';
import { resolveBlueprint } from './lib/resolve-blueprint';
import { GithubImportMenuItem } from './components/toolbar-buttons/github-import-menu-item';
import { acquireOAuthTokenIfNeeded } from './github/acquire-oauth-token-if-needed';
Expand All @@ -29,11 +30,13 @@ import {
asPullRequestAction,
} from './github/github-export-form/form';
import { joinPaths } from '@php-wasm/util';
import { PlaygroundContext } from './playground-context';
import { ActiveModal, PlaygroundContext } from './playground-context';
import { collectWindowErrors, logger } from '@php-wasm/logger';
import { ErrorReportModal } from './components/error-report-modal';
import { asContentType } from './github/import-from-github';
import { GitHubOAuthGuardModal } from './github/github-oauth-guard';
import { LogModal } from './components/log-modal';
import { StartErrorModal } from './components/start-error-modal';

collectWindowErrors(logger);

Expand Down Expand Up @@ -87,7 +90,7 @@ if (currentConfiguration.wp === '6.3') {
acquireOAuthTokenIfNeeded();

function Main() {
const [showErrorModal, setShowErrorModal] = useState(false);
const [activeModal, setActiveModal] = useState<ActiveModal | false>(false);
const [githubExportFiles, setGithubExportFiles] = useState<any[]>();
const [githubExportValues, setGithubExportValues] = useState<
Partial<ExportFormValues>
Expand Down Expand Up @@ -127,11 +130,22 @@ function Main() {
return values;
});

const modal = () => {
if (activeModal === 'log') {
return <LogModal />;
} else if (activeModal === 'error-report') {
return <ErrorReportModal blueprint={blueprint} />;
} else if (activeModal === 'start-error') {
return <StartErrorModal />;
}
return null;
};

return (
<PlaygroundContext.Provider
value={{ storage, showErrorModal, setShowErrorModal }}
value={{ storage, activeModal, setActiveModal }}
>
<ErrorReportModal blueprint={blueprint} />
{modal()}
<PlaygroundViewport
storage={storage}
displayMode={displayMode}
Expand Down Expand Up @@ -165,6 +179,7 @@ function Main() {
<RestoreFromZipMenuItem onClose={onClose} />
<GithubImportMenuItem onClose={onClose} />
<GithubExportMenuItem onClose={onClose} />
<ViewLogs onClose={onClose} />
<MenuItem
icon={external}
iconPosition="left"
Expand Down
12 changes: 9 additions & 3 deletions packages/playground/website/src/playground-context.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { createContext, useContext } from 'react';
import { StorageType } from './types';

export type ActiveModal = 'error-report' | 'log' | 'start-error' | false;

export const PlaygroundContext = createContext<{
storage: StorageType;
showErrorModal: boolean;
setShowErrorModal: (show: boolean) => void;
}>({ storage: 'none', showErrorModal: false, setShowErrorModal: () => {} });
activeModal: ActiveModal;
setActiveModal: (modal: ActiveModal) => void;
}>({
storage: 'none',
activeModal: false,
setActiveModal: () => {},
});
export const usePlaygroundContext = () => useContext(PlaygroundContext);
Loading