mirror of
https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
synced 2024-12-25 19:04:18 +00:00
Implement corpora endpoint/socket.io namespace
This commit is contained in:
parent
93344c9573
commit
328f85ba52
@ -133,6 +133,9 @@ def create_app(config: Config = Config) -> Flask:
|
|||||||
from .namespaces.cqi_over_sio import CQiOverSocketIONamespace
|
from .namespaces.cqi_over_sio import CQiOverSocketIONamespace
|
||||||
socketio.on_namespace(CQiOverSocketIONamespace('/cqi_over_sio'))
|
socketio.on_namespace(CQiOverSocketIONamespace('/cqi_over_sio'))
|
||||||
|
|
||||||
|
from .namespaces.corpora import CorporaNamespace
|
||||||
|
socketio.on_namespace(CorporaNamespace('/corpora'))
|
||||||
|
|
||||||
from .namespaces.jobs import JobsNamespace
|
from .namespaces.jobs import JobsNamespace
|
||||||
socketio.on_namespace(JobsNamespace('/jobs'))
|
socketio.on_namespace(JobsNamespace('/jobs'))
|
||||||
|
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
from flask_login import current_user
|
|
||||||
from flask_socketio import join_room
|
|
||||||
from app import hashids, socketio
|
|
||||||
from app.decorators import socketio_login_required
|
|
||||||
from app.models import Corpus
|
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('GET /corpora/<corpus_id>')
|
|
||||||
@socketio_login_required
|
|
||||||
def get_corpus(corpus_hashid):
|
|
||||||
corpus_id = hashids.decode(corpus_hashid)
|
|
||||||
corpus = Corpus.query.get(corpus_id)
|
|
||||||
if corpus is None:
|
|
||||||
return {'options': {'status': 404, 'statusText': 'Not found'}}
|
|
||||||
if not (
|
|
||||||
corpus.is_public
|
|
||||||
or corpus.user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'options': {'status': 403, 'statusText': 'Forbidden'}}
|
|
||||||
return {
|
|
||||||
'body': corpus.to_json_serializable(),
|
|
||||||
'options': {
|
|
||||||
'status': 200,
|
|
||||||
'statusText': 'OK',
|
|
||||||
'headers': {'Content-Type: application/json'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('SUBSCRIBE /corpora/<corpus_id>')
|
|
||||||
@socketio_login_required
|
|
||||||
def subscribe_corpus(corpus_hashid):
|
|
||||||
corpus_id = hashids.decode(corpus_hashid)
|
|
||||||
corpus = Corpus.query.get(corpus_id)
|
|
||||||
if corpus is None:
|
|
||||||
return {'options': {'status': 404, 'statusText': 'Not found'}}
|
|
||||||
if not (
|
|
||||||
corpus.is_public
|
|
||||||
or corpus.user == current_user
|
|
||||||
or current_user.is_administrator
|
|
||||||
):
|
|
||||||
return {'options': {'status': 403, 'statusText': 'Forbidden'}}
|
|
||||||
join_room(f'/corpora/{corpus.hashid}')
|
|
||||||
return {'options': {'status': 200, 'statusText': 'OK'}}
|
|
215
app/namespaces/corpora.py
Normal file
215
app/namespaces/corpora.py
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from flask import current_app, Flask, url_for
|
||||||
|
from flask_login import current_user
|
||||||
|
from flask_socketio import Namespace
|
||||||
|
from string import punctuation
|
||||||
|
import nltk
|
||||||
|
from app import db, hashids, socketio
|
||||||
|
from app.decorators import socketio_login_required
|
||||||
|
from app.models import Corpus, CorpusFollowerAssociation, CorpusFollowerRole
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_corpus(app: Flask, corpus_id: int):
|
||||||
|
with app.app_context():
|
||||||
|
corpus = Corpus.query.get(corpus_id)
|
||||||
|
corpus.delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _build_corpus(app: Flask, corpus_id: int):
|
||||||
|
with app.app_context():
|
||||||
|
corpus = Corpus.query.get(corpus_id)
|
||||||
|
corpus.build()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
class CorporaNamespace(Namespace):
|
||||||
|
@socketio_login_required
|
||||||
|
def on_delete(self, corpus_hashid: str) -> dict:
|
||||||
|
if not isinstance(corpus_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
corpus_id = hashids.decode(corpus_hashid)
|
||||||
|
|
||||||
|
if not isinstance(corpus_id, int):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
corpus = Corpus.query.get(corpus_id)
|
||||||
|
|
||||||
|
if corpus is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
|
if not (
|
||||||
|
corpus.user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
socketio.start_background_task(
|
||||||
|
_delete_corpus,
|
||||||
|
current_app._get_current_object(),
|
||||||
|
corpus_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'body': f'Corpus "{corpus.title}" marked for deletion',
|
||||||
|
'status': 202,
|
||||||
|
'statusText': 'Accepted'
|
||||||
|
}
|
||||||
|
|
||||||
|
@socketio_login_required
|
||||||
|
def on_build(self, corpus_hashid: str) -> dict:
|
||||||
|
if not isinstance(corpus_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
corpus_id = hashids.decode(corpus_hashid)
|
||||||
|
|
||||||
|
if not isinstance(corpus_id, int):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
corpus = Corpus.query.get(corpus_id)
|
||||||
|
|
||||||
|
if corpus is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
|
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
||||||
|
if not (
|
||||||
|
cfa is not None and cfa.role.has_permission('MANAGE_FILES')
|
||||||
|
or corpus.user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
if len(corpus.files.all()) == 0:
|
||||||
|
return {'status': 409, 'statusText': 'Conflict'}
|
||||||
|
|
||||||
|
socketio.start_background_task(
|
||||||
|
_build_corpus,
|
||||||
|
current_app._get_current_object(),
|
||||||
|
corpus_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'body': f'Corpus "{corpus.title}" marked for building',
|
||||||
|
'status': 202,
|
||||||
|
'statusText': 'Accepted'
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: Think about where to place this, as this does not belong here...
|
||||||
|
@socketio_login_required
|
||||||
|
def on_get_stopwords(self):
|
||||||
|
languages = [
|
||||||
|
'german',
|
||||||
|
'english',
|
||||||
|
'catalan',
|
||||||
|
'greek',
|
||||||
|
'spanish',
|
||||||
|
'french',
|
||||||
|
'italian',
|
||||||
|
'russian',
|
||||||
|
'chinese'
|
||||||
|
]
|
||||||
|
|
||||||
|
nltk.download('stopwords', quiet=True)
|
||||||
|
stopwords = {
|
||||||
|
language: nltk.corpus.stopwords.words(language)
|
||||||
|
for language in languages
|
||||||
|
}
|
||||||
|
stopwords['punctuation'] = list(punctuation)
|
||||||
|
stopwords['punctuation'] += ['—', '|', '–', '“', '„', '--']
|
||||||
|
stopwords['user_stopwords'] = []
|
||||||
|
|
||||||
|
return {
|
||||||
|
'body': stopwords,
|
||||||
|
'status': 200,
|
||||||
|
'statusText': 'OK'
|
||||||
|
}
|
||||||
|
|
||||||
|
@socketio_login_required
|
||||||
|
def on_create_share_link(self, corpus_hashid: str, expiration_date: str, role_name: str) -> dict:
|
||||||
|
if not isinstance(corpus_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
if not isinstance(expiration_date, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
if not isinstance(role_name, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
print(corpus_hashid, expiration_date, role_name)
|
||||||
|
|
||||||
|
corpus_id = hashids.decode(corpus_hashid)
|
||||||
|
|
||||||
|
if not isinstance(corpus_id, int):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
corpus = Corpus.query.get(corpus_id)
|
||||||
|
|
||||||
|
if corpus is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
|
cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
|
||||||
|
if not (
|
||||||
|
cfa is not None and cfa.role.has_permission('MANAGE_FOLLOWERS')
|
||||||
|
or corpus.user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
_expiration_date = datetime.strptime(expiration_date, '%b %d, %Y')
|
||||||
|
|
||||||
|
cfr = CorpusFollowerRole.query.filter_by(name=role_name).first()
|
||||||
|
if cfr is None:
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
token = current_user.generate_follow_corpus_token(
|
||||||
|
corpus.hashid,
|
||||||
|
role_name,
|
||||||
|
_expiration_date
|
||||||
|
)
|
||||||
|
|
||||||
|
corpus_share_link = url_for(
|
||||||
|
'corpora.follow_corpus',
|
||||||
|
corpus_id=corpus_id,
|
||||||
|
token=token,
|
||||||
|
_external=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'body': corpus_share_link,
|
||||||
|
'status': 200,
|
||||||
|
'statusText': 'OK'
|
||||||
|
}
|
||||||
|
|
||||||
|
@socketio_login_required
|
||||||
|
def on_set_is_public(corpus_hashid: str, new_value: bool) -> dict:
|
||||||
|
if not isinstance(corpus_id, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
if not isinstance(new_value, bool):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
corpus_id = hashids.decode(corpus_hashid)
|
||||||
|
|
||||||
|
if not isinstance(corpus_id, int):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
|
corpus = Corpus.query.get(corpus_id)
|
||||||
|
|
||||||
|
if corpus is None:
|
||||||
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
|
if not (
|
||||||
|
corpus.user == current_user
|
||||||
|
or current_user.is_administrator
|
||||||
|
):
|
||||||
|
return {'status': 403, 'statusText': 'Forbidden'}
|
||||||
|
|
||||||
|
corpus.is_public = new_value
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'body': f'Corpus "{corpus.title}" is now {"public" if new_value else "private"}',
|
||||||
|
'status': 200,
|
||||||
|
'statusText': 'OK'
|
||||||
|
}
|
@ -169,6 +169,7 @@ class CQiOverSocketIONamespace(Namespace):
|
|||||||
|
|
||||||
for param in signature(fn).parameters.values():
|
for param in signature(fn).parameters.values():
|
||||||
# Check if the parameter is optional or required
|
# Check if the parameter is optional or required
|
||||||
|
# The following is true for required parameters
|
||||||
if param.default is param.empty:
|
if param.default is param.empty:
|
||||||
if param.name not in fn_args:
|
if param.name not in fn_args:
|
||||||
return {'code': 400, 'msg': 'Bad Request'}
|
return {'code': 400, 'msg': 'Bad Request'}
|
||||||
|
@ -23,6 +23,9 @@ def _restart_job(app: Flask, job_id: int):
|
|||||||
class JobsNamespace(Namespace):
|
class JobsNamespace(Namespace):
|
||||||
@socketio_login_required
|
@socketio_login_required
|
||||||
def on_delete(self, job_hashid: str) -> dict:
|
def on_delete(self, job_hashid: str) -> dict:
|
||||||
|
if not isinstance(job_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
job_id = hashids.decode(job_hashid)
|
job_id = hashids.decode(job_hashid)
|
||||||
|
|
||||||
if not isinstance(job_id, int):
|
if not isinstance(job_id, int):
|
||||||
@ -31,7 +34,7 @@ class JobsNamespace(Namespace):
|
|||||||
job = Job.query.get(job_id)
|
job = Job.query.get(job_id)
|
||||||
|
|
||||||
if job is None:
|
if job is None:
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
job.user == current_user
|
job.user == current_user
|
||||||
@ -53,6 +56,9 @@ class JobsNamespace(Namespace):
|
|||||||
|
|
||||||
@socketio_admin_required
|
@socketio_admin_required
|
||||||
def on_log(self, job_hashid: str) -> dict:
|
def on_log(self, job_hashid: str) -> dict:
|
||||||
|
if not isinstance(job_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
job_id = hashids.decode(job_hashid)
|
job_id = hashids.decode(job_hashid)
|
||||||
|
|
||||||
if not isinstance(job_id, int):
|
if not isinstance(job_id, int):
|
||||||
@ -61,7 +67,7 @@ class JobsNamespace(Namespace):
|
|||||||
job = Job.query.get(job_id)
|
job = Job.query.get(job_id)
|
||||||
|
|
||||||
if job is None:
|
if job is None:
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
|
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
|
||||||
return {'status': 409, 'statusText': 'Conflict'}
|
return {'status': 409, 'statusText': 'Conflict'}
|
||||||
@ -72,11 +78,14 @@ class JobsNamespace(Namespace):
|
|||||||
return {
|
return {
|
||||||
'body': log,
|
'body': log,
|
||||||
'status': 200,
|
'status': 200,
|
||||||
'statusText': 'Forbidden'
|
'statusText': 'OK'
|
||||||
}
|
}
|
||||||
|
|
||||||
socketio_login_required
|
socketio_login_required
|
||||||
def on_restart(self, job_hashid: str) -> dict:
|
def on_restart(self, job_hashid: str) -> dict:
|
||||||
|
if not isinstance(job_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
job_id = hashids.decode(job_hashid)
|
job_id = hashids.decode(job_hashid)
|
||||||
|
|
||||||
if not isinstance(job_id, int):
|
if not isinstance(job_id, int):
|
||||||
@ -85,7 +94,7 @@ class JobsNamespace(Namespace):
|
|||||||
job = Job.query.get(job_id)
|
job = Job.query.get(job_id)
|
||||||
|
|
||||||
if job is None:
|
if job is None:
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
job.user == current_user
|
job.user == current_user
|
||||||
|
@ -16,6 +16,9 @@ def _delete_user(app: Flask, user_id: int):
|
|||||||
class UsersNamespace(Namespace):
|
class UsersNamespace(Namespace):
|
||||||
@socketio_login_required
|
@socketio_login_required
|
||||||
def on_get(self, user_hashid: str) -> dict:
|
def on_get(self, user_hashid: str) -> dict:
|
||||||
|
if not isinstance(user_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
user_id = hashids.decode(user_hashid)
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
if not isinstance(user_id, int):
|
if not isinstance(user_id, int):
|
||||||
@ -24,7 +27,7 @@ class UsersNamespace(Namespace):
|
|||||||
user = User.query.get(user_id)
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
user == current_user
|
user == current_user
|
||||||
@ -43,6 +46,9 @@ class UsersNamespace(Namespace):
|
|||||||
|
|
||||||
@socketio_login_required
|
@socketio_login_required
|
||||||
def on_subscribe(self, user_hashid: str) -> dict:
|
def on_subscribe(self, user_hashid: str) -> dict:
|
||||||
|
if not isinstance(user_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
user_id = hashids.decode(user_hashid)
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
if not isinstance(user_id, int):
|
if not isinstance(user_id, int):
|
||||||
@ -51,7 +57,7 @@ class UsersNamespace(Namespace):
|
|||||||
user = User.query.get(user_id)
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
user == current_user
|
user == current_user
|
||||||
@ -65,6 +71,9 @@ class UsersNamespace(Namespace):
|
|||||||
|
|
||||||
@socketio_login_required
|
@socketio_login_required
|
||||||
def on_unsubscribe(self, user_hashid: str) -> dict:
|
def on_unsubscribe(self, user_hashid: str) -> dict:
|
||||||
|
if not isinstance(user_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
user_id = hashids.decode(user_hashid)
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
if not isinstance(user_id, int):
|
if not isinstance(user_id, int):
|
||||||
@ -73,7 +82,7 @@ class UsersNamespace(Namespace):
|
|||||||
user = User.query.get(user_id)
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
user == current_user
|
user == current_user
|
||||||
@ -87,6 +96,9 @@ class UsersNamespace(Namespace):
|
|||||||
|
|
||||||
@socketio_login_required
|
@socketio_login_required
|
||||||
def on_delete(self, user_hashid: str) -> dict:
|
def on_delete(self, user_hashid: str) -> dict:
|
||||||
|
if not isinstance(user_hashid, str):
|
||||||
|
return {'status': 400, 'statusText': 'Bad Request'}
|
||||||
|
|
||||||
user_id = hashids.decode(user_hashid)
|
user_id = hashids.decode(user_hashid)
|
||||||
|
|
||||||
if not isinstance(user_id, int):
|
if not isinstance(user_id, int):
|
||||||
@ -95,7 +107,7 @@ class UsersNamespace(Namespace):
|
|||||||
user = User.query.get(user_id)
|
user = User.query.get(user_id)
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return {'status': 404, 'statusText': 'Not found'}
|
return {'status': 404, 'statusText': 'Not Found'}
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
user == current_user
|
user == current_user
|
||||||
|
@ -3,6 +3,7 @@ nopaque.App = class App {
|
|||||||
this.socket = io({transports: ['websocket'], upgrade: false});
|
this.socket = io({transports: ['websocket'], upgrade: false});
|
||||||
|
|
||||||
// Endpoints
|
// Endpoints
|
||||||
|
this.corpora = new nopaque.app.endpoints.Corpora(this);
|
||||||
this.jobs = new nopaque.app.endpoints.Jobs(this);
|
this.jobs = new nopaque.app.endpoints.Jobs(this);
|
||||||
this.users = new nopaque.app.endpoints.Users(this);
|
this.users = new nopaque.app.endpoints.Users(this);
|
||||||
|
|
||||||
|
57
app/static/js/app/endpoints/corpora.js
Normal file
57
app/static/js/app/endpoints/corpora.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
nopaque.app.endpoints.Corpora = class Corpora {
|
||||||
|
constructor(app) {
|
||||||
|
this.app = app;
|
||||||
|
|
||||||
|
this.socket = io('/corpora', {transports: ['websocket'], upgrade: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id) {
|
||||||
|
const response = await this.socket.emitWithAck('delete', id);
|
||||||
|
|
||||||
|
if (response.status != 202) {
|
||||||
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
async build(id) {
|
||||||
|
const response = await this.socket.emitWithAck('build', id);
|
||||||
|
|
||||||
|
if (response.status != 202) {
|
||||||
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStopwords() {
|
||||||
|
const response = await this.socket.emitWithAck('get_stopwords');
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createShareLink(id, expirationDate, roleName) {
|
||||||
|
const response = await this.socket.emitWithAck('create_share_link', id, expirationDate, roleName);
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setIsPublic(id, newValue) {
|
||||||
|
const response = await this.socket.emitWithAck('set_is_public', id, newValue);
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
throw new Error(`[${response.status}] ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body;
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
stopwords: undefined,
|
stopwords: undefined,
|
||||||
originalStopwords: {},
|
originalStopwords: {},
|
||||||
stopwordCache: {},
|
stopwordCache: {},
|
||||||
promises: {getStopwords: undefined},
|
|
||||||
tokenSet: new Set()
|
tokenSet: new Set()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,22 +72,11 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStopwords() {
|
async getStopwords() {
|
||||||
this.data.promises.getStopwords = new Promise((resolve, reject) => {
|
const stopwords = await app.corpora.getStopwords();
|
||||||
nopaque.requests.corpora.entity.getStopwords()
|
this.data.originalStopwords = structuredClone(stopwords);
|
||||||
.then((response) => {
|
this.data.stopwords = structuredClone(stopwords);
|
||||||
response.json()
|
return stopwords;
|
||||||
.then((json) => {
|
|
||||||
this.data.originalStopwords = structuredClone(json);
|
|
||||||
this.data.stopwords = structuredClone(json);
|
|
||||||
resolve(this.data.stopwords);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return this.data.promises.getStopwords;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGeneralCorpusInfo() {
|
renderGeneralCorpusInfo() {
|
||||||
@ -121,7 +109,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
num_unique_pos: Object.entries(corpusData.s_attrs.text.lexicon[i].freqs.pos).length,
|
num_unique_pos: Object.entries(corpusData.s_attrs.text.lexicon[i].freqs.pos).length,
|
||||||
num_unique_simple_pos: Object.entries(corpusData.s_attrs.text.lexicon[i].freqs.simple_pos).length
|
num_unique_simple_pos: Object.entries(corpusData.s_attrs.text.lexicon[i].freqs.simple_pos).length
|
||||||
};
|
};
|
||||||
|
|
||||||
textData.push(resource);
|
textData.push(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +250,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
}
|
}
|
||||||
return filteredData;
|
return filteredData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
renderFrequenciesGraphic(tokenSet) {
|
renderFrequenciesGraphic(tokenSet) {
|
||||||
this.data.tokenSet = tokenSet;
|
this.data.tokenSet = tokenSet;
|
||||||
@ -272,7 +260,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
let texts = Object.entries(corpusData.s_attrs.text.lexicon);
|
let texts = Object.entries(corpusData.s_attrs.text.lexicon);
|
||||||
let graphtype = document.querySelector('.frequencies-graph-mode-button.disabled').dataset.graphType;
|
let graphtype = document.querySelector('.frequencies-graph-mode-button.disabled').dataset.graphType;
|
||||||
let tokenCategory = frequenciesTokenCategoryDropdownElement.firstChild.textContent.toLowerCase();
|
let tokenCategory = frequenciesTokenCategoryDropdownElement.firstChild.textContent.toLowerCase();
|
||||||
|
|
||||||
let graphData = this.createFrequenciesGraphData(tokenCategory, texts, graphtype, tokenSet);
|
let graphData = this.createFrequenciesGraphData(tokenCategory, texts, graphtype, tokenSet);
|
||||||
let graphLayout = {
|
let graphLayout = {
|
||||||
barmode: graphtype === 'bar' ? 'stack' : '',
|
barmode: graphtype === 'bar' ? 'stack' : '',
|
||||||
@ -328,7 +316,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
for (let i = 0; i < texts.length; i++) {
|
for (let i = 0; i < texts.length; i++) {
|
||||||
tokenCountPerText[i] = (tokenCountPerText[i] || 0) + (texts[i][1].freqs[tokenCategory][originalId] || 0);
|
tokenCountPerText[i] = (tokenCountPerText[i] || 0) + (texts[i][1].freqs[tokenCategory][originalId] || 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let data = {
|
let data = {
|
||||||
x: textTitles,
|
x: textTitles,
|
||||||
y: tokenCountPerText,
|
y: tokenCountPerText,
|
||||||
@ -353,7 +341,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
stopwordLanguageChipList.innerHTML = '';
|
stopwordLanguageChipList.innerHTML = '';
|
||||||
userStopwordListContainer.innerHTML = '';
|
userStopwordListContainer.innerHTML = '';
|
||||||
stopwordInputField.value = '';
|
stopwordInputField.value = '';
|
||||||
|
|
||||||
// Render stopword language selection. Set english as default language. Filter out user_stopwords.
|
// Render stopword language selection. Set english as default language. Filter out user_stopwords.
|
||||||
if (stopwordLanguageSelection.children.length === 0) {
|
if (stopwordLanguageSelection.children.length === 0) {
|
||||||
Object.keys(stopwords).forEach(language => {
|
Object.keys(stopwords).forEach(language => {
|
||||||
@ -379,7 +367,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
// Render english stopwords as default ...
|
// Render english stopwords as default ...
|
||||||
let selectedLanguage = document.querySelector('#stopword-language-selection').value;
|
let selectedLanguage = document.querySelector('#stopword-language-selection').value;
|
||||||
this.renderStopwordLanguageChipList(selectedLanguage, stopwords[selectedLanguage]);
|
this.renderStopwordLanguageChipList(selectedLanguage, stopwords[selectedLanguage]);
|
||||||
|
|
||||||
// ... or render selected language stopwords.
|
// ... or render selected language stopwords.
|
||||||
stopwordLanguageSelection.addEventListener('change', (event) => {
|
stopwordLanguageSelection.addEventListener('change', (event) => {
|
||||||
this.renderStopwordLanguageChipList(event.target.value, stopwords[event.target.value]);
|
this.renderStopwordLanguageChipList(event.target.value, stopwords[event.target.value]);
|
||||||
@ -402,7 +390,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
|
|
||||||
// Initialize Materialize components.
|
// Initialize Materialize components.
|
||||||
M.Chips.init(
|
M.Chips.init(
|
||||||
stopwordInputField,
|
stopwordInputField,
|
||||||
{
|
{
|
||||||
placeholder: 'Add stopwords',
|
placeholder: 'Add stopwords',
|
||||||
onChipAdd: (event) => {
|
onChipAdd: (event) => {
|
||||||
@ -428,7 +416,7 @@ nopaque.corpus_analysis.StaticVisualizationExtension = class StaticVisualization
|
|||||||
deleteLanguageStopwordListEntriesButton.classList.toggle('disabled', stopwordLength === 0);
|
deleteLanguageStopwordListEntriesButton.classList.toggle('disabled', stopwordLength === 0);
|
||||||
resetLanguageStopwordListEntriesButton.classList.toggle('disabled', stopwordLength === originalStopwordListLength);
|
resetLanguageStopwordListEntriesButton.classList.toggle('disabled', stopwordLength === originalStopwordListLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderStopwordLanguageChipList(language, stopwords) {
|
renderStopwordLanguageChipList(language, stopwords) {
|
||||||
let stopwordLanguageChipList = document.querySelector('#stopword-language-chip-list');
|
let stopwordLanguageChipList = document.querySelector('#stopword-language-chip-list');
|
||||||
stopwordLanguageChipList.innerHTML = '';
|
stopwordLanguageChipList.innerHTML = '';
|
||||||
|
@ -5,50 +5,6 @@ nopaque.requests.corpora = {};
|
|||||||
|
|
||||||
nopaque.requests.corpora.entity = {};
|
nopaque.requests.corpora.entity = {};
|
||||||
|
|
||||||
nopaque.requests.corpora.entity.delete = (corpusId) => {
|
|
||||||
let input = `/corpora/${corpusId}`;
|
|
||||||
let init = {
|
|
||||||
method: 'DELETE'
|
|
||||||
};
|
|
||||||
return nopaque.requests.JSONfetch(input, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
nopaque.requests.corpora.entity.build = (corpusId) => {
|
|
||||||
let input = `/corpora/${corpusId}/build`;
|
|
||||||
let init = {
|
|
||||||
method: 'POST',
|
|
||||||
};
|
|
||||||
return nopaque.requests.JSONfetch(input, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
nopaque.requests.corpora.entity.generateShareLink = (corpusId, role, expiration) => {
|
|
||||||
let input = `/corpora/${corpusId}/generate-share-link`;
|
|
||||||
let init = {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({role: role, expiration: expiration})
|
|
||||||
};
|
|
||||||
return nopaque.requests.JSONfetch(input, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
nopaque.requests.corpora.entity.getStopwords = () => {
|
|
||||||
let input = `/corpora/stopwords`;
|
|
||||||
let init = {
|
|
||||||
method: 'GET'
|
|
||||||
};
|
|
||||||
return nopaque.requests.JSONfetch(input, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
nopaque.requests.corpora.entity.isPublic = {};
|
|
||||||
|
|
||||||
nopaque.requests.corpora.entity.isPublic.update = (corpusId, isPublic) => {
|
|
||||||
let input = `/corpora/${corpusId}/is_public`;
|
|
||||||
let init = {
|
|
||||||
method: 'PUT',
|
|
||||||
body: JSON.stringify(isPublic)
|
|
||||||
};
|
|
||||||
return nopaque.requests.JSONfetch(input, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Requests for /corpora/<entity>/files routes *
|
* Requests for /corpora/<entity>/files routes *
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Requests for /jobs routes *
|
|
||||||
*****************************************************************************/
|
|
||||||
nopaque.requests.jobs = {};
|
|
||||||
|
|
||||||
nopaque.requests.jobs.entity = {};
|
|
||||||
|
|
||||||
nopaque.requests.jobs.entity.delete = (jobId) => {
|
|
||||||
let input = `/jobs/${jobId}`;
|
|
||||||
let init = {
|
|
||||||
method: 'DELETE'
|
|
||||||
};
|
|
||||||
return nopaque.requests.JSONfetch(input, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
nopaque.requests.jobs.entity.log = (jobId) => {
|
|
||||||
let input = `/jobs/${jobId}/log`;
|
|
||||||
let init = {
|
|
||||||
method: 'GET'
|
|
||||||
};
|
|
||||||
return nopaque.requests.JSONfetch(input, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
nopaque.requests.jobs.entity.restart = (jobId) => {
|
|
||||||
let input = `/jobs/${jobId}/restart`;
|
|
||||||
let init = {
|
|
||||||
method: 'POST'
|
|
||||||
};
|
|
||||||
return nopaque.requests.JSONfetch(input, init);
|
|
||||||
};
|
|
@ -7,7 +7,7 @@ nopaque.resource_displays.CorpusDisplay = class CorpusDisplay extends nopaque.re
|
|||||||
this.displayElement
|
this.displayElement
|
||||||
.querySelector('.action-button[data-action="build-request"]')
|
.querySelector('.action-button[data-action="build-request"]')
|
||||||
.addEventListener('click', (event) => {
|
.addEventListener('click', (event) => {
|
||||||
nopaque.requests.corpora.entity.build(this.corpusId);
|
app.corpora.build(this.corpusId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
nopaque.requests.corpora.entity.delete(itemId);
|
app.corpora.delete(itemId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
modal.open();
|
modal.open();
|
||||||
@ -276,7 +276,7 @@ nopaque.resource_lists.CorpusList = class CorpusList extends nopaque.resource_li
|
|||||||
let listItem = this.listjs.get('id', selectedItemId)[0].elm;
|
let listItem = this.listjs.get('id', selectedItemId)[0].elm;
|
||||||
let values = this.listjs.get('id', listItem.dataset.id)[0].values();
|
let values = this.listjs.get('id', listItem.dataset.id)[0].values();
|
||||||
if (values['is-owner']) {
|
if (values['is-owner']) {
|
||||||
nopaque.requests.corpora.entity.delete(selectedItemId);
|
app.corpora.delete(selectedItemId);
|
||||||
} else {
|
} else {
|
||||||
nopaque.requests.corpora.entity.followers.entity.delete(selectedItemId, currentUserId);
|
nopaque.requests.corpora.entity.followers.entity.delete(selectedItemId, currentUserId);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
'js/app.js',
|
'js/app.js',
|
||||||
'js/app/index.js',
|
'js/app/index.js',
|
||||||
'js/app/endpoints/index.js',
|
'js/app/endpoints/index.js',
|
||||||
|
'js/app/endpoints/corpora.js',
|
||||||
'js/app/endpoints/jobs.js',
|
'js/app/endpoints/jobs.js',
|
||||||
'js/app/endpoints/users.js',
|
'js/app/endpoints/users.js',
|
||||||
'js/app/extensions/index.js',
|
'js/app/extensions/index.js',
|
||||||
@ -51,7 +52,6 @@
|
|||||||
'js/requests/admin.js',
|
'js/requests/admin.js',
|
||||||
'js/requests/contributions.js',
|
'js/requests/contributions.js',
|
||||||
'js/requests/corpora.js',
|
'js/requests/corpora.js',
|
||||||
'js/requests/jobs.js',
|
|
||||||
'js/requests/users.js',
|
'js/requests/users.js',
|
||||||
|
|
||||||
'js/corpus-analysis/index.js',
|
'js/corpus-analysis/index.js',
|
||||||
|
@ -246,7 +246,7 @@
|
|||||||
let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch');
|
let publishingModalIsPublicSwitchElement = document.querySelector('#publishing-modal-is-public-switch');
|
||||||
publishingModalIsPublicSwitchElement.addEventListener('change', (event) => {
|
publishingModalIsPublicSwitchElement.addEventListener('change', (event) => {
|
||||||
let newIsPublic = publishingModalIsPublicSwitchElement.checked;
|
let newIsPublic = publishingModalIsPublicSwitchElement.checked;
|
||||||
nopaque.requests.corpora.entity.isPublic.update({{ corpus.hashid|tojson }}, newIsPublic)
|
app.corpora.setIsPublic.update({{ corpus.hashid|tojson }}, newIsPublic)
|
||||||
.catch((response) => {
|
.catch((response) => {
|
||||||
publishingModalIsPublicSwitchElement.checked = !newIsPublic;
|
publishingModalIsPublicSwitchElement.checked = !newIsPublic;
|
||||||
});
|
});
|
||||||
@ -256,7 +256,7 @@ publishingModalIsPublicSwitchElement.addEventListener('change', (event) => {
|
|||||||
// #region Delete
|
// #region Delete
|
||||||
let deleteModalDeleteButtonElement = document.querySelector('#delete-modal-delete-button');
|
let deleteModalDeleteButtonElement = document.querySelector('#delete-modal-delete-button');
|
||||||
deleteModalDeleteButtonElement.addEventListener('click', (event) => {
|
deleteModalDeleteButtonElement.addEventListener('click', (event) => {
|
||||||
nopaque.requests.corpora.entity.delete({{ corpus.hashid|tojson }})
|
app.corpora.delete({{ corpus.hashid|tojson }})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
window.location.href = {{ url_for('main.dashboard')|tojson }};
|
window.location.href = {{ url_for('main.dashboard')|tojson }};
|
||||||
});
|
});
|
||||||
@ -346,19 +346,14 @@ M.Modal.init(
|
|||||||
shareLinkModalOutputContainerElement.classList.add('hide');
|
shareLinkModalOutputContainerElement.classList.add('hide');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
shareLinkModalCreateButtonElement.addEventListener('click', (event) => {
|
shareLinkModalCreateButtonElement.addEventListener('click', async (event) => {
|
||||||
let role = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
const role = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
||||||
let expiration = shareLinkModalExpirationDateDatepickerElement.value
|
const expiration = shareLinkModalExpirationDateDatepickerElement.value;
|
||||||
nopaque.requests.corpora.entity.generateShareLink({{ corpus.hashid|tojson }}, role, expiration)
|
const shareLink = await app.corpora.createShareLink({{ corpus.hashid|tojson }}, expiration, role);
|
||||||
.then((response) => {
|
shareLinkModalOutputContainerElement.classList.remove('hide');
|
||||||
response.json()
|
shareLinkModalOutputFieldElement.value = shareLink;
|
||||||
.then((json) => {
|
|
||||||
shareLinkModalOutputContainerElement.classList.remove('hide');
|
|
||||||
shareLinkModalOutputFieldElement.value = json.corpusShareLink;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
||||||
|
@ -273,7 +273,7 @@ publicCorpusFollowerList.add(
|
|||||||
{% if cfr.has_permission('MANAGE_FILES') %}
|
{% if cfr.has_permission('MANAGE_FILES') %}
|
||||||
let followerBuildRequest = document.querySelector('#follower-build-request');
|
let followerBuildRequest = document.querySelector('#follower-build-request');
|
||||||
followerBuildRequest.addEventListener('click', () => {
|
followerBuildRequest.addEventListener('click', () => {
|
||||||
nopaque.requests.corpora.entity.build({{ corpus.hashid|tojson }})
|
app.corpora.build({{ corpus.hashid|tojson }})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
@ -380,17 +380,12 @@ M.Modal.init(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
shareLinkModalCreateButtonElement.addEventListener('click', (event) => {
|
shareLinkModalCreateButtonElement.addEventListener('click', async (event) => {
|
||||||
let role = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
const roleName = shareLinkModalCorpusFollowerRoleSelectElement.value;
|
||||||
let expiration = shareLinkModalExpirationDateDatepickerElement.value
|
const expirationDate = shareLinkModalExpirationDateDatepickerElement.value;
|
||||||
nopaque.requests.corpora.entity.generateShareLink({{ corpus.hashid|tojson }}, role, expiration)
|
const shareLink = await app.corpora.createShareLink({{ corpus.hashid|tojson }}, expiration, role);
|
||||||
.then((response) => {
|
shareLinkModalOutputContainerElement.classList.remove('hide');
|
||||||
response.json()
|
shareLinkModalOutputFieldElement.value = shareLink;
|
||||||
.then((json) => {
|
|
||||||
shareLinkModalOutputContainerElement.classList.remove('hide');
|
|
||||||
shareLinkModalOutputFieldElement.value = json.corpusShareLink;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user