Skip to content

Commit

Permalink
EntryDiff scoring (#4)
Browse files Browse the repository at this point in the history
* Adds EntryDiff.score calculation

* Update .travis.yml

* Updates issues

* Fixes http tx dict product

* Revert "Update .travis.yml"

This reverts commit a64a6e1.

* Adds UI

* Adds labels coloring

* Adds decimalAdjust

* Moves score to Comparison

* Adds soft diffs into scoring result

* Removes TODO

Co-authored-by: Andrey Pokhilko <[email protected]>
  • Loading branch information
pythad and undera authored Sep 14, 2021
1 parent 4ab40b2 commit 04ce196
Show file tree
Hide file tree
Showing 13 changed files with 226 additions and 36 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ENV SPA_LOCATION=/app/harnic-spa
# install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
RUN python -m spacy download en_core_web_sm

# install backend
COPY harnic harnic
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,5 @@ docker rm harnic
- Add contextual context-wrap (*Hard*)
- Add request postData
- Handle soft diffs missing or added as soft (*Under question*)
- Http tx scores to 0.5/0.5
- Add score of entry and body to UI
36 changes: 29 additions & 7 deletions harnic-spa/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,31 @@
background-color: rgba(0, 0, 0, 0.25)
}
*/
.ui.table td.warning, .ui.table tr.warning {
background: rgba(246, 157, 80, 0.2) !important;
.ui.table td.warning, .ui.table tr.warning, .label.warning {
background: rgba(246, 157, 80, 0.2) !important;
}

.ui.table td.positive, .ui.table tr.positive {
background: rgba(70, 149, 74, 0.2) !important;
.ui.table td.positive, .ui.table tr.positive, .label.positive {
background: rgba(70, 149, 74, 0.2) !important;
}

.ui.table td.negative, .ui.table tr.negative {
background: rgba(201, 60, 55, 0.2) !important;
.ui.table td.negative, .ui.table tr.negative, .label.negative {
background: rgba(201, 60, 55, 0.2) !important;
}

.label.warning {
background: rgba(246, 157, 80, 0.55) !important;
color: white !important;
}

.label.positive {
background: rgba(70, 149, 74, 0.55) !important;
color: white !important;
}

.label.negative {
background: rgba(201, 60, 55, 0.55) !important;
color: white !important;
}

/* Some basic formatting */
Expand Down Expand Up @@ -180,14 +195,21 @@ code {
}

.reordering-icon {
float: right;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg);
}

.entry-diff.meta-label {
float: right;
}

.entry-diff.meta-label.reordering {
padding-right: 1px !important;
}

.truncated {
text-align: center;
font-family: 'Lato', auto;
Expand Down
59 changes: 52 additions & 7 deletions harnic-spa/src/components/DiffRecordRow.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState } from "react";
import { Tab, Table, Icon, Label } from "semantic-ui-react";
import { Tab, Table, Icon, Label, Menu } from "semantic-ui-react";

import RequestData from "./RequestData.js";
import ResponseData from "./ResponseData.js";
import { truncate } from ".././utils.js";
import { truncate, getScoreLabelClass, decimalAdjust } from ".././utils.js";

const DiffRecordRow = ({ record }) => {
const [isOpen, setIsOpen] = useState(false);
Expand Down Expand Up @@ -31,7 +31,16 @@ const DiffRecordRow = ({ record }) => {
const aPanes = record.pair.a
? [
{
menuItem: "Request",
menuItem: (
<Menu.Item key='request'>
Request
{record.tag === "diff" &&
<Label className={getScoreLabelClass(record.diff.score.by_http_tx_type.request)} floating>
{decimalAdjust('floor', record.diff.score.by_http_tx_type.request * 100, -1)}%
</Label>
}
</Menu.Item>
),
render: () => (
<Tab.Pane>
<RequestData
Expand All @@ -42,7 +51,16 @@ const DiffRecordRow = ({ record }) => {
),
},
{
menuItem: "Response",
menuItem: (
<Menu.Item key='response'>
Response
{record.tag === "diff" &&
<Label className={getScoreLabelClass(record.diff.score.by_http_tx_type.response)} floating>
{decimalAdjust('floor', record.diff.score.by_http_tx_type.response * 100, -1)}%
</Label>
}
</Menu.Item>
),
render: () => (
<Tab.Pane>
<ResponseData
Expand All @@ -51,6 +69,7 @@ const DiffRecordRow = ({ record }) => {
response={record.pair.a.response}
diff={record.diff && record.diff.comparisons.response}
initialEntry={true}
score={record.diff && record.diff.score}
/>
</Tab.Pane>
),
Expand All @@ -69,7 +88,16 @@ const DiffRecordRow = ({ record }) => {
const bPanes = record.pair.b
? [
{
menuItem: "Request",
menuItem: (
<Menu.Item key='request'>
Request
{record.tag === "diff" &&
<Label className={getScoreLabelClass(record.diff.score.by_http_tx_type.request)} floating>
{decimalAdjust('floor', record.diff.score.by_http_tx_type.request * 100, -1)}%
</Label>
}
</Menu.Item>
),
render: () => (
<Tab.Pane>
<RequestData
Expand All @@ -80,7 +108,16 @@ const DiffRecordRow = ({ record }) => {
),
},
{
menuItem: "Response",
menuItem: (
<Menu.Item key='response'>
Response
{record.tag === "diff" &&
<Label className={getScoreLabelClass(record.diff.score.by_http_tx_type.response)} floating>
{decimalAdjust('floor', record.diff.score.by_http_tx_type.response * 100, -1)}%
</Label>
}
</Menu.Item>
),
render: () => (
<Tab.Pane>
<ResponseData
Expand All @@ -89,6 +126,7 @@ const DiffRecordRow = ({ record }) => {
response={record.pair.b.response}
diff={record.diff && record.diff.comparisons.response}
initialEntry={false}
score={record.diff && record.diff.score}
/>
</Tab.Pane>
),
Expand Down Expand Up @@ -124,8 +162,15 @@ const DiffRecordRow = ({ record }) => {
</>
)}
{record.is_reordering && (
<Icon name="exchange" className="reordering-icon" />
<Label className="entry-diff meta-label reordering">
<Icon name="exchange" className="reordering-icon" />
</Label>
)}
{record.tag === "diff" &&
<Label className="entry-diff meta-label">
{decimalAdjust('floor', record.diff.score.final * 100, -1)}%
</Label>
}
</Table.Cell>
<Table.Cell>
{record.pair.b && (
Expand Down
8 changes: 7 additions & 1 deletion harnic-spa/src/components/ResponseData.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { DateTime } from "luxon";
import regexifyString from "regexify-string";

import ModalScrollingContent from "./ModalScrollingContent.js";
import { truncate, calculateDiffClass } from ".././utils.js";
import { truncate, calculateDiffClass, getScoreLabelClass, decimalAdjust } from ".././utils.js";

const ContentText = ({ value, request }) => {
if (value === null) {
Expand Down Expand Up @@ -69,6 +69,7 @@ const ResponseData = ({
response,
diff,
initialEntry,
score,
}) => {
const cmpIdx = initialEntry ? 0 : 1;

Expand Down Expand Up @@ -274,6 +275,11 @@ const ResponseData = ({
<List.Item>
<div>
<b>Content:</b>
{score &&
<Label className={getScoreLabelClass(score.full.response.content)}>
{decimalAdjust('floor', score.full.response.content * 100, -1)}%
</Label>
}
</div>
<List>
{Object.entries(response.content).map(([key, value]) => {
Expand Down
31 changes: 30 additions & 1 deletion harnic-spa/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,34 @@ const calculateDiffClass = (diff, criteria, key) => {
return keyClass;
};

export { truncate, calculateDiffClass };
const getScoreLabelClass = (score) => {
if (score < 0.25) {
return 'negative';
} else if (score == 1) {
return 'positive';
} else {
return 'warning';
}
};

const decimalAdjust = (type, value, exp) => {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// Shift
value = value.toString().split('e');
value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
// Shift back
value = value.toString().split('e');
return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
}

export { truncate, calculateDiffClass, getScoreLabelClass, decimalAdjust };

55 changes: 41 additions & 14 deletions harnic/compare/entry.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from functools import partial

from harnic.compare.utils import dict_compare, qp_compare, scalars_compare, content_compare
from harnic.constants import SOFT_HEADER_KEYS
from harnic.compare.utils import content_compare, dict_compare, dict_product, qp_compare, scalars_compare
from harnic.constants import SCORE_COEFS, SCORE_HTTP_TX_TYPE_COEFS, SOFT_HEADER_KEYS

headers_compare = partial(dict_compare, exceptions=SOFT_HEADER_KEYS, exculde_values=True)

Expand All @@ -13,25 +13,52 @@ def __init__(self, a, b):
self.a = a
self.b = b
self.equal = None
self.score = {}
self.comparisons = self._get_diff()

def _get_diff(self):
# method and url are not handled here as long as they are part of the entry hash
comparisons = {'request': {}, 'response': {}}

comparisons['request']['bodySize'] = scalars_compare(self.a.request['bodySize'],
self.b.request['bodySize'])
comparisons['request']['query_params'] = qp_compare(self.a.request['url'].query_params,
self.b.request['url'].query_params)
comparisons['request']['headers'] = headers_compare(self.a.request['headers'],
self.b.request['headers'])
diff_score = {
'request': {},
'response': {},
}

comparisons['response']['status'] = scalars_compare(self.a.response['status'],
self.b.response['status'])
comparisons['response']['headers'] = headers_compare(self.a.response['headers'],
self.b.response['headers'])
comparisons['response']['content'] = content_compare(self.a.response,
self.b.response)
cmp = scalars_compare(self.a.request['url'].url, self.b.request['url'].url)
diff_score['request']['url'] = cmp.score

cmp = scalars_compare(self.a.request['bodySize'], self.b.request['bodySize'])
comparisons['request']['bodySize'] = cmp

cmp = qp_compare(self.a.request['url'].query_params, self.b.request['url'].query_params)
comparisons['request']['query_params'], diff_score['request']['query_params'] = cmp, cmp.score

cmp = headers_compare(self.a.request['headers'], self.b.request['headers'])
comparisons['request']['headers'], diff_score['request']['headers'] = cmp, cmp.score

# TODO: implement postData cmp
diff_score['request']['postData'] = 1 # Treat same for now

cmp = scalars_compare(self.a.response['status'], self.b.response['status'])
comparisons['response']['status'], diff_score['response']['status'] = cmp, cmp.score

cmp = headers_compare(self.a.response['headers'], self.b.response['headers'])
comparisons['response']['headers'], diff_score['response']['headers'] = cmp, cmp.score

cmp = content_compare(self.a.response, self.b.response)
comparisons['response']['content'], diff_score['response']['content'] = cmp, cmp.score

self.equal = all(all(cmp.equal for cmp in criteria.values()) for criteria in comparisons.values())

self.score['full'] = diff_score
diff_score_with_coefs = {
'request': sum(dict_product(diff_score['request'], SCORE_COEFS['request']).values()),
'response': sum(dict_product(diff_score['response'], SCORE_COEFS['response']).values()),
}
self.score['by_http_tx_type'] = diff_score_with_coefs
diff_score_with_coefs = dict_product(diff_score_with_coefs, SCORE_HTTP_TX_TYPE_COEFS)
final_score = sum(diff_score_with_coefs.values())
self.score['final'] = final_score

return comparisons
11 changes: 8 additions & 3 deletions harnic/compare/matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def _build_files_diff(opcodes, file1, file2):
PermTag.DIFF: 0,
PermTag.INSERT: 0,
PermTag.DELETE: 0,
'_diff_scores_sum': 0,
}
perms_total = _calculate_permutations_total_number(opcodes)
with tqdm(total=perms_total, desc='Constructing diff records') as pbar:
Expand All @@ -77,6 +78,8 @@ def _build_files_diff(opcodes, file1, file2):
dr = DiffRecord(pair, entry_diff, tag_selector)
records.append(dr)
stats[tag_selector] += 1
if tag_selector == PermTag.DIFF:
stats['_diff_scores_sum'] += entry_diff.score['final']
pbar.update()
elif tag == 'replace':
for i in range(i1, i2):
Expand All @@ -101,15 +104,15 @@ def _build_files_diff(opcodes, file1, file2):
records.append(dr)
stats[PermTag.INSERT] += 1
pbar.update()
stats['ratio'] = 2.0 * stats[PermTag.EQUAL] / (len(file1) + len(file2))

stats['ratio'] = 2.0 * (stats[PermTag.EQUAL] + stats['_diff_scores_sum']) / (len(file1) + len(file2))

reorders = _calculate_reorders(records)
reorders_stats = _calculate_reorders_stats(reorders, stats, (file1, file2))
stats = {
'with_reorders': reorders_stats,
'strict_order': stats,
}

return records, reorders, stats


Expand Down Expand Up @@ -162,7 +165,9 @@ def _calculate_reorders_stats(reorders, stats, files):
for reorder in reorders:
tag_selector = PermTag.EQUAL if reorder['entry_diff'].equal else PermTag.DIFF
stats[tag_selector] += 1
stats['ratio'] = 2.0 * stats[PermTag.EQUAL] / (len(file1) + len(file2))
if tag_selector == PermTag.DIFF:
stats['_diff_scores_sum'] += reorder['entry_diff'].score['final']
stats['ratio'] = 2.0 * (stats[PermTag.EQUAL] + stats['_diff_scores_sum']) / (len(file1) + len(file2))

return stats

Expand Down
1 change: 1 addition & 0 deletions harnic/compare/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ComparisonSchema(Schema):
class EntryDiffSchema(Schema):
equal = fields.Bool()
comparisons = fields.Dict(values=fields.Dict(values=fields.Nested('ComparisonSchema')))
score = fields.Dict()


class PairSchema(Schema):
Expand Down
Loading

0 comments on commit 04ce196

Please sign in to comment.