2023-10-24 18:35:07 +02:00
|
|
|
/*
|
|
|
|
|
Copyright (c) Microsoft Corporation.
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
2024-11-01 05:42:06 +01:00
|
|
|
import { ansi2html } from '@web/ansi2html';
|
2023-10-24 18:35:07 +02:00
|
|
|
import * as React from 'react';
|
|
|
|
|
import './testErrorView.css';
|
2024-10-11 01:49:17 +02:00
|
|
|
import type { ImageDiff } from '@web/shared/imageDiffView';
|
|
|
|
|
import { ImageDiffView } from '@web/shared/imageDiffView';
|
2025-02-06 12:31:45 +01:00
|
|
|
import { GitCommitInfoContext } from './reportView';
|
2023-10-24 18:35:07 +02:00
|
|
|
|
|
|
|
|
export const TestErrorView: React.FC<{
|
|
|
|
|
error: string;
|
2024-10-17 17:33:15 +02:00
|
|
|
testId?: string;
|
2025-02-06 17:05:12 +01:00
|
|
|
hidePrompt?: boolean;
|
|
|
|
|
}> = ({ error, testId, hidePrompt }) => {
|
2024-10-11 01:49:17 +02:00
|
|
|
const html = React.useMemo(() => ansiErrorToHtml(error), [error]);
|
2025-02-06 12:31:45 +01:00
|
|
|
return (
|
2025-02-06 17:10:10 +01:00
|
|
|
<>
|
|
|
|
|
<div className='test-error-view test-error-text' data-testid={testId}>
|
|
|
|
|
<div dangerouslySetInnerHTML={{ __html: html || '' }}></div>
|
|
|
|
|
</div>
|
|
|
|
|
<PromptButton error={error} />
|
|
|
|
|
</>
|
2025-02-06 12:31:45 +01:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ansiRegex = new RegExp('([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', 'g');
|
|
|
|
|
export function stripAnsiEscapes(str: string): string {
|
|
|
|
|
return str.replace(ansiRegex, '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PromptButton: React.FC<{
|
|
|
|
|
error: string;
|
|
|
|
|
}> = ({ error }) => {
|
2025-02-06 17:05:12 +01:00
|
|
|
const [copied, setCopied] = React.useState(false);
|
2025-02-06 12:31:45 +01:00
|
|
|
const gitCommitInfo = React.useContext(GitCommitInfoContext);
|
|
|
|
|
if (!gitCommitInfo)
|
|
|
|
|
return undefined;
|
|
|
|
|
|
|
|
|
|
const diff = gitCommitInfo['pull.diff'] ?? gitCommitInfo['revision.diff'];
|
|
|
|
|
if (!diff)
|
|
|
|
|
return undefined;
|
|
|
|
|
|
2025-02-06 17:10:10 +01:00
|
|
|
const showFireworks = () => {
|
|
|
|
|
const fireworksContainer = document.createElement('div');
|
|
|
|
|
fireworksContainer.className = 'fireworks-container';
|
|
|
|
|
document.body.appendChild(fireworksContainer);
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
document.body.removeChild(fireworksContainer);
|
|
|
|
|
}, 2000);
|
|
|
|
|
};
|
|
|
|
|
|
2025-02-06 12:31:45 +01:00
|
|
|
return (
|
2025-02-06 17:05:12 +01:00
|
|
|
<button
|
2025-02-06 17:10:10 +01:00
|
|
|
style={{
|
|
|
|
|
width: '100%',
|
|
|
|
|
padding: '10px',
|
|
|
|
|
marginTop: '10px',
|
|
|
|
|
borderRadius: '10px',
|
|
|
|
|
border: '2px solid #4caf50',
|
|
|
|
|
backgroundColor: copied ? '#4caf50' : '#fff',
|
|
|
|
|
color: copied ? '#fff' : '#4caf50',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
|
|
|
|
|
transition: 'background-color 0.3s, color 0.3s, transform 0.3s'
|
|
|
|
|
}}
|
2025-02-06 17:05:12 +01:00
|
|
|
onClick={async () => {
|
|
|
|
|
await navigator.clipboard.writeText([
|
|
|
|
|
'You are a helpful assistant. Help me understand the error cause. Here is the error:',
|
|
|
|
|
stripAnsiEscapes(error),
|
|
|
|
|
'And this is the code diff:',
|
|
|
|
|
diff
|
|
|
|
|
].join('\n\n'));
|
|
|
|
|
setCopied(true);
|
2025-02-06 17:10:10 +01:00
|
|
|
showFireworks();
|
2025-02-06 17:05:12 +01:00
|
|
|
setTimeout(() => setCopied(false), 1000);
|
2025-02-06 17:10:10 +01:00
|
|
|
}}
|
|
|
|
|
onMouseEnter={e => e.currentTarget.style.transform = 'scale(1.05)'}
|
|
|
|
|
onMouseLeave={e => e.currentTarget.style.transform = 'scale(1)'}
|
|
|
|
|
>
|
2025-02-06 17:05:12 +01:00
|
|
|
{copied ? 'Copied!' : 'Copy prompt to fix with AI'}
|
|
|
|
|
</button>
|
2025-02-06 12:31:45 +01:00
|
|
|
);
|
2023-10-24 18:35:07 +02:00
|
|
|
};
|
|
|
|
|
|
2024-10-11 01:49:17 +02:00
|
|
|
export const TestScreenshotErrorView: React.FC<{
|
|
|
|
|
errorPrefix?: string,
|
|
|
|
|
diff: ImageDiff,
|
|
|
|
|
errorSuffix?: string,
|
|
|
|
|
}> = ({ errorPrefix, diff, errorSuffix }) => {
|
|
|
|
|
const prefixHtml = React.useMemo(() => ansiErrorToHtml(errorPrefix), [errorPrefix]);
|
|
|
|
|
const suffixHtml = React.useMemo(() => ansiErrorToHtml(errorSuffix), [errorSuffix]);
|
|
|
|
|
return <div data-testid='test-screenshot-error-view' className='test-error-view'>
|
|
|
|
|
<div dangerouslySetInnerHTML={{ __html: prefixHtml || '' }} className='test-error-text' style={{ marginBottom: 20 }}></div>
|
|
|
|
|
<ImageDiffView key='image-diff' diff={diff} hideDetails={true}></ImageDiffView>
|
|
|
|
|
<div data-testid='error-suffix' dangerouslySetInnerHTML={{ __html: suffixHtml || '' }} className='test-error-text'></div>
|
|
|
|
|
</div>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function ansiErrorToHtml(text?: string): string {
|
2024-11-01 05:42:06 +01:00
|
|
|
const defaultColors = {
|
2024-10-11 01:49:17 +02:00
|
|
|
bg: 'var(--color-canvas-subtle)',
|
|
|
|
|
fg: 'var(--color-fg-default)',
|
|
|
|
|
};
|
2024-11-01 05:42:06 +01:00
|
|
|
return ansi2html(text || '', defaultColors);
|
2023-10-24 18:35:07 +02:00
|
|
|
}
|