Skip to content

Commit

Permalink
rework active+archiving
Browse files Browse the repository at this point in the history
  • Loading branch information
joernu76 committed Sep 9, 2024
1 parent 863f052 commit 52c7a04
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 50 deletions.
2 changes: 2 additions & 0 deletions docs/mscolab.rst
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ Operation based features
- All the operations the user has created or has been added to can be found in Mscolab's main window along with the user's access level.
- To start working on an operation the user needs to select it which enables all the operation related buttons.
- Any change made to an operation by a user will be shared with everyone in real-time unless `Work Locally` is turned on.(More on this later)
- Operations can be manually archived to remove them from normal view without having to delete them.
They can be found again in the Archive view, where they can be unarchived for further use.

Operation Permissions
.....................
Expand Down
18 changes: 5 additions & 13 deletions mslib/mscolab/file_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,7 @@ def list_operations(self, user, skip_archived=False):
permissions = Permission.query.filter_by(u_id=user.id).all()
for permission in permissions:
operation = Operation.query.filter_by(id=permission.op_id).first()
if operation.last_used is not None and (
datetime.datetime.now(tz=datetime.timezone.utc) - operation.last_used
).days > mscolab_settings.ARCHIVE_THRESHOLD:
# outdated OPs get archived
self.update_operation(permission.op_id, "active", False, user)
# new query to get up-to-date data
if skip_archived:
operation = Operation.query.filter_by(id=permission.op_id, active=skip_archived).first()
else:
operation = Operation.query.filter_by(id=permission.op_id).first()

if operation is not None:
if operation is not None and (operation.active or not skip_archived):
operations.append({
"op_id": permission.op_id,
"access_level": permission.access_level,
Expand Down Expand Up @@ -367,6 +356,9 @@ def update_operation(self, op_id, attribute, value, user):
# the user changing the {category}{mscolab_settings.GROUP_POSTFIX} needs to have rights in the op
# then members of this op gets added to all others of same category
self.import_permissions(op_id, ops.id, user.id)
elif attribute == "active":
if isinstance(value, str):
value = value.upper() == "TRUE"
setattr(operation, attribute, value)
db.session.commit()
return True
Expand All @@ -376,7 +368,7 @@ def delete_operation(self, op_id, user):
op_id: operation id
user: logged in user
"""
if self.auth_type(user.id, op_id) != "creator":
if not self.is_creator(user.id, op_id):
return False
Permission.query.filter_by(op_id=op_id).delete()
Change.query.filter_by(op_id=op_id).delete()
Expand Down
8 changes: 1 addition & 7 deletions mslib/mscolab/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
import logging
import fs
import git
import datetime
import dateutil.relativedelta
from sqlalchemy.exc import IntegrityError

from mslib.mscolab.conf import mscolab_settings
Expand Down Expand Up @@ -215,13 +213,9 @@ def archive_operation(path=None, emailid=None):
perm = Permission.query.filter_by(u_id=user.id, op_id=operation.id).first()
if perm is None:
return False
elif perm.access_level != "creator":
elif perm.access_level not in ["admin", "creator"]:
return False
operation.active = False
operation.last_used = (
datetime.datetime.now(tz=datetime.timezone.utc) -
dateutil.relativedelta.relativedelta(days=mscolab_settings.ARCHIVE_THRESHOLD)
)
db.session.commit()


Expand Down
13 changes: 3 additions & 10 deletions mslib/mscolab/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,12 +658,12 @@ def update_operation():
attribute = request.form['attribute']
value = request.form['value']
user = g.user
r = str(fm.update_operation(int(op_id), attribute, value, user))
if r == "True":
r = fm.update_operation(int(op_id), attribute, value, user)
if r is True:
token = request.args.get('token', request.form.get('token', False))
json_config = {"token": token}
sockio.sm.update_operation_list(json_config)
return r
return str(r)


@APP.route('/operation_details', methods=["GET"])
Expand All @@ -690,13 +690,6 @@ def set_last_used():
fm.update_operation(int(op_id), 'last_used',
datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(days=days_ago),
user)
if days_ago > mscolab_settings.ARCHIVE_THRESHOLD:
fm.update_operation(int(op_id), "active", False, user)
else:
fm.update_operation(int(op_id), "active", True, user)
token = request.args.get('token', request.form.get('token', False))
json_config = {"token": token}
sockio.sm.update_operation_list(json_config)
return jsonify({"success": True}), 200


Expand Down
29 changes: 12 additions & 17 deletions mslib/msui/mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def __init__(self, parent=None, mscolab=None):

def select_archived_operation(self, item):
logging.debug('select_inactive_operation')
if item.access_level == "creator":
if item.access_level in ["creator", "admin"]:
self.archived_op_id = item.op_id
self.pbUnarchiveOperation.setEnabled(True)
else:
Expand All @@ -101,20 +101,19 @@ def unarchive_operation(self):
data = {
"token": self.mscolab.token,
"op_id": self.archived_op_id,
"attribute": "active",
"value": "True"
}
url = urljoin(self.mscolab.mscolab_server_url, 'set_last_used')
url = urljoin(self.mscolab.mscolab_server_url, 'update_operation')
try:
res = requests.post(url, data=data, timeout=tuple(config_loader(dataset="MSCOLAB_timeout")))
except requests.exceptions.RequestException as e:
logging.debug(e)
show_popup(self.parent, "Error", "Some error occurred! Could not unarchive operation.")
self.logout()
else:
if res.text != "False":
res = res.json()
if res["success"]:
self.mscolab.reload_operations()
else:
show_popup(self.parent, "Error", "Some error occurred! Could not activate operation")
if res.text == "True":
self.mscolab.reload_operations()
else:
show_popup(self.parent, "Error", "Session expired, new login required")
self.mscolab.logout()
Expand Down Expand Up @@ -1818,9 +1817,10 @@ def archive_operation(self):
"token": self.token,
"op_id": self.active_op_id,
# when a user archives an operation we set the max “natural” integer in days
"days": 99999, # larger values run into a lot of issues
"attribute": "active",
"value": "False"
}
url = urljoin(self.mscolab_server_url, 'set_last_used')
url = urljoin(self.mscolab_server_url, 'update_operation')
try:
res = requests.post(url, data=data, timeout=tuple(config_loader(dataset="MSCOLAB_timeout")))
except requests.exceptions.RequestException as e:
Expand All @@ -1833,6 +1833,7 @@ def archive_operation(self):
logging.debug("activate local")
self.ui.listFlightTracks.setCurrentRow(0)
self.ui.activate_selected_flight_track()
self.active_op_id = None
else:
show_popup(self.ui, "Error", "Your Connection is expired. New Login required!")
self.logout()
Expand Down Expand Up @@ -1866,12 +1867,6 @@ def set_active_op_id(self, item):

self.signal_unarchive_operation.emit(self.active_op_id)

self.inactive_op_id = None
font = QtGui.QFont()
for i in range(self.ui.listOperationsMSC.count()):
self.ui.listOperationsMSC.item(i).setFont(font)
font.setBold(False)

# Set active operation description
self.set_operation_desc_label(self.active_operation_description)
# set active flightpath here
Expand Down Expand Up @@ -1970,14 +1965,14 @@ def show_operation_options(self):
self.ui.actionChangeDescription.setEnabled(True)
self.ui.filterCategoryCb.setEnabled(True)
self.ui.actionRenameOperation.setEnabled(True)
self.ui.actionArchiveOperation.setEnabled(True)
else:
if self.admin_window is not None:
self.admin_window.close()

if self.access_level in ["creator"]:
self.ui.actionDeleteOperation.setEnabled(True)
self.ui.actionLeaveOperation.setEnabled(False)
self.ui.actionArchiveOperation.setEnabled(True)

self.ui.menuImportFlightTrack.setEnabled(True)

Expand Down
41 changes: 41 additions & 0 deletions tests/_test_mscolab/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
import datetime
import pytest
import json
import io
Expand Down Expand Up @@ -402,11 +403,51 @@ def test_set_last_used(self):
assert add_user(self.userdata[0], self.userdata[1], self.userdata[2])
with self.app.test_client() as test_client:
operation, token = self._create_operation(test_client, self.userdata)
old = operation.last_used
response = test_client.post('/set_last_used', data={"token": token,
"op_id": operation.id})
assert response.status_code == 200
data = json.loads(response.data.decode('utf-8'))
assert data["success"] is True
new = operation.last_used
assert old != new
response = test_client.post('/set_last_used', data={"token": token,
"op_id": operation.id,
"days": 10})
assert response.status_code == 200
data = json.loads(response.data.decode('utf-8'))
assert data["success"] is True
new = operation.last_used
assert datetime.timedelta(days=11) > old - new > datetime.timedelta(days=9)

def test_set_active(self):
assert add_user(self.userdata[0], self.userdata[1], self.userdata[2])
with self.app.test_client() as test_client:
operation, token = self._create_operation(test_client, self.userdata)
assert operation.active is True
response = test_client.post('/update_operation', data={
"token": token,
"op_id": operation.id, "attribute": "active", "value": "False"})
assert response.status_code == 200
data = response.data.decode('utf-8')
assert data == "True"
assert operation.active is False

response = test_client.post('/update_operation', data={
"token": token,
"op_id": operation.id, "attribute": "active", "value": "True"})
assert response.status_code == 200
data = response.data.decode('utf-8')
assert data == "True"
assert operation.active is True

response = test_client.post('/update_operation', data={
"token": token,
"op_id": operation.id, "attribute": "active", "value": False})
assert response.status_code == 200
data = response.data.decode('utf-8')
assert data == "True"
assert operation.active is False

def test_get_users_without_permission(self):
assert add_user(self.userdata[0], self.userdata[1], self.userdata[2])
Expand Down
19 changes: 16 additions & 3 deletions tests/_test_msui/test_mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,7 @@ def test_add_operation(self, qtbot):
self._activate_operation_at_index(1)
assert self.window.mscolab.active_operation_name == "reproduce-test"

@mock.patch("PyQt5.QtWidgets.QInputDialog.getText", return_value=("flight7", True))
def test_handle_delete_operation(self, mocktext, qtbot):
def test_handle_delete_operation(self, qtbot):
self._connect_to_mscolab(qtbot)
modify_config_file({"MSS_auth": {self.url: "berta@something.org"}})
self._create_user(qtbot, "berta", "berta@something.org", "something")
Expand All @@ -676,11 +675,13 @@ def test_handle_delete_operation(self, mocktext, qtbot):
op_id = self.window.mscolab.get_recent_op_id()
assert op_id is not None
assert self.window.listOperationsMSC.model().rowCount() == 1
with mock.patch("PyQt5.QtWidgets.QMessageBox.information", return_value=QtWidgets.QMessageBox.Ok) as m:
with mock.patch("PyQt5.QtWidgets.QMessageBox.information", return_value=QtWidgets.QMessageBox.Ok) as m, \
mock.patch("PyQt5.QtWidgets.QInputDialog.getText", return_value=("flight7", True)):
self.window.actionDeleteOperation.trigger()
qtbot.wait_until(
lambda: m.assert_called_once_with(self.window, "Success", 'Operation "flight7" was deleted!')
)
assert self.window.mscolab.active_op_id is None
op_id = self.window.mscolab.get_recent_op_id()
assert op_id is None
# check operation dir name removed
Expand Down Expand Up @@ -744,6 +745,18 @@ def test_update_description(self, mocktext, qtbot):
assert self.window.mscolab.active_op_id is not None
assert self.window.mscolab.active_operation_description == "new_description"

def test_archive_operation(self, qtbot):
self._connect_to_mscolab(qtbot)
modify_config_file({"MSS_auth": {self.url: "something@something.org"}})
self._create_user(qtbot, "something", "something@something.org", "something")
self._create_operation(qtbot, "flight1234", "Description flight1234")
assert self.window.listOperationsMSC.model().rowCount() == 1
self._activate_operation_at_index(0)
assert self.window.mscolab.active_op_id is not None
with mock.patch("PyQt5.QtWidgets.QMessageBox.warning", return_value=QtWidgets.QMessageBox.Yes):
self.window.actionArchiveOperation.trigger()
assert self.window.mscolab.active_op_id is None

@mock.patch("PyQt5.QtWidgets.QInputDialog.getText", return_value=("new_category", True))
def test_update_category(self, mocktext, qtbot):
self._connect_to_mscolab(qtbot)
Expand Down

0 comments on commit 52c7a04

Please sign in to comment.