Browse Source

Implement assessment result detail page

Lucas Mendonça 4 years ago
parent
commit
797a79908a

BIN
css/fonts/NimbusSanLConBold.ttf


BIN
css/fonts/NimbusSanLConRegular.ttf


BIN
css/fonts/texgyreheros-regular.otf


+ 71 - 0
css/ivprog-assessment.css

@@ -0,0 +1,71 @@
+@font-face {
+  font-family: 'NimbusSanLConRegular';
+  src: url(fonts/NimbusSanLConRegular.ttf) format('truetype');
+  font-weight: normal;
+  font-style: normal;
+}
+@font-face {
+  font-family: 'NimbusSanLConBold';
+  src: url(fonts/NimbusSanLConBold.ttf) format('truetype');
+  font-weight: bold;
+  font-style: normal;
+}
+@font-face {
+  font-family: 'TeXGyreHerosRegular';
+  src: url(fonts/texgyreheros-regular.otf) format('opentype');
+  font-weight: normal;
+  font-style: normal;
+}
+body {
+  font-family: 'TeXGyreHerosRegular';
+  background-color: #b9c7ca
+}
+.details-body > .details-header > h2 {margin-bottom: 0.5rem;}
+.details-body > .details-header > p {
+  padding-left: 1rem;
+  margin: 0;
+}
+table td { font-size: 14pt;}
+.stringdiff-delete, .stringdiff-insert {font-weight: bold;}
+.stringdiff-insert, .assessment-input-read {color: #22a222}
+.stringdiff-delete {
+  text-decoration: line-through;
+  color: #d02929
+}
+.details-body h3 {
+  margin-top: 0.5rem;
+  margin-bottom: 0.5rem;
+}
+.detaisl-div-table {
+  padding-left: 1rem;
+  margin-top: 1rem;
+}
+.assessment-output-table {
+  border-collapse: collapse;
+  border-style: hidden;
+}
+.assessment-output-table tr:nth-child(odd) {background: #CCC}
+.assessment-output-table tr:nth-child(even) {background: #f7f2c9}
+.assessment-output-table tr > th {
+  background-color: #000;
+  color: #fff;
+  font-family: 'NimbusSansLBoldCond';
+  font-weight: bold;
+  font-style: normal; 
+  letter-spacing: .05rem;
+}
+.assessment-output-table td, .assessment-output-table th {
+  border-left: 1px solid black;
+  padding: 0.5rem;
+  text-align: center;
+  max-width: 16rem;
+  font-family: 'NimbusSanLConRegular';
+  vertical-align: middle;
+  letter-spacing: .05rem;
+}
+.assessment-string-expected, .assessment-string-generated, .assessment-string-diff {text-align: left;}
+p.assessment-failed-execution {padding-left: 1rem;}
+.assessment-failed-case {color:#FF1212}
+.assessment-input-unread {color: #d02929}
+.assessment-number-result-failed, .assessment-bool-result-failed {color: #d02929}
+.assessment-number-result, .assessment-bool-result {color: #22a222}

+ 8 - 1
i18n/pt/ui.json

@@ -116,5 +116,12 @@
   "text_teacher_filter": "Filtro",
   "text_teacher_filter_active": "Ativado",
   "text_teacher_filter_help": "Ao ativar o filtro, as modificações do iVProg estarão bloqueadas.",
-  "text_join_assessment_outputs": " ; "
+  "text_join_assessment_outputs": " ; ",
+  "assessment-detail-time-label": "Duração",
+  "assessment-detail-grade-label": "Nota",
+  "assessment-detail-input-label": "Entradas",
+  "assessment-detail-output-label": "Saídas",
+  "assessment-detail-expected-label": "Esperadas",
+  "assessment-detail-generated-label": "Geradas",
+  "assessment-detail-result-label": "Resultado"
 }

+ 131 - 20
js/assessment/output_matching/assessment_result.js

@@ -1,24 +1,75 @@
 import StringDiff from "./../../util/string_diff";
+import { LocalizedStrings } from './../../services/localizedStringsService'
+
 export class OutputAssessmentResult {
 
-  // Status code - it is not grade related!
-  // 0 - Succesful execution
-  // 1 - failed execution
+  static get PAGE_TEMPLATE () {
+    return `<!DOCTYPE html>
+    <html>
+      <head>
+        <meta http-equiv='content-type' content='text/html; charset=UTF-8'>
+        <link rel='stylesheet' href='css/ivprog-assessment.css' type='text/css'/>
+      </head>
+    <body>
+      :assessment-result:
+    </body>
+    </html>`;
+  }
 
   static get DETAIL_TEMPLATE () {
-    return ` <div>
-    </div>
-    `;
+    return `<div class='details-body'>
+        <div class='details-header'>
+          <h2>:test-name:</h2>
+          <p>:time-label:: <span>:time:ms</span></p>
+          <p>:grade-label:: <span>:grade:%</span></p>
+        </div>
+        <div>
+          <h3>:input-label:</h3>
+          <ul>
+            <li>:input-list:</li>
+          </ul>
+        </div>
+        <div>
+          <h3>:output-label:</h3>
+          :output-result:
+        </div>
+      </div>`;
+  }
+
+  static get OUPUT_TABLE_TEMPLATE () {
+    return `<div class='detaisl-div-table'>
+        <table class='assessment-output-table'>
+          <tr>
+            <th>:expected-label:</th>
+            <th>:generated-label:</th>
+            <th>:result-label:</th>
+          </tr>
+          :results:
+        </table>
+      </div>`;
   }
 
   static get OUTPUT_TEMPLATE () {
-    return `<td class=":class-expected:">$0</td>
-            <td class=":class-generated:">$1</td>
-            <td class=":class-result:">$2</td>`;
+    return `<tr><td class=':class-expected:'>$0</td>
+            <td class=':class-generated:'>$1</td>
+            <td class=':class-result:'>$2</td></tr>`;
   }
 
-  constructor (status, result, store, time, error_msg = '') {
+  static get FAILED_TEMPLATE () {
+    return `<p class='assessment-failed-execution'><span class='assessment-failed-case'>✗</span>$0</p>`;
+  }
+
+  static get INPUT_INFO_TEMPLATE () {
+    return `<span class='$0'>$1</span>`;
+  }
+
+  // Status code - it is not grade related!
+  // 0 - Succesful execution
+  // 1 - failed execution
+  constructor (name, status, inputs, result, store, time, error_msg = '') {
+    this.name = name;
     this.status = status;
+    this.inputs = inputs;
     this.results = result;
     this.store = store;
     this.time = time;
@@ -26,26 +77,86 @@ export class OutputAssessmentResult {
   }
 
   get grade () {
+    if(this.results == null) {
+      return 0;
+    }
     return this.results.reduce((prev, val) => prev + val.grade, 0) / this.results.length;
   }
 
   prepareResults () {
-    
+    let template = OutputAssessmentResult.DETAIL_TEMPLATE;
+    const grade = (this.grade * 100).toFixed(2);
+    const time = this.time || "-";
+    template = template.replace(':test-name:', this.name);
+    template = template.replace(':time-label:', LocalizedStrings.getUI('assessment-detail-time-label'));
+    template = template.replace(':time:', time);
+    template = template.replace(':grade-label:', LocalizedStrings.getUI('assessment-detail-grade-label'));
+    template = template.replace(':grade:', grade);
+    const input_spans = this.prepareInputList(this.inputs);
+    template = template.replace(':input-label:', LocalizedStrings.getUI('assessment-detail-input-label'));
+    template = template.replace(':input-list:', input_spans);
+    template = template.replace(':output-label:', LocalizedStrings.getUI('assessment-detail-output-label'));
+    if(this.status == 0) {
+      const output_rows = this.results.map(result => {
+        if(result.type == "string") {
+          return this.formatString(result);
+        } else if (result.type == "number") {
+          return this.formatNumber(result);
+        } else {
+          return this.formatBool(result);
+        }
+      }, this);
+      template = template.replace(':output-result:', this.prepareOutputTable(output_rows));
+    } else {
+      let failed_text =  OutputAssessmentResult.FAILED_TEMPLATE;
+      failed_text = failed_text.replace("$0", this.error_msg);
+      template = template.replace(":output-result:", failed_text);
+    }
+    return template;
+  }
+
+  prepareInputList (input_list) {
+    const list = input_list.map(input => {
+      let template = OutputAssessmentResult.INPUT_INFO_TEMPLATE;
+      template = template.replace("$1", input.value);
+      if(input.read) {
+        template = template.replace("$0", "assessment-input-read");
+      } else {
+        template = template.replace("$0", "assessment-input-unread");
+      }
+      return template;
+    }, this);
+    return list.join(LocalizedStrings.getUI('text_join_assessment_outputs'));
   }
 
-  generateOutput (output) {
+  prepareOutputTable (output_rows) {
+    let template = OutputAssessmentResult.OUPUT_TABLE_TEMPLATE;
+    template = template.replace(':expected-label:', LocalizedStrings.getUI('assessment-detail-expected-label'));
+    template = template.replace(':generated-label:', LocalizedStrings.getUI('assessment-detail-generated-label'));
+    template = template.replace(':result-label:', LocalizedStrings.getUI('assessment-detail-result-label'));
+    template = template.replace(':results:', output_rows.join(''));
+    return template;
+  }
 
+  generateOutput () {
+    const assessment_result =  this.prepareResults();
+    let page = OutputAssessmentResult.PAGE_TEMPLATE;
+    page = page.replace(':assessment-result:', assessment_result);
+    page = page.replace(/(\r|\n|\t)/gm,'').replace(/> *</g, '><');
+    return page;
   }
 
   formatNumber (result) {
+    const result_class = result.grade == 1 ? 'assessment-number-result' : 'assessment-number-result-failed'; 
     let template = this.formatOutput('assessment-number-expected',
-      'assessment-number-generated', 'assessment-number-result', result);
+      'assessment-number-generated', result_class, result);
     return template
   }
 
   formatBool (result) {
+    const result_class = result.grade == 1 ? 'assessment-bool-result' : 'assessment-bool-result-failed';
     let template = this.formatOutput('assessment-bool-expected',
-      'assessment-bool-generated', 'assessment-bool-result', result);
+      'assessment-bool-generated', result_class, result);
     return template
   }
 
@@ -64,12 +175,12 @@ export class OutputAssessmentResult {
   formatString (result) {
     const expected_class = 'assessment-string-expected';
     const generated_class = 'assessment-string-generated';
-    const result_class = 'assessment-string-result';
+    //const result_class = 'assessment-string-result';
 
     let template = OutputAssessmentResult.OUTPUT_TEMPLATE;
     template = template.replace(":class-expected:", expected_class);
     template = template.replace(":class-generated:", generated_class);
-    template = template.replace(":class-result:", result_class);
+    //template = template.replace(":class-result:", result_class);
 
     const g_string = result.generated || "";
     const e_string = result.expected || "";
@@ -77,11 +188,13 @@ export class OutputAssessmentResult {
     template = template.replace("$1", result.generated);
     if(result.grade == 1) {
       template = template.replace("$2", "✓");
+      template = template.replace(":class-result:", "assessment-string-diff");
     } else {
       const diff = StringDiff(g_string, e_string);
       const diff_vec = diff.map(part => this.getDiffStringStyle(part[1], part[0]), this);
       const diff_string = diff_vec.reduce((prev, actual) => prev + actual, "");
-      template = template.replace("$2", diff_string);
+      template = template.replace("$2", "<span class='assessment-failed-case'>✗</span>" + diff_string);
+      template = template.replace(":class-result:", 'assessment-string-result');
     }
     return template;
   }
@@ -97,6 +210,4 @@ export class OutputAssessmentResult {
         return template.replace("$0", "stringdiff-equal").replace("$1", text);
     }
   }
-
-
-}
+}

+ 6 - 6
js/assessment/output_matching/output_matching.js

@@ -3,6 +3,7 @@ import { InputAssessment } from "../../util/input_assessment";
 import { OutputTest } from "../../util/outputTest";
 import { Config } from "../../util/config";
 import { levenshteinDistance } from "../../util/utils";
+import { OutputAssessmentResult } from './assessment_result';
 import * as TypeParser from "./../../typeSystem/parsers";
 import * as LocalizedStringsService from "../../services/localizedStringsService";
 import * as OutputResult from "./output_result";
@@ -46,8 +47,8 @@ export class OutputMatching {
     return this.program.interpretAST().then( sto => {
       const final_time = Date.now() - start_time;
       if(input.isInputAvailable()) {
-        return {status:1, grade:0, error_msg: refThis.getErrorMessage('test_case_few_reads', this.name+1),
-          store:sto, time: final_time};
+        return new OutputAssessmentResult(this.name, 1, input.input_list,
+          null, sto, final_time, refThis.getErrorMessage('test_case_few_reads', this.name+1))
       }
       const result = gen_output.list.map((g_out, i) => {
         if(i >= this.expected_output.length) {
@@ -61,11 +62,10 @@ export class OutputMatching {
           result.push(new OutputResult.OutputMatchResult(e_out, null, 0, this.getPotentialOutputType(e_out)));
         }
       }
-      const grade = result.reduce((prev, val) => prev + val.grade, 0) / result.length;
-      return {status: 0, grade: grade, error_msg: "", store: sto, time: final_time, results: result};
+      return new OutputAssessmentResult(this.name, 0,  input.input_list, result, sto, final_time);
     }).catch(error => {
-      return {status:1, grade:0, error_msg: refThis.getErrorMessage('test_case_failed_exception', this.name + 1, error.message),
-        store:null, time: null};
+      return new OutputAssessmentResult(this.name, 1,  input.input_list, null, null,
+        null, refThis.getErrorMessage('test_case_failed_exception', this.name + 1, error.message))
     });
   }
 

+ 3 - 3
js/runner.js

@@ -19,15 +19,15 @@ const ivprogLexer = LanguageService.getCurrentLexer();
 //     i++;
 // }
 // const anaSin = new IVProgParser(input, ivprogLexer);
-const editor = new JsonEditor('#json-renderer', {});
+const editor = new window.JsonEditor('#json-renderer', {});
 const domConsole = new DOMConsole("#console", true);
 domConsole.hide();
 // proc.interpretAST().then( sto => {
 //   console.log(sto.applyStore('a'));
 // }).catch(e => console.log(e));
 try {
-  $('#btn').click( () => {
-    const input = $('#input').val();
+  window.$('#btn').click( () => {
+    const input = window.$('#input').val();
     const analiser = new IVProgParser(input, ivprogLexer);
     try {
       const data = analiser.parseTree();

+ 135 - 0
js/util/base64.js

@@ -0,0 +1,135 @@
+/**
+*
+*  Base64 encode / decode
+*  Source: http://www.webtoolkit.info/
+*  Modified by: @lucascalion - 24/07/2019
+*
+**/
+
+const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+  
+export function encode (input) {
+      let output = "";
+      let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+      let i = 0;
+  
+      input = _utf8_encode(input);
+  
+      while (i < input.length) {
+  
+          chr1 = input.charCodeAt(i++);
+          chr2 = input.charCodeAt(i++);
+          chr3 = input.charCodeAt(i++);
+  
+          enc1 = chr1 >> 2;
+          enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+          enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+          enc4 = chr3 & 63;
+  
+          if (isNaN(chr2)) {
+              enc3 = enc4 = 64;
+          } else if (isNaN(chr3)) {
+              enc4 = 64;
+          }
+  
+          output = output +
+            _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
+            _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
+  
+      }
+  
+      return output;
+}
+
+export function decode (input) {
+      let output = "";
+      let chr1, chr2, chr3;
+      let enc1, enc2, enc3, enc4;
+      let i = 0;
+  
+      input = input.replace(/[^A-Za-z0-9+/=]/g, "");
+  
+      while (i < input.length) {
+  
+          enc1 = _keyStr.indexOf(input.charAt(i++));
+          enc2 = _keyStr.indexOf(input.charAt(i++));
+          enc3 = _keyStr.indexOf(input.charAt(i++));
+          enc4 = _keyStr.indexOf(input.charAt(i++));
+  
+          chr1 = (enc1 << 2) | (enc2 >> 4);
+          chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+          chr3 = ((enc3 & 3) << 6) | enc4;
+  
+          output = output + String.fromCharCode(chr1);
+  
+          if (enc3 != 64) {
+              output = output + String.fromCharCode(chr2);
+          }
+          if (enc4 != 64) {
+              output = output + String.fromCharCode(chr3);
+          }
+  
+      }
+  
+      output = _utf8_decode(output);
+  
+      return output;
+  
+}
+
+function  _utf8_encode (string) {
+      string = string.replace(/\r\n/g,"\n");
+      let utftext = "";
+  
+      for (let n = 0; n < string.length; n++) {
+  
+          const c = string.charCodeAt(n);
+  
+          if (c < 128) {
+              utftext += String.fromCharCode(c);
+          }
+          else if((c > 127) && (c < 2048)) {
+              utftext += String.fromCharCode((c >> 6) | 192);
+              utftext += String.fromCharCode((c & 63) | 128);
+          }
+          else {
+              utftext += String.fromCharCode((c >> 12) | 224);
+              utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+              utftext += String.fromCharCode((c & 63) | 128);
+          }
+  
+      }
+  
+      return utftext;
+}
+
+function _utf8_decode (utftext) {
+      let string = "";
+      let i = 0;
+      let c, c2, c3;
+      c = c2 = c3 = 0;
+  
+      while ( i < utftext.length ) {
+  
+          c = utftext.charCodeAt(i);
+  
+          if (c < 128) {
+              string += String.fromCharCode(c);
+              i++;
+          }
+          else if((c > 191) && (c < 224)) {
+              c2 = utftext.charCodeAt(i+1);
+              string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+              i += 2;
+          }
+          else {
+              c2 = utftext.charCodeAt(i+1);
+              c3 = utftext.charCodeAt(i+2);
+              string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+              i += 3;
+          }
+  
+      }
+  
+      return string;
+}

+ 0 - 48
templates/assessment_details.html

@@ -1,48 +0,0 @@
-<html>
-  <head>
-    <style>
-      .stringdiff-insert {
-        color: green
-      }
-      .stringdiff-delete {
-        text-decoration: line-through;
-        color: red;
-      }
-    </style>
-  </head>
-<body>
-  <div>
-    <div>
-      <h2>Teste 1</h2>
-      <p>Elapsed time: <span>15ms</span></p>
-      <p>Grade: <span>98.60%</span></p>
-    </div>
-    <div>
-      <h3>Input</h3>
-      <ul>
-        <li>1 ; 2 ; 3 ; 4 ; 5</li>
-      </ul>
-    </div>
-    <div>
-      <h3>Output</h3>
-      <table>
-        <tr>
-          <th>Expected</th>
-          <th>Generated</th>
-          <th>Result</th>
-        </tr>
-        <tr>
-          <td>4</td>
-          <td>4</td>
-          <td>✓</td>
-        </tr>
-        <tr>
-          <td>Um texto</td>
-          <td>Um testo</td>
-          <td><span class='stringdiff-equal'>Um te</span><span class='stringdiff-delete'>s</span><span class='stringdiff-insert'>x</span><span class='stringdiff-equal'>to</span></td>
-          </tr>
-      </table>
-    </div>
-  </div>
-</body>
-</html>

+ 1 - 0
webpack.config.js

@@ -59,6 +59,7 @@ module.exports = {
         {from:'js/iassign-integration-functions.js', to:path.resolve(__dirname, 'build/js')},
         {from:"css/ivprog-visual-1.0.css", to:path.resolve(__dirname, 'build/css')},
         {from:"css/ivprog-term.css", to:path.resolve(__dirname, 'build/css')},
+        {from:"css/ivprog-assessment.css", to:path.resolve(__dirname, 'build/css')},
         {from:"css/ivprog-editor.css", to:path.resolve(__dirname, 'build/css')},
         {from:"css/roboto.css", to:path.resolve(__dirname, 'build/css')},
         {from:"css/fonts/", to:path.resolve(__dirname, 'build/css/fonts')},