Skip to content

Commit

Permalink
Add boss db dataset (#4036)
Browse files Browse the repository at this point in the history
* add tab to add bossdb dataset

* upgrade antd to newest version

* use Input.Password antd component which allows to show password

* fix tests by not polluting global namespace

* fix window mocking in non-test env

* update changelog

* fix pretty

* Add Neuroglancer Dataset with Authentication (#4037)

* add authentication upload to neuroglancer add dataset view

* fix linting

* indicate that credentials are optional and when they are needed

* upgrade wk-connect in docker-compose

* pretty
  • Loading branch information
daniel-wer authored Apr 30, 2019
1 parent f7ec620 commit 8e5d14b
Show file tree
Hide file tree
Showing 22 changed files with 623 additions and 273 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
[Commits](https://github.com/scalableminds/webknossos/compare/19.04.0...HEAD)

### Added
- BossDB datasets can now be added to webKnossos using the webknossos-connect service. [#4036](https://github.com/scalableminds/webknossos/pull/4036)
-

### Changed
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ services:

# webKnossos-connect
webknossos-connect:
image: scalableminds/webknossos-connect:master__190
image: scalableminds/webknossos-connect:master__205
volumes:
- "./conf/connect:/app/data"
# Publish webknossos-connect ports in webknossos container
Expand Down
2 changes: 2 additions & 0 deletions flow-typed/npm/antd_vx.x.x.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ declare module "antd" {
declare export class Dropdown<P> extends React$Component<P> {}
declare export class Icon<P> extends React$Component<P> {}
declare class InputGroup<P> extends React$Component<P> {}
declare class InputPassword<P> extends React$Component<P> {}
declare class InputSearch<P> extends React$Component<P> {}
declare class InputTextArea<P> extends React$Component<P> {}
declare export class Input<P> extends React$Component<P> {
static Group: typeof InputGroup;
static Password: typeof InputPassword;
static Search: typeof InputSearch;
static TextArea: typeof InputTextArea;
focus: () => void;
Expand Down
30 changes: 23 additions & 7 deletions frontend/javascripts/admin/api_flow_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,22 +380,38 @@ export type DatasetConfig = {
+zipFile: File,
};

type WkConnectLayer = {
type NeuroglancerLayer = {
// This is the source URL of the layer, should start with gs://, http:// or https://
source: string,
type: "image" | "segmentation",
};

export type WkConnectDatasetConfig = {
neuroglancer: {
[organizationName: string]: {
[datasetName: string]: {
layers: { [layerName: string]: WkConnectLayer },
},
type NeuroglancerDatasetConfig = {
[organizationName: string]: {
[datasetName: string]: {
layers: { [layerName: string]: NeuroglancerLayer },
credentials?: Object,
},
},
};

type BossDatasetConfig = {
[organizationName: string]: {
[datasetName: string]: {
domain: string,
collection: string,
experiment: string,
username: string,
password: string,
},
},
};

export type WkConnectDatasetConfig = {
neuroglancer?: NeuroglancerDatasetConfig,
boss?: BossDatasetConfig,
};

export type APITimeTracking = {
time: string,
timestamp: number,
Expand Down
9 changes: 4 additions & 5 deletions frontend/javascripts/admin/auth/change_password_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Store from "oxalis/store";
import type { RouterHistory } from "react-router-dom";

const FormItem = Form.Item;
const { Password } = Input;

type Props = {
form: Object,
Expand Down Expand Up @@ -86,7 +87,7 @@ class ChangePasswordView extends React.PureComponent<Props, State> {
message: messages["auth.reset_old_password"],
},
],
})(<Input type="password" placeholder="Old Password" />)}
})(<Password placeholder="Old Password" />)}
</FormItem>
<FormItem hasFeedback>
{getFieldDecorator("password.password1", {
Expand All @@ -104,8 +105,7 @@ class ChangePasswordView extends React.PureComponent<Props, State> {
},
],
})(
<Input
type="password"
<Password
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
placeholder="New Password"
/>,
Expand All @@ -127,8 +127,7 @@ class ChangePasswordView extends React.PureComponent<Props, State> {
},
],
})(
<Input
type="password"
<Password
onBlur={this.handleConfirmBlur}
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
placeholder="Confirm New Password"
Expand Down
7 changes: 3 additions & 4 deletions frontend/javascripts/admin/auth/finish_reset_password_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Toast from "libs/toast";
import type { RouterHistory } from "react-router-dom";

const FormItem = Form.Item;
const { Password } = Input;

type Props = {
form: Object,
Expand Down Expand Up @@ -89,8 +90,7 @@ class FinishResetPasswordView extends React.PureComponent<Props, State> {
},
],
})(
<Input
type="password"
<Password
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
placeholder="New Password"
/>,
Expand All @@ -112,8 +112,7 @@ class FinishResetPasswordView extends React.PureComponent<Props, State> {
},
],
})(
<Input
type="password"
<Password
onBlur={this.handleConfirmBlur}
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
placeholder="Confirm New Password"
Expand Down
4 changes: 2 additions & 2 deletions frontend/javascripts/admin/auth/login_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Store from "oxalis/store";
import messages from "messages";

const FormItem = Form.Item;
const { Password } = Input;

type Props = {
layout: "horizontal" | "vertical" | "inline",
Expand Down Expand Up @@ -71,9 +72,8 @@ function LoginForm({ layout, form, onLoggedIn, hideFooter, style }: Props) {
{getFieldDecorator("password", {
rules: [{ required: true, message: messages["auth.registration_password_input"] }],
})(
<Input
<Password
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
type="password"
placeholder="Password"
/>,
)}
Expand Down
7 changes: 3 additions & 4 deletions frontend/javascripts/admin/auth/registration_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { setHasOrganizationsAction } from "oxalis/model/actions/ui_actions";

const FormItem = Form.Item;
const { Option } = Select;
const { Password } = Input;

type Props = {|
form: Object,
Expand Down Expand Up @@ -240,8 +241,7 @@ class RegistrationForm extends React.PureComponent<Props, State> {
},
],
})(
<Input
type="password"
<Password
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
placeholder="Password"
/>,
Expand All @@ -265,8 +265,7 @@ class RegistrationForm extends React.PureComponent<Props, State> {
},
],
})(
<Input
type="password"
<Password
onBlur={this.handleConfirmBlur}
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
placeholder="Confirm Password"
Expand Down
3 changes: 2 additions & 1 deletion frontend/javascripts/admin/auth/registration_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class RegistrationView extends React.PureComponent<Props> {
<Link to="/onboarding">Create a new organization.</Link>
) : (
<React.Fragment>
Contact <a href="mailto:[email protected]">[email protected]</a> for help on setting up webKnossos.
Contact <a href="mailto:[email protected]">[email protected]</a> for help on
setting up webKnossos.
</React.Fragment>
)}
</Card>
Expand Down
157 changes: 157 additions & 0 deletions frontend/javascripts/admin/dataset/dataset_add_boss_view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// @flow
import { Form, Input, Button, Col, Row } from "antd";
import { connect } from "react-redux";
import React from "react";
import _ from "lodash";

import type { APIDataStore, APIUser } from "admin/api_flow_types";
import type { OxalisState } from "oxalis/store";
import { addWkConnectDataset } from "admin/admin_rest_api";
import messages from "messages";
import Toast from "libs/toast";
import * as Utils from "libs/utils";
import { trackAction } from "oxalis/model/helpers/analytics";
import {
CardContainer,
DatasetNameFormItem,
DatastoreFormItem,
} from "admin/dataset/dataset_components";

const FormItem = Form.Item;
const { Password } = Input;

const Slash = () => (
<Col span={1} style={{ textAlign: "center" }}>
<div style={{ marginTop: 35 }}>/</div>
</Col>
);

type OwnProps = {|
datastores: Array<APIDataStore>,
onAdded: (string, string) => void,
|};
type StateProps = {|
activeUser: ?APIUser,
|};
type Props = {| ...OwnProps, ...StateProps |};
type PropsWithForm = {|
...Props,
form: Object,
|};

class DatasetAddBossView extends React.PureComponent<PropsWithForm> {
handleSubmit = evt => {
evt.preventDefault();
const { activeUser } = this.props;

this.props.form.validateFields(async (err, formValues) => {
if (err || activeUser == null) return;

const { name, domain, collection, experiment, username, password } = formValues;
const httpsDomain = domain.startsWith("bossdb://")
? domain.replace(/^bossdb/, "https")
: domain;
const datasetConfig = {
boss: {
[activeUser.organization]: {
[name]: {
domain: httpsDomain,
collection,
experiment,
username,
password,
},
},
},
};

trackAction("Add BossDB dataset");
await addWkConnectDataset(formValues.datastore, datasetConfig);

Toast.success(messages["dataset.add_success"]);
await Utils.sleep(3000); // wait for 3 seconds so the server can catch up / do its thing
this.props.onAdded(activeUser.organization, formValues.name);
});
};

render() {
const { form, activeUser, datastores } = this.props;
const { getFieldDecorator } = form;

return (
<div style={{ padding: 5 }}>
<CardContainer title="Add BossDB Dataset">
<Form style={{ marginTop: 20 }} onSubmit={this.handleSubmit} layout="vertical">
<Row gutter={8}>
<Col span={12}>
<DatasetNameFormItem form={form} activeUser={activeUser} />
</Col>
<Col span={12}>
<DatastoreFormItem form={form} datastores={datastores} />
</Col>
</Row>
<Row gutter={8}>
<Col span={12}>
<FormItem label="Domain" hasFeedback>
{getFieldDecorator("domain", {
rules: [{ required: true }],
validateFirst: true,
})(<Input />)}
</FormItem>
</Col>
<Slash />
<Col span={5}>
<FormItem label="Collection" hasFeedback>
{getFieldDecorator("collection", {
rules: [{ required: true }],
validateFirst: true,
})(<Input />)}
</FormItem>
</Col>
<Slash />
<Col span={5}>
<FormItem label="Experiment" hasFeedback>
{getFieldDecorator("experiment", {
rules: [{ required: true }],
validateFirst: true,
})(<Input />)}
</FormItem>
</Col>
</Row>
<Row gutter={8}>
<Col span={12}>
<FormItem label="Username" hasFeedback>
{getFieldDecorator("username", {
rules: [{ required: true }],
validateFirst: true,
})(<Input />)}
</FormItem>
</Col>
<Col span={12}>
<FormItem label="Password" hasFeedback>
{getFieldDecorator("password", {
rules: [{ required: true }],
validateFirst: true,
})(<Password />)}
</FormItem>
</Col>
</Row>
<FormItem style={{ marginBottom: 0 }}>
<Button size="large" type="primary" htmlType="submit" style={{ width: "100%" }}>
Add
</Button>
</FormItem>
</Form>
</CardContainer>
</div>
);
}
}

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

export default connect<Props, OwnProps, _, _, _, _>(mapStateToProps)(
Form.create()(DatasetAddBossView),
);
Loading

0 comments on commit 8e5d14b

Please sign in to comment.