Remove old result package

This commit is contained in:
Patrick Jentsch 2020-07-15 14:52:41 +02:00
parent 0714070a9b
commit ac3b20d304
15 changed files with 45 additions and 858 deletions

View File

@ -59,7 +59,4 @@ def create_app(config_name):
from .services import services as services_blueprint from .services import services as services_blueprint
app.register_blueprint(services_blueprint, url_prefix='/services') app.register_blueprint(services_blueprint, url_prefix='/services')
from .results import results as results_blueprint
app.register_blueprint(results_blueprint, url_prefix='/results')
return app return app

View File

@ -133,8 +133,6 @@ class User(UserMixin, db.Model):
cascade='save-update, merge, delete') cascade='save-update, merge, delete')
jobs = db.relationship('Job', backref='creator', lazy='dynamic', jobs = db.relationship('Job', backref='creator', lazy='dynamic',
cascade='save-update, merge, delete') cascade='save-update, merge, delete')
results = db.relationship('Result', backref='creator', lazy='dynamic',
cascade='save-update, merge, delete')
query_results = db.relationship('QueryResult', query_results = db.relationship('QueryResult',
backref='creator', backref='creator',
cascade='save-update, merge, delete', cascade='save-update, merge, delete',
@ -660,59 +658,6 @@ class QueryResult(db.Model):
return '<QueryResult {}>'.format(self.title) return '<QueryResult {}>'.format(self.title)
class Result(db.Model):
'''
Class to define a result set of one query.
'''
__tablename__ = 'results'
id = db.Column(db.Integer, primary_key=True)
# Foreign keys
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
# Relationships'
corpus_metadata = db.Column(db.JSON())
file = db.relationship('ResultFile', backref='result', lazy='dynamic',
cascade='save-update, merge, delete')
def delete(self):
result_file_path = os.path.join(current_app.config['NOPAQUE_STORAGE'],
self.file[0].dir)
try:
os.remove(result_file_path)
except OSError:
pass
db.session.delete(self)
def __repr__(self):
'''
String representation of the Result. For human readability.
'''
return '<Result ID: {result_id}>'.format(result_id=self.id)
class ResultFile(db.Model):
'''
Class to define a ResultFile
'''
__tablename__ = 'result_files'
# Primary key
id = db.Column(db.Integer, primary_key=True)
# Foreign keys
result_id = db.Column(db.Integer, db.ForeignKey('results.id'))
# Fields
filename = db.Column(db.String(255))
dir = db.Column(db.String(255))
def delete(self):
db.session.delete(self)
db.session.commit()
def __repr__(self):
'''
String representation of the ResultFile. For human readability.
'''
return '<ResultFile {result_file_name}>'.format(result_file_name=self.filename) # noqa
''' '''
' Flask-Login is told to use the applications custom anonymous user by setting ' Flask-Login is told to use the applications custom anonymous user by setting
' its class in the login_manager.anonymous_user attribute. ' its class in the login_manager.anonymous_user attribute.

View File

@ -1,5 +0,0 @@
from flask import Blueprint
results = Blueprint('results', __name__)
from . import views # noqa

View File

@ -1,18 +0,0 @@
from flask_wtf import FlaskForm
from werkzeug.utils import secure_filename
from wtforms import FileField, SubmitField, ValidationError
from wtforms.validators import DataRequired
class ImportResultsForm(FlaskForm):
'''
Form used to import one result json file.
'''
file = FileField('File', validators=[DataRequired()])
submit = SubmitField()
def validate_file(self, field):
if not field.data.filename.lower().endswith('.json'):
raise ValidationError('File does not have an approved extension: '
'.json')
field.data.filename = secure_filename(field.data.filename)

View File

@ -1,13 +0,0 @@
from .. import db
from ..decorators import background
from ..models import Result
@background
def delete_result(result_id, *args, **kwargs):
with kwargs['app'].app_context():
result = Result.query.get(result_id)
if result is None:
raise Exception('Result {} not found'.format(result_id))
result.delete() # cascades down and also deletes ResultFile
db.session.commit()

View File

@ -1,186 +0,0 @@
from . import results
from . import tasks
from .. import db
from ..corpora.forms import DisplayOptionsForm, InspectDisplayOptionsForm
from ..models import Result, ResultFile, User
from .forms import ImportResultsForm
from datetime import datetime
from flask import (abort, render_template, current_app, request, redirect,
flash, url_for, make_response, send_from_directory)
from flask_login import current_user, login_required
import json
import os
from .. import logger
from jsonschema import validate
@results.route('/import', methods=['GET', 'POST'])
@login_required
def result_import():
'''
View to import one json result file. Uses the ImportReultFileForm.
'''
import_results_form = ImportResultsForm(prefix='add-result-file-form')
if import_results_form.is_submitted():
if not import_results_form.validate():
return make_response(import_results_form.errors, 400)
# Save the file
# result creation only happens on file save to avoid creating a result
# object in the db everytime by just visiting the import_results page
result = Result(user_id=current_user.id)
db.session.add(result)
db.session.commit()
if not (result.creator == current_user
or current_user.is_administrator()):
abort(403)
# create paths to save the uploaded json file
dir = os.path.join(str(result.user_id),
'results',
'corpus_analysis_results',
str(result.id))
abs_dir = os.path.join(current_app.config['NOPAQUE_STORAGE'], dir)
abs_file_path = os.path.join(abs_dir,
import_results_form.file.data.filename)
os.makedirs(abs_dir)
# save the json file
import_results_form.file.data.save(abs_file_path)
# Create ResultFile db entry
result_file = ResultFile(result_id=result.id,
dir=dir,
filename=import_results_form.file.data.filename) # noqa
db.session.add(result_file)
db.session.commit()
# reads uploaded json file
with open(abs_file_path, 'r') as f:
corpus_metadata = json.load(f)
try:
# open json schema to validate against it
with open('app/static/json_schema/nopaque_cqi_py_results_schema.json', # noqa
'r') as s:
schema = json.load(s)
# validate if imported json is actually a json result file
validate(instance=corpus_metadata, schema=schema)
# if validated continue
# delete matches and cpos_lookup from read json file
del corpus_metadata['matches']
del corpus_metadata['cpos_lookup']
# save metadate directly as json into one field
result.corpus_metadata = corpus_metadata
flash('Result file added!', 'result')
db.session.commit()
return make_response(
{'redirect_url': url_for('results.results_overview')},
201)
except Exception as e:
# this runs if validation fails
flash('Uploaded file was not a valid result JSON!', 'result')
# deletes before created Result and ResultFile db entries
tasks.delete_result(result.id)
return make_response(
{'redirect_url': url_for('results.result_import')},
201)
return render_template('results/result_import.html.j2',
import_results_form=import_results_form,
title='Add corpus file')
@results.route('/')
@login_required
def results_overview():
'''
Shows an overview of imported results.
'''
# get all results of current user
results = User.query.get(current_user.id).results
def __p_time(time_str):
# helper to convert the datetime into a nice readable string
return datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S.%f')
# convert results into a list of dicts to add the measier to list.js in
# the template
results = [dict(query=r.corpus_metadata['query'],
match_count=r.corpus_metadata['match_count'],
corpus_name=r.corpus_metadata['corpus_name'],
corpus_creation_date=__p_time(r.corpus_metadata['corpus_creation_date']), # noqa
corpus_analysis_date=__p_time(r.corpus_metadata['corpus_analysis_date']), # noqa
corpus_type=r.corpus_metadata['corpus_type'],
file_id=r.file[0].id,
id=r.id)
for r in results]
return render_template('results/results.html.j2',
title='Imported Results',
# table=table,
results=results)
@results.route('/<int:result_id>/details')
@login_required
def result_details(result_id):
'''
View to show metadate and details about on imported result file.
'''
result = Result.query.get_or_404(result_id)
if not (result.creator == current_user or current_user.is_administrator()):
abort(403)
return render_template('results/result_details.html.j2',
result=result,
title='Result Details')
@results.route('/<int:result_id>/inspect')
@login_required
def result_inspect(result_id):
'''
View to inspect one imported result file in a corpus analysis like interface
'''
display_options_form = DisplayOptionsForm(
prefix='display-options-form',
result_context=request.args.get('context', 20),
results_per_page=request.args.get('results_per_page', 30))
inspect_display_options_form = InspectDisplayOptionsForm(
prefix='inspect-display-options-form')
result = Result.query.get_or_404(result_id)
result_file_path = os.path.join(current_app.config['NOPAQUE_STORAGE'],
result.file[0].dir,
result.file[0].filename)
with open(result_file_path, 'r') as result_json:
result_json = json.load(result_json)
if not (result.creator == current_user or current_user.is_administrator()):
abort(403)
return render_template('results/result_inspect.html.j2',
display_options_form=display_options_form,
inspect_display_options_form=inspect_display_options_form,
result=result,
result_json=result_json,
title='Result Insepct')
@results.route('/<int:result_id>/delete')
@login_required
def result_delete(result_id):
result = Result.query.get_or_404(result_id)
if not result.id == result_id:
abort(404)
if not (result.creator == current_user
or current_user.is_administrator()):
abort(403)
tasks.delete_result(result_id)
flash('Result deleted!')
return redirect(url_for('results.results_overview'))
@results.route('/<int:result_id>/file/<int:result_file_id>/download')
@login_required
def result_download(result_id, result_file_id):
result_file = ResultFile.query.get_or_404(result_file_id)
if not result_file.result_id == result_id:
abort(404)
if not (result_file.result.creator == current_user
or current_user.is_administrator()):
abort(403)
dir = os.path.join(current_app.config['NOPAQUE_STORAGE'],
result_file.dir)
return send_from_directory(as_attachment=True,
directory=dir,
filename=result_file.filename)

View File

@ -1,6 +1,6 @@
class RessourceList extends List { class RessourceList extends List {
constructor(idOrElement, subscriberList, type, options={}) { constructor(idOrElement, subscriberList, type, options={}) {
if (!["Corpus", "CorpusFile", "Job", "JobInput", "QueryResult", "User", "result"].includes(type)) { if (!["Corpus", "CorpusFile", "Job", "JobInput", "QueryResult", "User"].includes(type)) {
console.error("Unknown Type!"); console.error("Unknown Type!");
return; return;
} }
@ -124,16 +124,6 @@ RessourceList.dataMapper = {
link: `/query_results/${query_result.id}`, link: `/query_results/${query_result.id}`,
query: query_result.query_metadata.query, query: query_result.query_metadata.query,
title: query_result.title}), title: query_result.title}),
result: result => ({query: result.query,
match_count: result.match_count,
corpus_name: result.corpus_name,
corpus_creation_date: result.corpus_creation_date,
corpus_analysis_date: result.corpus_analysis_date,
corpus_type : result.corpus_type,
"details-link": `${result.id}/details`,
"inspect-link": `${result.id}/inspect`,
"download-link": `${result.id}/file/${result.file_id}/download`,
"delete-modal": `delete-result-${result.id}-modal`}),
// Mapping for user entities shown in admin table // Mapping for user entities shown in admin table
User: user => ({username: user.username, User: user => ({username: user.username,
email: user.email, email: user.email,
@ -277,56 +267,6 @@ RessourceList.options = {
{data: ["id"]}, {data: ["id"]},
{name: "inspect-link", attr: "href"}, {name: "inspect-link", attr: "href"},
{name: "link", attr: "href"}]}, {name: "link", attr: "href"}]},
// Result (imported from corpus analysis) entity blueprint setting html
// strucuture per entity per row
// Link classes have to correspond with Links defined in the Mapping process
result: {item: `<tr>
<td class="query"></td>
<td class="match_count"></td>
<td class="corpus_name"></td>
<td class="corpus_creation_date"></td>
<td class="corpus_analysis_date"></td>
<td class="corpus_type"></td>
<td class="actions right-align">
<a class="btn-floating tooltipped details-link
waves-effect waves-light"
data-position="top"
data-tooltip="Metadata Info">
<i class="material-icons">info_outline</i>
</a>
<a class="btn-floating tooltipped inspect-link
waves-effect waves-light"
data-position="top"
data-tooltip="View Results">
<i class="material-icons">search</i>
</a>
<a class="btn-floating tooltipped download-link
waves-effect waves-light"
data-position="top"
data-tooltip="Download">
<i class="material-icons">file_download</i>
</a>
<a class="btn-floating tooltipped red delete-modal
waves-effect waves-light modal-trigger"
data-position="top"
data-tooltip="Delete">
<i class="material-icons">delete</i>
</a>
</td>
</tr>`,
// Result Value Names per column. Have to correspond with keys from the
// Mapping step above.
valueNames: ["query",
"match_count",
"corpus_name",
"corpus_creation_date",
"corpus_analysis_date",
"corpus_type",
{name: "details-link", attr: "href"},
{name: "inspect-link", attr: "href"},
{name: "download-link", attr: "href"},
{name: "delete-modal", attr: "data-target"}]
},
// User entity blueprint setting html strucuture per entity per row // User entity blueprint setting html strucuture per entity per row
// Link classes have to correspond with Links defined in the Mapping process // Link classes have to correspond with Links defined in the Mapping process
User: {item: `<tr> User: {item: `<tr>

View File

@ -28,8 +28,6 @@
<ul class="pagination"></ul> <ul class="pagination"></ul>
</div> </div>
<div class="card-action right-align"> <div class="card-action right-align">
<a class="waves-effect waves-light btn" href="{{ url_for('results.result_import') }}">Import Results<i class="material-icons right">file_upload</i></a>
<a class="waves-effect waves-light btn" href="{{ url_for('results.results_overview') }}">Show Imported Results<i class="material-icons right">folder</i></a>
<a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a> <a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
</div> </div>
</div> </div>

View File

@ -1,119 +0,0 @@
{% extends "nopaque.html.j2" %}
{% block page_content %}
<div class="col s12">
<p>Below the metadata for the results from the Corpus
<i>{{ result.corpus_metadata.corpus_name }}</i> generated with the query
<i>{{ result.corpus_metadata.query }}</i> are shown.
</p>
<p>{{ texts_metadata }}</p>
</div>
<div class="col s12">
<div class="card">
<div class="card-content" id="results">
<table class="responsive-table highlight">
<thead>
<tr>
<th>Metadata Description</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for pair in result.corpus_metadata|dictsort %}
<tr>
<td>{{ pair[0] }}</td>
{% if pair[0] == 'corpus_all_texts'
or pair[0] == 'text_lookup' %}
<td>
<table>
{% for key, value in pair[1].items() %}
<tr style="border-bottom: none;">
<td>
<i>{{ value['title'] }}</i> written
by <i>{{ value['author'] }}</i>
in <i>{{ value['publishing_year'] }}</i>
<a class="waves-effect
waves-light
btn
right
more-text-detials"
data-metadata-key="{{ pair[0] }}"
data-text-key="{{ key }}"
href="#modal-text-details">More
<i class="material-icons right"
data-metadata-key="{{ pair[0] }}"
data-text-key="{{ key }}">
info_outline
</i>
</a>
</td>
</tr>
{% endfor %}
</table>
</td>
{% else %}
<td>{{ pair[1] }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-action right-align">
<a class="waves-effect waves-light btn left-align" href="{{ url_for('results.results_overview') }}">Back To Overview<i class="material-icons right">arrow_back</i></a>
<a class="waves-effect waves-light btn" href="{{ url_for('results.result_inspect', result_id=result.id) }}">Inspect Results<i class="material-icons right">search</i></a>
</div>
</div>
</div>
<!-- Modal Structure -->
<div id="modal-text-details" class="modal modal-fixed-footer">
<div class="modal-content">
<h4>Bibliographic data</h4>
<p id="bibliographic-data"></p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-green red btn">Close</a>
</div>
</div>
<script>
var moreTextDetailsButtons;
moreTextDetailsButtons = document.getElementsByClassName("more-text-detials");
for (var btn of moreTextDetailsButtons) {
btn.onclick = () => {
let modal = document.getElementById("modal-text-details");
modal = M.Modal.init(modal, {"dismissible": true});
modal.open();
let metadataKey = event.target.dataset.metadataKey;
let textKey = event.target.dataset.textKey;
let textData = {{ result.corpus_metadata|tojson|safe }}[metadataKey][textKey];
console.log(textData);
let bibliographicData = document.getElementById("bibliographic-data");
bibliographicData.innerHTML = "";
let table = document.createElement("table");
for (let [key, value] of Object.entries(textData)) {
table.insertAdjacentHTML("afterbegin",
`
<tr>
<td>${key}</td>
<td>${value}</td>
</tr>
`);
}
table.insertAdjacentHTML("afterbegin",
`
<thead>
<th>Description</th>
<th>Value</th>
</thead>
`)
bibliographicData.appendChild(table);
}
}
</script>
{% endblock %}

View File

@ -1,39 +0,0 @@
{% extends "nopaque.html.j2" %}
{% block page_content %}
<div class="col s12 m4">
<p>Fill out the following form to upload and view Results and Sub Results
exported from the Corpus analsis Tool.</p>
<a class="waves-effect waves-light btn" href="{{ url_for('main.dashboard') }}"><i class="material-icons left">arrow_back</i>Back to dashboard</a>
</div>
<div class="col s12 m8">
<form class="nopaque-submit-form" data-progress-modal="progress-modal">
<div class="card">
<div class="card-content">
{{ import_results_form.hidden_tag() }}
<div class="row">
<div class="col s12">
{{ M.render_field(import_results_form.file, accept='.json', placeholder='Choose your .json file') }}
</div>
</div>
</div>
<div class="card-action right-align">
{{ M.render_field(import_results_form.submit, material_icon='send') }}
</div>
</div>
</form>
</div>
<div id="progress-modal" class="modal">
<div class="modal-content">
<h4><i class="material-icons prefix">file_upload</i> Uploading file...</h4>
<div class="progress">
<div class="determinate" style="width: 0%"></div>
</div>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-light btn red abort-request">Cancel</a>
</div>
</div>
{% endblock %}

View File

@ -1,281 +0,0 @@
{% extends "nopaque.html.j2" %}
{% set headline = ' ' %}
{% set full_width = True %}
{% block page_content %}
<div class="col s12" id="query-display">
<div class="card">
<div class="card-content" id="result-list" style="overflow: hidden;">
<div class="row" style="margin-bottom: 0px;">
<div class="col s12 m3 l3" id="results-info">
<div class="row section">
<h6 style="margin-top: 0px;">Infos</h6>
<div class="divider" style="margin-bottom: 10px;"></div>
<div class="col" id="infos">
<p>
Displaying
<span id="received-match-count">
</span> of
<span id="match-count"></span>
matches.
<br>
Matches occured in
<span id="text-lookup-count"></span>
corpus files:
<br>
<span id=text-titles></span>
</p>
<div class="progress hide" id="query-results-progress">
<div class="determinate" id="query-results-determinate"></div>
</div>
</div>
</div>
</div>
<div class="col s12 m9 l9" id="actions-and-tools">
<div class="row section">
<div class="col s12 m3 l3" id="display">
<h6 style="margin-top: 0px;">Display</h6>
<div class="divider" style="margin-bottom: 10px;"></div>
<div class="row">
<div class="col s12">
<form id="display-options-form">
{{ M.render_field(display_options_form.results_per_page,
material_icon='format_list_numbered') }}
{{ M.render_field(display_options_form.result_context,
material_icon='short_text') }}
{{ M.render_field(display_options_form.expert_mode) }}
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Table showing the query results -->
<div class="col s12">
<ul class="pagination paginationTop"></ul>
<table class="responsive-table highlight">
<thead>
<tr>
<th style="width: 2%">Nr.</th>
<th style="width: 3%">Title</th>
<th style="width: 25%">Left context</th>
<th style="width: 35%">Match</th>
<th style="width: 10%">Actions</th>
<th style="width: 25%">Right Context</th>
</tr>
</thead>
<tbody class="list" id="query-results">
</tbody>
</table>
<ul class="pagination paginationBottom"></ul>
</div>
</div>
</div>
</div>
</div>
<!-- Context modal used for detailed information about one match -->
<div id="context-modal" class="modal modal-fixed-footer">
<div class="modal-content">
<form>
<div class="row" style="margin-bottom: 0px; margin-top: -20px;">
<div class="col s12 m6 l6">
<div class="section">
<h6 style="margin-top: 0px;">Display</h6>
<div class="divider" style="margin-bottom: 10px;"></div>
<div class="col s12" style="margin-bottom: 10px;" id="display-inspect">
{{ inspect_display_options_form.expert_mode_inspect.label.text }}
<div class="switch right">
<label>
{{ inspect_display_options_form.expert_mode_inspect() }}
<span class="lever"></span>
</label>
</div>
</div>
<div class="col s12" style="margin-bottom: 10px;" id="create-inspect">
{{ inspect_display_options_form.highlight_sentences.label.text }}
<div class="switch right">
<label>
{{ inspect_display_options_form.highlight_sentences() }}
<span class="lever"></span>
</label>
</div>
</div>
<div class="col s12" style="margin-bottom: 10px;">
Sentences around match
<div class="input-field right" style="margin-top: -2rem;
margin-bottom: -2rem;
height: 0px;">
<p class="range-field">
<input type="range"
id="context-sentences"
min="1"
max="10"
value="3" />
</p>
</div>
</div>
</div>
</div>
</div>
</form>
<div class="row section">
<h5 style="margin-top: 0px;">Context for match:
<span id="context-match-nr"></span></h5>
<div class="divider" style="margin-bottom: 10px;"></div>
<div class="col s12" >
<div id="context-results">
</div>
</div>
</div>
</div>
<div class="modal-footer">
{# <a id="inspect-download-context" class="left waves-effect waves-light btn">
Export Single Context
<i class="material-icons right">file_download</i>
</a> #}
<a href="#!" class="modal-close waves-effect waves-light red btn">Close</a>
</div>
</div>
<script src="{{ url_for('static', filename='js/nopaque.Results.js') }}">
</script>
<script src="{{ url_for('static', filename='js/nopaque.callbacks.js') }}">
</script>
<script src="{{ url_for('static', filename='js/nopaque.InteractionElement.js') }}">
</script>
<script>
// ###### global variables ######
var full_result_json;
var result_json;
var queryResultsDeterminateElement; // The progress bar for recieved results
var receivedMatchCountElement; // Nr. of loaded matches will be displayed in this element
var textLookupCountElement // Nr of texts the matches occured in will be shown in this element
var textTitlesElement; // matched text titles
var progress; // global progress value
var queryResultsProgressElement; // Div element holding the progress bar
var expertModeSwitchElement; // Expert mode switch Element
var matchCountElement; // Total nr. of matches will be displayed in this element
var interactionElements; // Interaction elements and their parameters
var contextModal; // Modal to open on inspect for further match context
// ###### Defining local scope variables
let displayOptionsFormElement; // Form holding the display informations
let resultItems; // array of built html result items row element. This is called when results are transmitted and being recieved
let hitsPerPageInputElement;let contextPerItemElement; // Form Element for display option
let paginationElements;
let inspectBtnElements;
// ###### Initializing variables ######
displayOptionsFormElement = document.getElementById("display-options-form");
resultItems = [];
queryResultsDeterminateElement = document.getElementById("query-results-determinate");
receivedMatchCountElement = document.getElementById("received-match-count");
textLookupCountElement = document.getElementById("text-lookup-count");
textTitlesElement = document.getElementById("text-titles");
queryResultsProgressElement = document.getElementById("query-results-progress");
expertModeSwitchElement = document.getElementById("display-options-form-expert_mode");
matchCountElement = document.getElementById("match-count");
hitsPerPageInputElement = document.getElementById("display-options-form-results_per_page");
contextPerItemElement = document.getElementById("display-options-form-result_context");
paginationElements = document.getElementsByClassName("pagination");
contextModal = document.getElementById("context-modal");
// js list options
displayOptionsData = ResultsList.getDisplayOptions(displayOptionsFormElement);
resultsListOptions = {page: displayOptionsData["resultsPerPage"],
pagination: [{
name: "paginationTop",
paginationClass: "paginationTop",
innerWindow: 8,
outerWindow: 1
}, {
paginationClass: "paginationBottom",
innerWindow: 8,
outerWindow: 1
}],
valueNames: ["titles", "lc", "c", "rc", {data: ["index"]}],
item: `<span></span>`
};
document.addEventListener("DOMContentLoaded", () => {
// Initialize some Modals
contextModal = M.Modal.init(contextModal, {"dismissible": true});
// ###### recreating chunk structure to reuse callback queryRenderResults()
full_result_json = {{ result_json|tojson|safe }};
result_json = {};
result_json["chunk"] = {};
result_json.chunk["cpos_lookup"] = full_result_json.cpos_lookup;
result_json.chunk["cpos_ranges"] = full_result_json.cpos_ranges;
result_json.chunk["matches"] = full_result_json.matches;
result_json.chunk["text_lookup"] = full_result_json.text_lookup;
// Init corpus analysis components
data = new Data();
resultsList = new ResultsList("result-list", resultsListOptions);
resultsMetaData = new MetaData();
results = new Results(data, resultsList, resultsMetaData);
results.clearAll(); // inits some object keys and values
// TODO: save metadate into results.metaData
// setting some initial values for user feedback
matchCountElement.innerText = full_result_json.match_count;
// Initialization of interactionElemnts
// An interactionElement is an object identifing a switch or button via
// htmlID. Callbacks are set for these elements which will be triggered on
// a pagination interaction by the user or if the status of the element has
// been altered. (Like the switche has ben turned on or off).
interactionElements = new Array();
let expertModeInteraction = new InteractionElement("display-options-form-expert_mode");
expertModeInteraction.setCallback("on",
results.jsList.expertModeOn,
results.jsList,
["query-display"])
expertModeInteraction.setCallback("off",
results.jsList.expertModeOff,
results.jsList,
["query-display"])
let activateInspectInteraction = new InteractionElement("inspect",
false);
activateInspectInteraction.setCallback("noCheck",
results.jsList.activateInspect,
results.jsList);
let changeContextInteraction = new InteractionElement("display-options-form-results_per_page",
false);
changeContextInteraction.setCallback("noCheck",
results.jsList.changeContext,
results.jsList)
interactionElements.push(expertModeInteraction, activateInspectInteraction, changeContextInteraction);
// checks if a change for every interactionElement happens and executes
// the callbacks accordingly
InteractionElement.onChangeExecute(interactionElements);
// eventListener if pagination is used to apply new context size to new page
// and also activate inspect match if progress is 100
// also adds more interaction buttons like add to sub results
for (let element of paginationElements) {
element.addEventListener("click", (event) => {
results.jsList.pageChangeEventInteractionHandler(interactionElements);
});
}
// render results in table imported parameter is true
queryRenderResults(result_json, true);
// live update of hits per page if hits per page value is changed
let changeHitsPerPageBind = results.jsList.changeHitsPerPage.bind(results.jsList);
hitsPerPageInputElement.onchange = changeHitsPerPageBind;
// live update of lr context per item if context value is changed
contextPerItemElement.onchange = results.jsList.changeContext;
});
</script>
{% endblock %}

View File

@ -1,71 +0,0 @@
{% extends "nopaque.html.j2" %}
{% set full_width = True %}
{% block page_content %}
<div class="col s12">
<p>This is an overview of all your imported results.</p>
</div>
<div class="col s12">
<div class="card">
<div class="card-content" id="results">
<div class="input-field">
<i class="material-icons prefix">search</i>
<input id="search-results" class="search" type="search"></input>
<label for="search-results">Search results</label>
</div>
<ul class="pagination paginationTop"></ul>
<table class="highlight responsive-table">
<thead>
<tr>
<th class="sort" data-sort="query">Query</th>
<th class="sort" data-sort="match_count">Match count</th>
<th class="sort" data-sort="corpus_name">Corpus name</th>
<th class="sort" data-sort="corpus_creation_date">Corpus creation date</th>
<th class="sort" data-sort="corpus_analysis_date">Corpus analysis date</th>
<th class="sort" data-sort="corpus_type">Corpus type</th>
<th>{# Actions #}</th>
</tr>
</thead>
<tbody class="list">
<tr class="show-if-only-child">
<td colspan="5">
<span class="card-title"><i class="material-icons left">folder</i>Nothing here...</span>
<p>No results yet imported.</p>
</td>
</tr>
</tbody>
</table>
<ul class="pagination paginationBottom"></ul>
</div>
<div class="card-action right-align">
<a class="waves-effect waves-light btn" href="{{ url_for('results.result_import') }}">Import Results<i class="material-icons right">file_upload</i></a>
</div>
</div>
</div>
{# Delete modals #}
{% for result in results %}
<div id="delete-result-{{ result.id }}-modal" class="modal">
<div class="modal-content">
<h4>Confirm result file deletion</h4>
<p>Do you really want to delete the result file created on <i>{{ result.corpus_analysis_date }}</i>?
<p>The file holds results for the query <i>{{ result.query }}</i>.</p>
<p>The file will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a href="#!" class="btn modal-close waves-effect waves-light">Cancel</a>
<a class="btn modal-close red waves-effect waves-light" href="{{ url_for('results.result_delete', result_id=result.id) }}"><i class="material-icons left">delete</i>Delete</a>
</div>
</div>
{% endfor %}
<script>
var ressources = {{ results|tojson|safe }};
var importedResultsList = new RessourceList("results", null, "result");
importedResultsList.addRessources(ressources);
RessourceList.modifyTooltips();
</script>
{% endblock %}

View File

@ -37,8 +37,6 @@
<ul class="pagination"></ul> <ul class="pagination"></ul>
</div> </div>
<div class="card-action right-align"> <div class="card-action right-align">
<a class="waves-effect waves-light btn" href="{{ url_for('results.result_import') }}">Import Results<i class="material-icons right">file_upload</i></a>
<a class="waves-effect waves-light btn" href="{{ url_for('results.results_overview') }}">Show Imported Results<i class="material-icons right">folder</i></a>
<a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a> <a class="waves-effect waves-light btn" href="{{ url_for('corpora.add_corpus') }}">New corpus<i class="material-icons right">add</i></a>
</div> </div>
</div> </div>

View File

@ -0,0 +1,43 @@
"""empty message
Revision ID: c3827cddea6e
Revises: 9d21b228d353
Create Date: 2020-07-15 12:33:24.574579
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'c3827cddea6e'
down_revision = '9d21b228d353'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('results')
op.drop_table('result_files')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('result_files',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('result_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('filename', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('dir', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['result_id'], ['results.id'], name='result_files_result_id_fkey'),
sa.PrimaryKeyConstraint('id', name='result_files_pkey')
)
op.create_table('results',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('corpus_metadata', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='results_user_id_fkey'),
sa.PrimaryKeyConstraint('id', name='results_pkey')
)
# ### end Alembic commands ###

View File

@ -3,7 +3,7 @@ eventlet.monkey_patch() # noqa
from app import create_app, db, socketio from app import create_app, db, socketio
from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult, from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult,
NotificationData, NotificationEmailData, QueryResult, NotificationData, NotificationEmailData, QueryResult,
Result, ResultFile, Role, User) Role, User)
from flask_migrate import Migrate, upgrade from flask_migrate import Migrate, upgrade
import os import os
@ -22,8 +22,6 @@ def make_shell_context():
'NotificationData': NotificationData, 'NotificationData': NotificationData,
'NotificationEmailData': NotificationEmailData, 'NotificationEmailData': NotificationEmailData,
'QueryResult': QueryResult, 'QueryResult': QueryResult,
'Result': Result,
'ResultFile': ResultFile,
'Role': Role, 'Role': Role,
'User': User} 'User': User}