diff --git a/web/app/__init__.py b/web/app/__init__.py index f0e9a4ce808a2a08dda298a081cb6926ddf0cb08..57d5d73c70e2e62332a2a931376118d56eddcadf 100644 --- a/web/app/__init__.py +++ b/web/app/__init__.py @@ -59,7 +59,4 @@ def create_app(config_name): from .services import services as services_blueprint 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 diff --git a/web/app/models.py b/web/app/models.py index 24fd7b3ad679cade72f4a1e17938d3591c2c65be..d377272c0e5d2468f0d006272bf252af35a13d3d 100644 --- a/web/app/models.py +++ b/web/app/models.py @@ -133,8 +133,6 @@ class User(UserMixin, db.Model): cascade='save-update, merge, delete') jobs = db.relationship('Job', backref='creator', lazy='dynamic', cascade='save-update, merge, delete') - results = db.relationship('Result', backref='creator', lazy='dynamic', - cascade='save-update, merge, delete') query_results = db.relationship('QueryResult', backref='creator', cascade='save-update, merge, delete', @@ -660,59 +658,6 @@ class QueryResult(db.Model): 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 application’s custom anonymous user by setting ' its class in the login_manager.anonymous_user attribute. diff --git a/web/app/results/__init__.py b/web/app/results/__init__.py deleted file mode 100644 index 2f1f59a7c10787fc5c0f8d9b7a6107bfff9fe16d..0000000000000000000000000000000000000000 --- a/web/app/results/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from flask import Blueprint - - -results = Blueprint('results', __name__) -from . import views # noqa \ No newline at end of file diff --git a/web/app/results/forms.py b/web/app/results/forms.py deleted file mode 100644 index 13533ca6d3df76298ce46411bbe39f5e84de1509..0000000000000000000000000000000000000000 --- a/web/app/results/forms.py +++ /dev/null @@ -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) diff --git a/web/app/results/tasks.py b/web/app/results/tasks.py deleted file mode 100644 index d139501f8ecac2f3e7fb804abf3ce4911799907f..0000000000000000000000000000000000000000 --- a/web/app/results/tasks.py +++ /dev/null @@ -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() diff --git a/web/app/results/views.py b/web/app/results/views.py deleted file mode 100644 index 733ac962cfd3cb17cfeab23bd893e4a523c9dfff..0000000000000000000000000000000000000000 --- a/web/app/results/views.py +++ /dev/null @@ -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) diff --git a/web/app/static/js/nopaque.lists.js b/web/app/static/js/nopaque.lists.js index c7ec8fa899135e2b615d04a2333b90c4218b1b03..50d1a0c3b2ba9551fb83353b6b051f4653d76194 100644 --- a/web/app/static/js/nopaque.lists.js +++ b/web/app/static/js/nopaque.lists.js @@ -1,6 +1,6 @@ class RessourceList extends List { 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!"); return; } @@ -124,16 +124,6 @@ RessourceList.dataMapper = { link: `/query_results/${query_result.id}`, query: query_result.query_metadata.query, 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 User: user => ({username: user.username, email: user.email, @@ -277,56 +267,6 @@ RessourceList.options = { {data: ["id"]}, {name: "inspect-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 // Link classes have to correspond with Links defined in the Mapping process User: {item: `<tr> diff --git a/web/app/templates/main/dashboard.html.j2 b/web/app/templates/main/dashboard.html.j2 index 420ed561738496a9c08e7540ac5d7fbfb55cd5ff..fa45862c2877255e256ec0d37bb1633d901f04aa 100644 --- a/web/app/templates/main/dashboard.html.j2 +++ b/web/app/templates/main/dashboard.html.j2 @@ -28,8 +28,6 @@ <ul class="pagination"></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> - <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> </div> </div> diff --git a/web/app/templates/results/result_details.html.j2 b/web/app/templates/results/result_details.html.j2 deleted file mode 100644 index 7a85aa4a751d035cc2f204a3a17f8f567c6d41f3..0000000000000000000000000000000000000000 --- a/web/app/templates/results/result_details.html.j2 +++ /dev/null @@ -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 %} diff --git a/web/app/templates/results/result_import.html.j2 b/web/app/templates/results/result_import.html.j2 deleted file mode 100644 index f1e1d41fd9f41b5a5b2b4fc458ce6d3124a0e969..0000000000000000000000000000000000000000 --- a/web/app/templates/results/result_import.html.j2 +++ /dev/null @@ -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 %} diff --git a/web/app/templates/results/result_inspect.html.j2 b/web/app/templates/results/result_inspect.html.j2 deleted file mode 100644 index e28b964aa525797235da7ea9502536793a58c5d9..0000000000000000000000000000000000000000 --- a/web/app/templates/results/result_inspect.html.j2 +++ /dev/null @@ -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 %} \ No newline at end of file diff --git a/web/app/templates/results/results.html.j2 b/web/app/templates/results/results.html.j2 deleted file mode 100644 index ecf8095aae34b08707086e95119fa5578537305b..0000000000000000000000000000000000000000 --- a/web/app/templates/results/results.html.j2 +++ /dev/null @@ -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 %} diff --git a/web/app/templates/services/corpus_analysis.html.j2 b/web/app/templates/services/corpus_analysis.html.j2 index ff416fc7b23fd997cb666ddf4c7a2fc94b8182c6..ce93c8e0fd489dd1c6bd663072ab781035d81c84 100644 --- a/web/app/templates/services/corpus_analysis.html.j2 +++ b/web/app/templates/services/corpus_analysis.html.j2 @@ -37,8 +37,6 @@ <ul class="pagination"></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> - <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> </div> </div> diff --git a/web/migrations/versions/c3827cddea6e_.py b/web/migrations/versions/c3827cddea6e_.py new file mode 100644 index 0000000000000000000000000000000000000000..61e156d997b09e99b51108b2c5ddb29a6c4ec81f --- /dev/null +++ b/web/migrations/versions/c3827cddea6e_.py @@ -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 ### diff --git a/web/nopaque.py b/web/nopaque.py index b19c3b873e75c5ec087a2b121cfd8fb0f8ad7fd0..56b2bbeb709a1e78d045ee948732f980f59a9fb9 100644 --- a/web/nopaque.py +++ b/web/nopaque.py @@ -3,7 +3,7 @@ eventlet.monkey_patch() # noqa from app import create_app, db, socketio from app.models import (Corpus, CorpusFile, Job, JobInput, JobResult, NotificationData, NotificationEmailData, QueryResult, - Result, ResultFile, Role, User) + Role, User) from flask_migrate import Migrate, upgrade import os @@ -22,8 +22,6 @@ def make_shell_context(): 'NotificationData': NotificationData, 'NotificationEmailData': NotificationEmailData, 'QueryResult': QueryResult, - 'Result': Result, - 'ResultFile': ResultFile, 'Role': Role, 'User': User}