Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul admin interface #479

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 28 additions & 29 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
Changelog
=========


Development
-----------
----------

Added
~~~~~

- `[#443]`_: Added support and testing for Python 3.11
- `[#479]`_: A "Filters" resource to the admin interface, allowing users to view, edit and delete filters there. This resolves `[#476]`_.
- `[#433]`_: "Statistic" selector that allows the selection of "measurement" or "isolation forest". The isolation forest statistic plots the multivariate outlier score in a non-parametric way, with an adjustable threshold
- `[#479]`_: Give all users access to the admin interface. Unprivileged users won't be able to edit or delete most entities, however
- `[#479]`_: Consistently added show, edit and delete buttons to all pages of the admin interface

- `[#433]`_ Rewrite of trend chart outlier detection
* Added a "statistic" selector that allows the selection of "measurement" or "isolation forest"
* Remove outlier detection from normal measurement plot, because the normal assumption is not reasonable
* The isolation forest statistic plots the multivariate outlier score in a non-parametric way, with an adjustable threshold
Changed
~~~~~~~

- `[#479]`_: Allow unprivileged users to create new filters and delete their own filters via the REST API. This also resolves issues where these users couldn't create their own filters on certain pages.
- `[#479]`_: The "Edit Filters" page now redirects to the Filters tab of the Data Admin page. This relates to `[#476]`_.
- `[#440]`_ Set up poetry to manage the dependencies, which might keep the dependencies from breaking down, and reduce the chances of the happening of issues such as `[#430]`_
- `[#443]`_
* Dropped support for Python 3.6, added support and testing for Python <= 3.11
* Added docker-compose logging in the CI
* Stopped using Meinheld workers in the Docker image, since this is largely unmaintained
* Fix a bug in the database script when constructing URLs that broken under new SQLAlchemy versions
* Bump pytest
* Fix a bug in pytest where we used `scope` as a positional argument
* Update the SubFactoryList to a new version that works with newer FactoryBoy versions

.. _[#430]: https://github.com/ewels/MegaQC/issues/430
.. _[#440]: https://github.com/ewels/MegaQC/pull/440
.. _[#433]: https://github.com/ewels/MegaQC/pull/433
Fixed
~~~~~

=======
- `[#443]`_: Fix a bug in the database script when constructing URLs that broken under new SQLAlchemy versions

.. _section-1:
Removed
~~~~~~~

- `[#433]`_ Removed outlier detection from normal measurement plot, because the normal assumption is not reasonable
- `[#443]`_: Dropped support for Python 3.6

0.3.0
-----

.. _breaking-changes-1:

Breaking Changes
~~~~~~~~~~~~~~~~

Expand All @@ -56,18 +58,13 @@ Breaking Changes

- Dropped support for Node 8

.. _new-features-1:

New Features
~~~~~~~~~~~~

- `[#140]`_ Added a changelog. It’s here! You’re reading it!
- Sphinx based documentation on Github Pages
- `[#69]`_ Added a check to verify that a database exists and exit nicely if not


.. _bug-fixes-1:

Bug Fixes
~~~~~~~~~

Expand All @@ -77,16 +74,13 @@ Bug Fixes
- `[#170]`_ Improved handling of environment variables with environs
- `[#194]`_ Forward more headers through nginx when using Docker Compose. This should avoid bad HTTP redirects.

.. _internal-changes-1:

Internal Changes
~~~~~~~~~~~~~~~~

- Tests for the REST API permissions
- Enforce inactive users (by default) in the model layer
- Many and more dependency updates


.. _[#69]: https://github.com/ewels/MegaQC/issues/69
.. _[#138]: https://github.com/ewels/MegaQC/issues/138
.. _[#139]: https://github.com/ewels/MegaQC/issues/139
Expand All @@ -95,4 +89,9 @@ Internal Changes
.. _[#156]: https://github.com/ewels/MegaQC/issues/156
.. _[#170]: https://github.com/ewels/MegaQC/issues/170
.. _[#194]: https://github.com/ewels/MegaQC/issues/194
.. _[#430]: https://github.com/ewels/MegaQC/issues/430
.. _[#433]: https://github.com/ewels/MegaQC/pull/433
.. _[#440]: https://github.com/ewels/MegaQC/pull/440
.. _[#443]: https://github.com/ewels/MegaQC/pull/443
.. _[#476]: https://github.com/ewels/MegaQC/issues/476
.. _[#479]: https://github.com/ewels/MegaQC/issues/479
31 changes: 30 additions & 1 deletion megaqc/rest_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
JSON API standard where relevant: https://jsonapi.org/format/
"""

import typing
from hashlib import sha1
from http import HTTPStatus

from flapison import ResourceDetail, ResourceList, ResourceRelationship
from flapison import (
JsonApiException,
ResourceDetail,
ResourceList,
ResourceRelationship,
)
from flapison.schema import get_nested_fields, get_relationships
from flask import Blueprint, current_app, jsonify, make_response, request
from flask_login import current_user, login_required
Expand Down Expand Up @@ -284,11 +290,34 @@ class FilterList(PermissionsMixin, ResourceList):
schema = schemas.SampleFilterSchema
data_layer = dict(session=db.session, model=models.SampleFilter)

# Users should be able to create new filters
@api_perms(Permission.USER)
def post(self, **kwargs):
# Bypass the default admin-only permissions inherited from the parent class
return ResourceList.post(self, **kwargs)


class Filter(PermissionsMixin, ResourceDetail):
schema = schemas.SampleFilterSchema
data_layer = dict(session=db.session, model=models.SampleFilter)

@api_perms(Permission.USER)
def delete(self, **kwargs):
if kwargs["permission"] == Permission.USER:
# Users should be able to delete their own filters
filter = db.session.query(models.SampleFilter).get(kwargs["id"])
if filter is not None:
filter = typing.cast(models.SampleFilter, filter)
if filter.user_id != kwargs["user"].user_id:
raise JsonApiException(
title="Insufficient permissions to access this resource",
detail="You do not own this filter",
status=403,
)

# Bypass the default admin-only permissions inherited from the parent class
return ResourceDetail.delete(self, **kwargs)


class FilterRelationship(PermissionsMixin, ResourceRelationship):
schema = schemas.SampleFilterSchema
Expand Down
6 changes: 3 additions & 3 deletions megaqc/templates/nav.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ <h6 class="dropdown-header">Plots and Dashboards</h6>
<a class="dropdown-item" href="{{ url_for('public.create_dashboard') }}">Create Dashboard</a>
<a class="dropdown-item" href="{{ url_for('public.plot_favourites') }}">Favourite Plots</a>
<div class="dropdown-divider"></div>
<h6 class="dropdown-header">Uploads and Filters</h6>
<h6 class="dropdown-header">Manage Data</h6>
<a class="dropdown-item" href="{{ url_for('public.queued_uploads') }}">Pending Uploads</a>
<a class="dropdown-item" href="{{ url_for('public.edit_filters') }}">Edit Filters</a>
<a class="dropdown-item" href="{{ url_for('public.admin', _anchor='filters') }}">Edit Filters</a>
<a class="dropdown-item" href="{{ url_for('public.edit_reports') }}">Edit Reports</a>
<a class="dropdown-item" href="{{ url_for('public.admin') }}">Data Admin</a>
{% if current_user.is_admin %}
<div class="dropdown-divider"></div>
<h6 class="dropdown-header">Administration Pages</h6>
<a class="dropdown-item" href="{{ url_for('user.manage_users') }}">Manage Users</a>
<a class="dropdown-item" href="{{ url_for('public.admin') }}">Data Admin</a>
{% endif %}
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="{{ url_for('public.logout') }}">Log Out</a>
Expand Down
7 changes: 7 additions & 0 deletions src/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
DashboardEdit,
DashboardShow,
} from "./admin/dashboards";
import { FilterList, FilterEdit, FilterShow } from "./admin/filter";
import { DataList, DataEdit, DataShow, DataCreate } from "./admin/sampleData";
import { UserList, UserEdit, UserShow } from "./admin/user";
import { getClient, getToken } from "./util/api";
Expand Down Expand Up @@ -133,6 +134,12 @@ function App() {
show={UserShow}
edit={UserEdit}
/>
<Resource
name="filters"
list={FilterList}
edit={FilterEdit}
show={FilterShow}
/>
<Resource name="filter_groups" list={FilterGroupList} />
<Resource
name="favourites"
Expand Down
6 changes: 6 additions & 0 deletions src/admin/dashboards.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
SimpleShowLayout,
TextField,
TextInput,
EditButton,
ShowButton,
DeleteButton,
} from "react-admin";
import { JsonInput, JsonField } from "./components/jsonField";

Expand All @@ -23,6 +26,9 @@ export const DashboardList = (props) => (
<DateField source="created_at" />
<DateField source="modified_at" />
<BooleanField source="is_public" />
<EditButton />
<ShowButton />
<DeleteButton />
</Datagrid>
</List>
);
Expand Down
6 changes: 6 additions & 0 deletions src/admin/dataType.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
SimpleShowLayout,
TextField,
TextInput,
EditButton,
ShowButton,
DeleteButton,
} from "react-admin";

export const DataTypeList = (props) => (
Expand All @@ -17,6 +20,9 @@ export const DataTypeList = (props) => (
<TextField source="id" />
<TextField source="section" />
<TextField source="key" />
<EditButton />
<ShowButton />
<DeleteButton />
</Datagrid>
</List>
);
Expand Down
6 changes: 6 additions & 0 deletions src/admin/favourite.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
SimpleShowLayout,
TextField,
TextInput,
EditButton,
ShowButton,
DeleteButton,
} from "react-admin";
import { JsonInput, JsonField } from "./components/jsonField";

Expand All @@ -21,6 +24,9 @@ export const FavouriteList = (props) => (
<TextField source="description" />
<TextField source="plot_type" />
<DateField source="created_at" />
<EditButton />
<ShowButton />
<DeleteButton />
</Datagrid>
</List>
);
Expand Down
83 changes: 83 additions & 0 deletions src/admin/filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from "react";
import {
Datagrid,
DateField,
DateInput,
Edit,
List,
Show,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
ReferenceField,
EditButton,
ShowButton,
BooleanField,
DeleteButton,
BooleanInput,
} from "react-admin";
import { JsonInput, JsonField } from "./components/jsonField";

export const FilterList = (props) => (
<List {...props}>
<Datagrid rowClick="show">
<TextField source="id" />
<TextField source="name" />
<TextField source="tag" />
<BooleanField source="public" />
<ReferenceField
link="show"
label="Owner"
source="user.id"
reference="users"
>
<TextField source="username" />
</ReferenceField>
<TextField source="data" />
<EditButton />
<ShowButton />
<DeleteButton />
</Datagrid>
</List>
);

export const FilterShow = (props) => (
<Show {...props}>
<SimpleShowLayout>
<TextField source="id" />
<TextField source="name" />
<TextField source="tag" />
<BooleanField source="public" />
<ReferenceField
link="show"
label="Owner"
source="user.id"
reference="users"
>
<TextField source="username" />
</ReferenceField>
<TextField source="data" />
</SimpleShowLayout>
</Show>
);

export const FilterEdit = (props) => (
<Edit {...props}>
<SimpleForm redirect="show">
<TextInput source="id" />
<TextInput source="name" />
<TextInput source="tag" />
<BooleanInput source="public" />
<ReferenceField
link="show"
label="Owner"
source="user.id"
reference="users"
>
<TextField source="username" />
</ReferenceField>
<JsonInput source="data" />
</SimpleForm>
</Edit>
);
12 changes: 11 additions & 1 deletion src/admin/filterGroup.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import React from "react";
import { Datagrid, List, TextField } from "react-admin";
import {
Datagrid,
List,
TextField,
EditButton,
ShowButton,
DeleteButton,
} from "react-admin";

export const FilterGroupList = (props) => (
<List {...props}>
<Datagrid rowClick="show">
<TextField source="id" />
<EditButton />
<ShowButton />
<DeleteButton />
</Datagrid>
</List>
);
6 changes: 6 additions & 0 deletions src/admin/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
SimpleShowLayout,
TextField,
TextInput,
EditButton,
ShowButton,
DeleteButton,
} from "react-admin";

import DefaultForm from "./components/defaultForm";
Expand All @@ -30,6 +33,9 @@ export const ReportMetaList = (props) => (
>
<TextField source="id" />
</ReferenceField>
<EditButton />
<ShowButton />
<DeleteButton />
</Datagrid>
</List>
);
Expand Down
Loading