Skip to content

Commit

Permalink
Reject dataset uploads if organization storage quota is exceeded (#6893)
Browse files Browse the repository at this point in the history
* Reject dataset uploads if organization storage quota is exceeded

* warn user in upload-dataset-view when storage is exceeded and disable upload button

* changelog

---------

Co-authored-by: Philipp Otto <[email protected]>
Co-authored-by: Philipp Otto <[email protected]>
  • Loading branch information
3 people authored Mar 27, 2023
1 parent 8c79390 commit 80fa509
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Interpolation during rendering is now more performance intensive, since the rendering approach was changed. Therefore, interpolation is disabled by default. On the flip side, the rendered quality is often higher than it used to be. [#6748](https://github.com/scalableminds/webknossos/pull/6748)
- Updated the styling of the "welcome" screen for new users to be in line with the new branding. [#6904](https://github.com/scalableminds/webknossos/pull/6904)
- Improved Terms-of-Service modal (e.g., allow to switch organization even when modal was blocking the remaining usage of WEBKNOSSOS). [#6930](https://github.com/scalableminds/webknossos/pull/6930)
- Uploads are now blocked when the organization’s storage quota is exceeded. [#6893](https://github.com/scalableminds/webknossos/pull/6893)

### Fixed
- Fixed an issue with text hints not being visible on the logout page for dark mode users. [#6916](https://github.com/scalableminds/webknossos/pull/6916)
Expand Down
3 changes: 3 additions & 0 deletions app/controllers/WKRemoteDataStoreController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class WKRemoteDataStoreController @Inject()(
organization <- organizationDAO.findOneByName(uploadInfo.organization)(GlobalAccessContext) ?~> Messages(
"organization.notFound",
uploadInfo.organization) ~> NOT_FOUND
usedStorageBytes <- organizationDAO.getUsedStorage(organization._id)
_ <- Fox.runOptional(organization.includedStorageBytes)(includedStorage =>
bool2Fox(usedStorageBytes <= includedStorage)) ?~> "dataSet.upload.storageExceeded" ~> FORBIDDEN
_ <- bool2Fox(organization._id == user._organization) ?~> "notAllowed" ~> FORBIDDEN
_ <- dataSetService.assertValidDataSetName(uploadInfo.name)
_ <- dataSetService.assertNewDataSetName(uploadInfo.name, organization._id) ?~> "dataSet.name.alreadyTaken"
Expand Down
1 change: 1 addition & 0 deletions conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dataSet.upload.validation.failed=Failed to validate Dataset information for uplo
dataSet.upload.linkRestricted=Can only link layers of datasets that are either public or allowed to be administrated by your account
dataSet.upload.invalidLinkedLayers=Could not link all requested layers
dataSet.upload.noFiles=Tried to finish upload with no files. May be a retry of a failed finish request, see previous errors.
dataSet.upload.storageExceeded=Cannot upload dataset because the storage quota of the organization is exceeded.
dataSet.explore.failed.readFile=Failed to read remote file
dataSet.explore.magDtypeMismatch=Element class must be the same for all mags of a layer. Got {0}

Expand Down
32 changes: 30 additions & 2 deletions frontend/javascripts/admin/dataset/dataset_upload_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ import classnames from "classnames";
import _ from "lodash";
import { useDropzone, FileWithPath } from "react-dropzone";
import ErrorHandling from "libs/error_handling";
import type { RouteComponentProps } from "react-router-dom";
import { Link, RouteComponentProps } from "react-router-dom";
import { withRouter } from "react-router-dom";
import type { APITeam, APIDataStore, APIUser, APIDatasetId } from "types/api_flow_types";
import type {
APITeam,
APIDataStore,
APIUser,
APIDatasetId,
APIOrganization,
} from "types/api_flow_types";
import type { OxalisState } from "oxalis/store";
import {
reserveDatasetUpload,
Expand Down Expand Up @@ -41,6 +47,8 @@ import { FormInstance } from "antd/lib/form";
import type { Vector3 } from "oxalis/constants";
import { FormItemWithInfo, confirmAsync } from "../../dashboard/dataset/helper_components";
import FolderSelection from "dashboard/folders/folder_selection";
import { hasPricingPlanExceededStorage } from "admin/organization/pricing_plan_utils";
import { enforceActiveOrganization } from "oxalis/model/accessors/organization_accessors";

const FormItem = Form.Item;
const REPORT_THROTTLE_THRESHOLD = 1 * 60 * 1000; // 1 min
Expand All @@ -56,6 +64,7 @@ type OwnProps = {
};
type StateProps = {
activeUser: APIUser | null | undefined;
organization: APIOrganization;
};
type Props = OwnProps & StateProps;
type PropsWithFormAndRouter = Props & {
Expand Down Expand Up @@ -613,6 +622,23 @@ class DatasetUploadView extends React.Component<PropsWithFormAndRouter, State> {
}}
>
<CardContainer withoutCard={withoutCard} title="Upload Dataset">
{hasPricingPlanExceededStorage(this.props.organization) ? (
<Alert
type="error"
message={
<>
Your organization has exceeded the available storage. Uploading new datasets is
disabled. Visit the{" "}
<Link to={`/organizations/${this.props.organization.name}`}>
organization page
</Link>{" "}
for details.
</>
}
style={{ marginBottom: 8 }}
/>
) : null}

<Form
onFinish={this.handleSubmit}
layout="vertical"
Expand Down Expand Up @@ -856,6 +882,7 @@ class DatasetUploadView extends React.Component<PropsWithFormAndRouter, State> {
size="large"
type="primary"
htmlType="submit"
disabled={hasPricingPlanExceededStorage(this.props.organization)}
style={{
width: "100%",
}}
Expand Down Expand Up @@ -1043,6 +1070,7 @@ function FileUploadArea({

const mapStateToProps = (state: OxalisState): StateProps => ({
activeUser: state.activeUser,
organization: enforceActiveOrganization(state.activeOrganization),
});

const connector = connect(mapStateToProps);
Expand Down

0 comments on commit 80fa509

Please sign in to comment.