Skip to content

Commit

Permalink
Network logs ui iteration (testimio#4)
Browse files Browse the repository at this point in the history
* Network logs UI iteration
Co-authored-by: Benjamin Gruenbaum <[email protected]>
  • Loading branch information
Bnaya Peretz committed Aug 25, 2020
1 parent 23d7e96 commit 191d1a0
Show file tree
Hide file tree
Showing 36 changed files with 316 additions and 96 deletions.
1 change: 1 addition & 0 deletions packages/client/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '../src/fonts/MyFontsWebfontsKit.css';
1 change: 0 additions & 1 deletion packages/client/public/index.css

This file was deleted.

3 changes: 0 additions & 3 deletions packages/client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@

<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/MyFontsWebfontsKit.css">
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@500&display=swap" rel="stylesheet">
<link rel="icon" href="%PUBLIC_URL%/logo.svg" />
<link href="/index.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Root Cause</title>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { MenuDropDown } from './MenuDropDown';
import { action } from '@storybook/addon-actions';

import HarIcon from '../../globalAssets/[email protected]';
import TestLogIcon from '../../globalAssets/TestLog.svg';

export default {
title: 'Menu drop down',
component: MenuDropDown,
};

export function AsIs() {
return (
<MenuDropDown
onItemClick={action('item-click')}
items={[
{
slug: 'download_har_file',
text: 'Download HAR file',
icon: HarIcon,
},
{
slug: 'download_log_file',
text: 'Download test log',
icon: TestLogIcon,
},
]}
/>
);
}
52 changes: 52 additions & 0 deletions packages/client/src/components/MenuDropDown/MenuDropDown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import styles from './styles.module.css';

export interface MenuItem {
slug: string;
text: string;
icon: string;
}

export function MenuDropDown({
items,
onItemClick,
}: {
items: MenuItem[];
onItemClick(slug: string): void;
}) {
return (
<div className={styles.menuDropDownWrapper}>
{items.map((item) => {
return (
<MenuDropDownItem
key={item.slug}
text={item.text}
icon={item.icon}
onClick={() => {
onItemClick(item.slug);
}}
/>
);
})}
</div>
);
}

function MenuDropDownItem({
text,
icon,
onClick,
}: {
text: string;
icon?: string;
onClick(): void;
}) {
return (
<div className={styles.menuItemWrapper} onClick={onClick}>
<div className={styles.iconBox}>
{icon ? <img src={icon} alt={`${text} icon`} /> : null}
</div>
<span className={styles.text}>{text}</span>
</div>
);
}
36 changes: 36 additions & 0 deletions packages/client/src/components/MenuDropDown/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.menuDropDownWrapper {
border: 1px solid #DBDFE3;
background-color: #FFFFFF;
box-shadow: 0 6px 12px 0 rgba(0,0,0,0.15);
display: inline-block;
}

.menuItemWrapper {
display: flex;
align-items: center;
height: 50px;
width: 200px;
user-select: none;
transition: background-color 150ms;
white-space: nowrap;
letter-spacing: normal;
}

.menuItemWrapper:hover {
background-color: #ECEEEF;
cursor: pointer;
}

.menuItemWrapper .text {
font-family: "Mont-Regular";
font-size: 14px;
}


.menuItemWrapper .iconBox {
width: 56px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as React from "react";
import type { StepResult } from "@testim/root-cause-types";
import { useState, useRef, useLayoutEffect, useMemo, useEffect } from "react";
import { useState, useRef, useLayoutEffect } from "react";
import styles from './styles.module.css';
import classnames from "classnames";
import type { Har } from "har-format";
import downloadHarButton from "./../../globalAssets/archive.svg";
import { harInTimeRange } from "../../utils/harEntriesInTimeRange";

export const StepResultTitlebar = function StepResultTitlebar({
selectedStep,
Expand All @@ -23,33 +21,6 @@ export const StepResultTitlebar = function StepResultTitlebar({
const [ hover, setHover ] = useState<boolean>(false);
const [ toolTipNeeded, setToolTipNeeded ] = useState<boolean>(false);
const stepNameRef = useRef<HTMLSpanElement>(null);
const [harFileObjectUrl, setHarFileObjectUrl] = React.useState<string>();

const harFileJSONForStep = useMemo(() => {
if (harFileContents === undefined || selectedStep.endTimestamp === undefined || selectedStep.startTimestamp === undefined) {
return;
}

const stepStartTime = new Date(selectedStep.startTimestamp);
const stepEndTime = new Date(selectedStep.endTimestamp);

return harInTimeRange(harFileContents, stepStartTime, stepEndTime);

}, [harFileContents, selectedStep.endTimestamp, selectedStep.startTimestamp]);

useEffect(() => {
if (harFileJSONForStep === undefined) {
return;
}

const blob = new Blob([JSON.stringify(harFileJSONForStep, null, 2)], {type : 'application/json'});
const objectUrl = window.URL.createObjectURL(blob);
setHarFileObjectUrl(objectUrl);

return function cleanup() {
window.URL.revokeObjectURL(objectUrl);
}
}, [harFileJSONForStep])

useLayoutEffect(() => {

Expand Down Expand Up @@ -77,15 +48,6 @@ export const StepResultTitlebar = function StepResultTitlebar({
<span className={styles.stepError}>{selectedStep.stepError?.message.substr(0, 50)}</span>
</div>
<div className={styles.tabs}>
{harFileObjectUrl && <div className={styles.downloadHar}>
<a href={harFileObjectUrl} download="step-har-file.har">
<div>
<img src={downloadHarButton} alt="download har" />
DOWNLOAD HAR FOR STEP
<div className={styles.underline}></div>
</div>
</a>
</div>}
<div
className={classnames({ [styles.selected]: selectedTab === "screenshots" })}
onClick={() => { selectTab("screenshots"); }} >
Expand Down
153 changes: 119 additions & 34 deletions packages/client/src/components/TestResultTitlebar/TestResultTitlebar.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,136 @@
import * as React from "react";
import styles from "./styles.module.css";
import type { TestSystemInfo, TestResultFile } from "@testim/root-cause-types";
import classnames from "classnames";
import * as React from 'react';
import styles from './styles.module.css';
import type { TestSystemInfo, TestResultFile } from '@testim/root-cause-types';
import classnames from 'classnames';
import ms from 'ms';
import downloadHarButton from "./../../globalAssets/archive.svg";
import { useExternalResourceUrl } from "../../stores/ExternalResourceUrlContext";
import { useExternalResourceUrl } from '../../stores/ExternalResourceUrlContext';
import { MenuDropDown } from '../MenuDropDown/MenuDropDown';
import HarIcon from '../../globalAssets/[email protected]';
// import TestLogIcon from '../../globalAssets/TestLog.svg';

export const TestResultTitlebar = React.memo(function TestResultTitlebar({ testMetadata, totalTime, isClickimMode }: { testMetadata: TestResultFile["metadata"], totalTime:number, isClickimMode:boolean }) {
const testStatus: "success" | "failed" | "unknown" = testMetadata.testEndStatus ? testMetadata.testEndStatus.success ? "success" : "failed" : "unknown"
export const TestResultTitlebar = React.memo(function TestResultTitlebar({
testMetadata,
totalTime,
isClickimMode,
}: {
testMetadata: TestResultFile['metadata'];
totalTime: number;
isClickimMode: boolean;
}) {
const testStatus: 'success' | 'failed' | 'unknown' = testMetadata.testEndStatus
? testMetadata.testEndStatus.success
? 'success'
: 'failed'
: 'unknown';
const getExternalResourceUrl = useExternalResourceUrl();

return (
<div className={styles.titleBar}>
<div className={styles.logo}>
{ !isClickimMode && (
{!isClickimMode && (
<>
<div className={styles.logoSvg} />
<span className={styles.logoText}>Root Cause</span>
<div className={styles.logoSvg} />
<span className={styles.logoText}>Root Cause</span>
</>
) }
<span className={classnames(styles.testName, { [styles.clickimTitle]:isClickimMode })} title={testMetadata.testFullName}>{testMetadata.testName}</span>
)}
<span
className={classnames(styles.testName, {
[styles.clickimTitle]: isClickimMode,
})}
title={testMetadata.testFullName}
>
{testMetadata.testName}
</span>
</div>
<div className={styles.testStatus}>
{testMetadata.hasNetworkLogs && <a className={styles.downloadHar} role="button" href={getExternalResourceUrl('networklogs.har')} download="har.json"><img src={downloadHarButton} alt="Download HAR file" />Download HAR</a>}
{testStatus === "success" && <div className={styles.success}>TEST PASSED</div>}
{testStatus === "failed" && <div className={styles.fail}>TEST FAILED</div>}
<div className={classnames(styles.info, {
[styles.failed]: testStatus === "failed"
})}>
{/* {testMetadata.hasNetworkLogs && (
<a
className={styles.downloadHar}
role="button"
href={getExternalResourceUrl('networklogs.har')}
download="har.json"
>
<img src={downloadHarButton} alt="Download HAR file" />
Download HAR
</a>
)} */}
{testStatus === 'success' && <div className={styles.success}>TEST PASSED</div>}
{testStatus === 'failed' && <div className={styles.fail}>TEST FAILED</div>}
<div
className={classnames(styles.info, {
[styles.failed]: testStatus === 'failed',
})}
>
<div className={styles.whiteBackground}></div>
<div className={styles.tooltipErrorSvg}></div>
<div className={styles.popup}>
<div className={styles.triangle}></div>
{
testMetadata.systemInfo && <TestInfoPopupFields fields={mapTestSystemDataToTestFields(testMetadata.systemInfo)} totalTime={totalTime} />
}
{testMetadata.systemInfo && (
<TestInfoPopupFields
fields={mapTestSystemDataToTestFields(testMetadata.systemInfo)}
totalTime={totalTime}
/>
)}
</div>
</div>
</div>
{testMetadata.hasNetworkLogs && (
<>
<div className={classnames(styles.statusMenuSeparator)}></div>
<div className={classnames(styles.moreVertMenu)}>
<div className={classnames(styles.moreMenuContent)}>
<MenuDropDown
onItemClick={(slug) => {
if (slug === 'download_har_file') {
const aElement = document.createElement('a');
const href = getExternalResourceUrl('networkLogs.har');
console.log({ href });

if (!href) {
return;
}

aElement.href = href;
aElement.download = 'networklogs.har';
aElement.click();
} else if (slug === 'download_log_file') {
// not available yet
}

// console.log({ slug });
}}
items={[
{
slug: 'download_har_file',
text: 'Download HAR file',
icon: HarIcon,
},
// not available yet
// {
// slug: 'download_log_file',
// text: 'Download test log',
// icon: TestLogIcon,
// },
]}
/>
</div>
</div>
</>
)}
</div>
);
});

function TestInfoPopupFields({ fields, totalTime }: { fields: Array<[string, string]>, totalTime: number }) {
function TestInfoPopupFields({
fields,
totalTime,
}: {
fields: Array<[string, string]>;
totalTime: number;
}) {
return (
<div className={styles.fields}>
<div className={styles.headField}>TEST FAILED - { ms(totalTime, { long: true}) }</div>
<div className={styles.headField}>TEST FAILED - {ms(totalTime, { long: true })}</div>
{fields.map(([fieldName, fieldValue], i) => (
<React.Fragment key={i}>
<span>{fieldName}</span>
Expand All @@ -59,16 +144,16 @@ function TestInfoPopupFields({ fields, totalTime }: { fields: Array<[string, str
function mapTestSystemDataToTestFields(systemData: TestSystemInfo) {
const fields: Array<[string, string]> = [];

fields.push(["Browser Platform", systemData.browserPlatform]);
fields.push(["Browser Version", systemData.browserVersion]);
fields.push(["User Agent", systemData.userAgent]);
fields.push(["Machine Model Name", systemData.modelName]);
fields.push(["Machine Model Version", systemData.modelVersion]);
fields.push(["Viewport", `${systemData.pageViewport.width}x${systemData.pageViewport.height}`]);
fields.push(["Scale Factor", `${systemData.pageViewport.deviceScaleFactor || 1}`]);
fields.push(["Mobile", `${systemData.pageViewport.isMobile ? "yes": "no"}`]);
fields.push(["Landscape", `${systemData.pageViewport.isLandscape ? "yes": "no"}`]);
fields.push(["Touch Support", `${systemData.pageViewport.hasTouch ? "yes": "no"}`]);
fields.push(['Browser Platform', systemData.browserPlatform]);
fields.push(['Browser Version', systemData.browserVersion]);
fields.push(['User Agent', systemData.userAgent]);
fields.push(['Machine Model Name', systemData.modelName]);
fields.push(['Machine Model Version', systemData.modelVersion]);
fields.push(['Viewport', `${systemData.pageViewport.width}x${systemData.pageViewport.height}`]);
fields.push(['Scale Factor', `${systemData.pageViewport.deviceScaleFactor || 1}`]);
fields.push(['Mobile', `${systemData.pageViewport.isMobile ? 'yes' : 'no'}`]);
fields.push(['Landscape', `${systemData.pageViewport.isLandscape ? 'yes' : 'no'}`]);
fields.push(['Touch Support', `${systemData.pageViewport.hasTouch ? 'yes' : 'no'}`]);

return fields;
}
Loading

0 comments on commit 191d1a0

Please sign in to comment.