Skip to content

Commit

Permalink
#145 move indicators to general tab
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Mrowetz committed Feb 12, 2017
1 parent 9dc26a2 commit 64612ed
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 100 deletions.
1 change: 1 addition & 0 deletions src/css-raw/perf-cascade.css
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ svg.water-fall-chart {width:100%; overflow: visible;}

.info-overlay-holder .tab {float: left; width:100%; height: 350px; padding:12px 12px 24px;}
.info-overlay-holder .tab h2 {font-size: 1.2em; margin:0.5em 0 0; padding: 0.5em 0 0.5em 1em; clear: both; border-top: solid 1px #efefef;}
.info-overlay-holder .tab h2:first-child {border-top: 0; padding-top: 0;}
.info-overlay-holder .tab pre {overflow-y: hidden;}
.info-overlay-holder .tab .preview {width: auto; max-width: 100%; max-height: 500px; border: solid 1px #666;}
.info-overlay-holder .tab dl:after {content: ""; display: table; clear: both;}
Expand Down
77 changes: 0 additions & 77 deletions src/ts/helpers/heuristics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {Entry} from "../typing/har";
import {RequestType} from "../typing/waterfall";
import {hasHeader} from "./har";
import * as misc from "./misc";

/**
*
Expand All @@ -13,77 +10,3 @@ import * as misc from "./misc";
export function isInStatusCodeRange(entry: Entry, lowerBound: number, upperBound: number) {
return entry.response.status >= lowerBound && entry.response.status <= upperBound;
}

function isCompressible(entry: Entry, requestType: RequestType): boolean {
const minCompressionSize = 1000;
// small responses
if (entry.response.bodySize < minCompressionSize) {
return false;
}

if (misc.contains(["html", "css", "javascript", "svg", "plain"], requestType)) {
return true;
}
const mime = entry.response.content.mimeType;
const compressableMimes = ["application/vnd.ms-fontobject",
"application/x-font-opentype",
"application/x-font-truetype",
"application/x-font-ttf",
"application/xml",
"font/eot",
"font/opentype",
"font/otf",
"image/vnd.microsoft.icon"];
if (misc.contains(["text"], mime.split("/")[0]) || misc.contains(compressableMimes, mime.split(";")[0])) {
return true;
}
return false;
}

/**
* Checks if response could be cacheable, but isn't due to lack of cache header.
* @param {Entry} entry - the waterfall entry.
* @returns {boolean}
*/
export function hasCacheIssue(entry: Entry) {
if (entry.request.method.toLowerCase() !== "get") {
return false;
}
if (entry.response.status === 204 || !isInStatusCodeRange(entry, 200, 299)) {
return false;
}

const headers = entry.response.headers;
return !(hasHeader(headers, "Cache-Control") || hasHeader(headers, "Expires"));
}

export function hasCompressionIssue(entry: Entry, requestType: RequestType) {
const headers = entry.response.headers;
return (!hasHeader(headers, "Content-Encoding") && isCompressible(entry, requestType));
}

/** Checks if the ressource uses https */
export function isSecure(entry: Entry) {
return entry.request.url.indexOf("https://") === 0;
}

export function isPush(entry: Entry): boolean {
function toInt(input: string | number): number {
if (typeof input === "string") {
return parseInt(input, 10);
} else {
return input;
}
}
return toInt(entry._was_pushed) === 1;
}

/**
* Check if the document (disregarding any initial http->https redirects) is loaded over a secure connection.
* @param {Entry[]} data - the waterfall entries data.
* @returns {boolean}
*/
export function documentIsSecure(data: Entry[]) {
const rootDocument = data.filter((e) => !e.response.redirectURL)[0];
return isSecure(rootDocument);
}
84 changes: 84 additions & 0 deletions src/ts/transformers/har-heuristics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Heuristics used at parse-time for HAR data
*/

import { hasHeader } from "../helpers/har";
import { isInStatusCodeRange } from "../helpers/heuristics";
import * as misc from "../helpers/misc";
import { Entry } from "../typing/har";
import { RequestType } from "../typing/waterfall";


function isCompressible(entry: Entry, requestType: RequestType): boolean {
const minCompressionSize = 1000;
// small responses
if (entry.response.bodySize < minCompressionSize) {
return false;
}

if (misc.contains(["html", "css", "javascript", "svg", "plain"], requestType)) {
return true;
}
const mime = entry.response.content.mimeType;
const compressableMimes = ["application/vnd.ms-fontobject",
"application/x-font-opentype",
"application/x-font-truetype",
"application/x-font-ttf",
"application/xml",
"font/eot",
"font/opentype",
"font/otf",
"image/vnd.microsoft.icon"];
if (misc.contains(["text"], mime.split("/")[0]) || misc.contains(compressableMimes, mime.split(";")[0])) {
return true;
}
return false;
}

/**
* Checks if response could be cacheable, but isn't due to lack of cache header.
* @param {Entry} entry - the waterfall entry.
* @returns {boolean}
*/
export function hasCacheIssue(entry: Entry) {
if (entry.request.method.toLowerCase() !== "get") {
return false;
}
if (entry.response.status === 204 || !isInStatusCodeRange(entry, 200, 299)) {
return false;
}

const headers = entry.response.headers;
return !(hasHeader(headers, "Cache-Control") || hasHeader(headers, "Expires"));
}

export function hasCompressionIssue(entry: Entry, requestType: RequestType) {
const headers = entry.response.headers;
return (!hasHeader(headers, "Content-Encoding") && isCompressible(entry, requestType));
}

/** Checks if the ressource uses https */
export function isSecure(entry: Entry) {
return entry.request.url.indexOf("https://") === 0;
}

export function isPush(entry: Entry): boolean {
function toInt(input: string | number): number {
if (typeof input === "string") {
return parseInt(input, 10);
} else {
return input;
}
}
return toInt(entry._was_pushed) === 1;
}

/**
* Check if the document (disregarding any initial http->https redirects) is loaded over a secure connection.
* @param {Entry[]} data - the waterfall entries data.
* @returns {boolean}
*/
export function documentIsSecure(data: Entry[]) {
const rootDocument = data.filter((e) => !e.response.redirectURL)[0];
return isSecure(rootDocument);
}
25 changes: 13 additions & 12 deletions src/ts/transformers/har.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as heuristics from "../helpers/heuristics";
import { isInStatusCodeRange } from "../helpers/heuristics";
import { roundNumber } from "../helpers/misc";
import { Entry, Har, PageTimings } from "../typing/har";
import {
Expand All @@ -11,6 +11,7 @@ import {
WaterfallEntryIndicator,
WaterfallEntryTiming,
} from "../typing/waterfall";
import * as harHeuristics from "./har-heuristics";

function createWaterfallEntry(name: string,
start: number,
Expand Down Expand Up @@ -95,7 +96,7 @@ function collectIndicators(entry: Entry, docIsTLS: boolean, requestType: Request
// const harEntry = entry;
let output: WaterfallEntryIndicator[] = [];

if (heuristics.isPush(entry)) {
if (harHeuristics.isPush(entry)) {
output.push({
description: "Response was pushed by the server using HTTP2 push.",
icon: "push",
Expand All @@ -105,38 +106,38 @@ function collectIndicators(entry: Entry, docIsTLS: boolean, requestType: Request
});
}

if (docIsTLS && !heuristics.isSecure(entry)) {
if (docIsTLS && !harHeuristics.isSecure(entry)) {
output.push({
description: "Insecure Connetion, it should uses TLS",
description: "Insecure request, it should use HTTPS.",
id: "noTls",
title: "Insecure Connection",
type: "error",
});
}

if (heuristics.hasCacheIssue(entry)) {
if (harHeuristics.hasCacheIssue(entry)) {
output.push({
description: "The response does not allow to be cached on the client. Consider setting 'Cache-Control' headers.",
description: "The response is not allow to be cached on the client. Consider setting 'Cache-Control' headers.",
id: "noCache",
title: "Response not cached",
type: "error",
});
}

if (heuristics.hasCompressionIssue(entry, requestType)) {
if (harHeuristics.hasCompressionIssue(entry, requestType)) {
output.push({
description: "The response is not compressed.",
description: "The response is not compressed. Consider enabling HTTP compression on your server.",
id: "noGzip",
title: "no gzip",
type: "error",
});
}

if (!entry.response.content.mimeType &&
heuristics.isInStatusCodeRange(entry, 200, 299) &&
entry.response.status !== 204) {
isInStatusCodeRange(entry, 200, 299) &&
entry.response.status !== 204) {
output.push({
description: "No MIME Type defined",
description: "Response doesn't contain a 'Content-Type' header.",
id: "warning",
title: "No MIME Type defined",
type: "warning",
Expand Down Expand Up @@ -186,7 +187,7 @@ export function transformPage(harData: Har, pageIndex: number = 0): WaterfallDat
console.log("%s: %s of %s page(s)", currPage.title, pageIndex + 1, data.pages.length);

let doneTime = 0;
const isTLS = heuristics.documentIsSecure(data.entries);
const isTLS = harHeuristics.documentIsSecure(data.entries);
const entries = data.entries
.filter((entry) => entry.pageref === currPage.id)
.map((entry) => {
Expand Down
42 changes: 34 additions & 8 deletions src/ts/waterfall/details-overlay/html-details-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,40 @@ function makeImgTab(accordionHeight: number, entry: WaterfallEntry) {
return makeTab(imgTag, false);
}

function makeIndicatorTab(entry: WaterfallEntry) {
function makeGeneralTab(generalData: KvTuple[], entry: WaterfallEntry){
let content = makeDefinitionList(generalData);
if (entry.indicators.length === 0) {
return "";
return makeTab(content, true);
}
let general = `<h2>General</h2>
<dl>${content}<dl>`;
content = "";

// Make indicator sections
let errors = entry.indicators
.filter((i) => i.type === "error")
.map((i) => [i.title, i.description] as KvTuple);
let warnings = entry.indicators
.filter((i) => i.type === "warning")
.map((i) => [i.title, i.description] as KvTuple);
// all others
let info = entry.indicators
.filter((i) => i.type !== "error" && i.type !== "warning")
.map((i) => [i.title, i.description] as KvTuple);

if (errors.length > 0) {
content += `<h2 class="no-boder">Error${errors.length > 1 ? "s" : ""}</h2>
<dl>${makeDefinitionList(errors)}</dl>`;
}
if (warnings.length > 0) {
content += `<h2 class="no-boder">Warning${warnings.length > 1 ? "s" : ""}</h2>
<dl>${makeDefinitionList(warnings)}</dl>`;
}
if (info.length > 0) {
content += `<h2 class="no-boder">Info</h2>
<dl>${makeDefinitionList(info)}</dl>`;
}
let content = makeDefinitionList(entry.indicators.map((i) => [i.title, i.description] as KvTuple));
return makeTab(content, false);
return makeTab(content + general, false);
}

function makeTabBtn(name: string, tab: string) {
Expand All @@ -56,23 +84,22 @@ export function createDetailsBody(requestID: number, entry: WaterfallEntry, acco
html.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "http://www.w3.org/2000/xmlns/");

const tabsData = getKeys(requestID, entry);
const generalTab = makeTab(makeDefinitionList(tabsData.general));
const generalTab = makeGeneralTab(tabsData.general, entry);
const timingsTab = makeTab(makeDefinitionList(tabsData.timings, true));
const requestDl = makeDefinitionList(tabsData.request);

const requestHeadersDl = makeDefinitionList(tabsData.requestHeaders);
const responseDl = makeDefinitionList(tabsData.response);
const responseHeadersDl = makeDefinitionList(tabsData.responseHeaders);
const imgTab = makeImgTab(accordeonHeight, entry);
const indicatorTab = makeIndicatorTab(entry);
// const indicatorTab = makeIndicatorTab(entry);

body.innerHTML = `
<div class="wrapper">
<header class="type-${entry.requestType}">
<h3><strong>#${requestID}</strong> ${entry.name}</h3>
<nav class="tab-nav">
<ul>
${makeTabBtn("Issues & Info", indicatorTab)}
${makeTabBtn("General", generalTab)}
<li><button class="tab-button">Request</button></li>
<li><button class="tab-button">Response</button></li>
Expand All @@ -82,7 +109,6 @@ export function createDetailsBody(requestID: number, entry: WaterfallEntry, acco
</ul>
</nav>
</header>
${indicatorTab}
${generalTab}
<div class="tab">
<dl>
Expand Down
6 changes: 3 additions & 3 deletions src/ts/waterfall/row/svg-indicators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Creation of sub-components used in a resource request row
*/

import * as heuristics from "../../helpers/heuristics";
import { isInStatusCodeRange } from "../../helpers/heuristics";
import { WaterfallEntry } from "../../typing/waterfall";

/**
Expand Down Expand Up @@ -30,9 +30,9 @@ export function getMimeTypeIcon(entry: WaterfallEntry): Icon {
if (!!harEntry.response.redirectURL) {
const url = encodeURI(harEntry.response.redirectURL.split("?")[0] || "");
return makeIcon("err3xx", `${harEntry.response.status} response status: Redirect to ${url}...`);
} else if (heuristics.isInStatusCodeRange(harEntry, 400, 499)) {
} else if (isInStatusCodeRange(harEntry, 400, 499)) {
return makeIcon("err4xx", `${harEntry.response.status} response status: ${harEntry.response.statusText}`);
} else if (heuristics.isInStatusCodeRange(harEntry, 500, 599)) {
} else if (isInStatusCodeRange(harEntry, 500, 599)) {
return makeIcon("err5xx", `${harEntry.response.status} response status: ${harEntry.response.statusText}`);
} else if (harEntry.response.status === 204) {
return makeIcon("plain", "No content");
Expand Down

0 comments on commit 64612ed

Please sign in to comment.