Skip to content

Commit 55640d5

Browse files
authored
Revert "Revert "Add Recommended Configuration to Task Types"" (#3504)
* Revert "Revert "Add Recommended Configuration to Task Types (#3466)" (#3502)" This reverts commit 246fbae. * add route to change the lasttasktypeid * use new taskTypeId route to update user's lastTaskTypeId * add display names and comments to recommended settings * add buttons to remove some of the recommended configuration quickly * uppercase button caption
1 parent 82456c1 commit 55640d5

40 files changed

+707
-130
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
1111

1212
### Added
1313

14-
-
14+
- Added the possibility to specify a recommended user configuration in a task type. The recommended configuration will be shown to users when they trace a task with a different task type and the configuration can be accepted or declined. [#3466](https://github.com/scalableminds/webknossos/pull/3466)
1515

1616
### Changed
1717

MIGRATIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ User-facing changes are documented in the [changelog](CHANGELOG.md).
88
-
99

1010
### Postgres Evolutions:
11-
-
11+
- [036-add-lastTaskTypeId-to-user.sql](conf/evolutions/036-add-lastTaskTypeId-to-user.sql)
1212

1313

1414
## [18.12.0](https://github.com/scalableminds/webknossos/releases/tag/18.12.0) - 2018-11-26

app/assets/javascripts/admin/admin_rest_api.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,23 @@ export function getUser(userId: string): Promise<APIUser> {
142142
return Request.receiveJSON(`/api/users/${userId}`);
143143
}
144144

145-
export function updateUser(newUser: APIUser): Promise<APIUser> {
145+
export function updateUser(newUser: $Shape<APIUser>): Promise<APIUser> {
146146
return Request.sendJSONReceiveJSON(`/api/users/${newUser.id}`, {
147-
method: "PUT",
147+
method: "PATCH",
148148
data: newUser,
149149
});
150150
}
151151

152+
export function updateLastTaskTypeIdOfUser(
153+
userId: string,
154+
lastTaskTypeId: string,
155+
): Promise<APIUser> {
156+
return Request.sendJSONReceiveJSON(`/api/users/${userId}/taskTypeId`, {
157+
method: "PUT",
158+
data: { lastTaskTypeId },
159+
});
160+
}
161+
152162
export async function getAuthToken(): Promise<string> {
153163
const { token } = await Request.receiveJSON("/api/auth/token");
154164
return token;

app/assets/javascripts/admin/api_flow_types.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export type APIUser = APIUserBase & {
137137
+isActive: boolean,
138138
+isEditable: boolean,
139139
+lastActivity: number,
140+
+lastTaskTypeId: ?string,
140141
+organization: string,
141142
};
142143

@@ -190,6 +191,7 @@ export type APITaskType = {
190191
+teamId: string,
191192
+teamName: string,
192193
+settings: APISettings,
194+
+recommendedConfiguration: ?string,
193195
};
194196

195197
export type TaskStatus = { +open: number, +active: number, +finished: number };
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// @flow
2+
import { Checkbox, Col, Collapse, Form, Icon, Input, Row, Tooltip, Table, Button } from "antd";
3+
import * as React from "react";
4+
import _ from "lodash";
5+
6+
import type { DatasetConfiguration, UserConfiguration } from "oxalis/store";
7+
import { jsonEditStyle } from "dashboard/dataset/helper_components";
8+
import { jsonStringify } from "libs/utils";
9+
import { settings } from "messages";
10+
import { validateUserSettingsJSON } from "dashboard/dataset/validation";
11+
12+
const FormItem = Form.Item;
13+
const { Panel } = Collapse;
14+
15+
const recommendedConfigByCategory = {
16+
orthogonal: {
17+
clippingDistance: 80,
18+
moveValue: 500,
19+
displayScalebars: false,
20+
newNodeNewTree: false,
21+
tdViewDisplayPlanes: false,
22+
},
23+
all: {
24+
dynamicSpaceDirection: true,
25+
highlightCommentedNodes: false,
26+
overrideNodeRadius: true,
27+
particleSize: 5,
28+
keyboardDelay: 0,
29+
displayCrosshair: true,
30+
},
31+
dataset: {
32+
fourBit: false,
33+
interpolation: true,
34+
quality: 0,
35+
segmentationOpacity: 0,
36+
highlightHoveredCellId: false,
37+
zoom: 0.8,
38+
renderMissingDataBlack: false,
39+
},
40+
flight: {
41+
clippingDistanceArbitrary: 60,
42+
moveValue3d: 600,
43+
mouseRotateValue: 0.001,
44+
rotateValue: 0.01,
45+
sphericalCapRadius: 500,
46+
},
47+
};
48+
49+
export const DEFAULT_RECOMMENDED_CONFIGURATION: $Shape<{|
50+
...UserConfiguration,
51+
...DatasetConfiguration,
52+
|}> = {
53+
...recommendedConfigByCategory.orthogonal,
54+
...recommendedConfigByCategory.all,
55+
...recommendedConfigByCategory.dataset,
56+
...recommendedConfigByCategory.flight,
57+
};
58+
59+
export const settingComments = {
60+
clippingDistance: "orthogonal mode",
61+
moveValue: "orthogonal mode",
62+
quality: "0 (high), 1 (medium), 2 (low)",
63+
clippingDistanceArbitrary: "flight/oblique mode",
64+
moveValue3d: "flight/oblique mode",
65+
};
66+
67+
const errorIcon = (
68+
<Tooltip title="The recommended user settings JSON has errors.">
69+
<Icon type="exclamation-circle" style={{ color: "#f5222d", marginLeft: 4 }} />
70+
</Tooltip>
71+
);
72+
73+
const columns = [
74+
{
75+
title: "Display Name",
76+
dataIndex: "name",
77+
},
78+
{
79+
title: "Key",
80+
dataIndex: "key",
81+
},
82+
{
83+
title: "Default Value",
84+
dataIndex: "value",
85+
},
86+
{
87+
title: "Comment",
88+
dataIndex: "comment",
89+
},
90+
];
91+
92+
const removeSettings = (form, settingsKey: string) => {
93+
const settingsString = form.getFieldValue("recommendedConfiguration");
94+
try {
95+
const settingsObject = JSON.parse(settingsString);
96+
const newSettings = _.omit(
97+
settingsObject,
98+
Object.keys(recommendedConfigByCategory[settingsKey]),
99+
);
100+
form.setFieldsValue({ recommendedConfiguration: jsonStringify(newSettings) });
101+
} catch (e) {
102+
console.error(e);
103+
}
104+
};
105+
106+
export default function RecommendedConfigurationView({
107+
form,
108+
enabled,
109+
onChangeEnabled,
110+
}: {
111+
form: Object,
112+
enabled: boolean,
113+
onChangeEnabled: boolean => void,
114+
}) {
115+
return (
116+
<Collapse
117+
onChange={openedPanels => onChangeEnabled(openedPanels.length === 1)}
118+
activeKey={enabled ? "config" : null}
119+
>
120+
<Panel
121+
key="config"
122+
header={
123+
<React.Fragment>
124+
<Checkbox checked={enabled} style={{ marginRight: 10 }} /> Add Recommended User Settings
125+
{enabled && form.getFieldError("recommendedConfiguration") && errorIcon}
126+
</React.Fragment>
127+
}
128+
showArrow={false}
129+
>
130+
<Row gutter={32}>
131+
<Col span={12}>
132+
<FormItem>
133+
The recommended configuration will be displayed to users when starting to trace a task
134+
with this task type. The user is able to accept or decline this recommendation.
135+
<br />
136+
<br />
137+
{form.getFieldDecorator("recommendedConfiguration", {
138+
rules: [
139+
{
140+
validator: validateUserSettingsJSON,
141+
},
142+
],
143+
})(
144+
<Input.TextArea
145+
spellCheck={false}
146+
autosize={{ minRows: 20 }}
147+
style={jsonEditStyle}
148+
/>,
149+
)}
150+
</FormItem>
151+
<Button style={{ marginRight: 10 }} onClick={() => removeSettings(form, "orthogonal")}>
152+
Remove Orthogonal-only Settings
153+
</Button>
154+
<Button onClick={() => removeSettings(form, "flight")}>
155+
Remove Flight/Oblique-only Settings
156+
</Button>
157+
</Col>
158+
<Col span={12}>
159+
Valid settings and their default values: <br />
160+
<br />
161+
<Table
162+
columns={columns}
163+
dataSource={_.map(DEFAULT_RECOMMENDED_CONFIGURATION, (value, key) => ({
164+
name: settings[key],
165+
key,
166+
value: value.toString(),
167+
comment: settingComments[key] || "",
168+
}))}
169+
size="small"
170+
pagination={false}
171+
/>
172+
</Col>
173+
</Row>
174+
</Panel>
175+
</Collapse>
176+
);
177+
}

app/assets/javascripts/admin/tasktype/task_type_create_view.js

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ import {
1111
updateTaskType,
1212
getTaskType,
1313
} from "admin/admin_rest_api";
14+
import { jsonStringify } from "libs/utils";
15+
import RecommendedConfigurationView, {
16+
DEFAULT_RECOMMENDED_CONFIGURATION,
17+
} from "admin/tasktype/recommended_configuration_view";
1418

1519
const FormItem = Form.Item;
16-
const Option = Select.Option;
17-
const TextArea = Input.TextArea;
20+
const { Option } = Select;
21+
const { TextArea } = Input;
1822

1923
type Props = {
2024
taskTypeId: ?string,
@@ -24,11 +28,13 @@ type Props = {
2428

2529
type State = {
2630
teams: Array<APITeam>,
31+
useRecommendedConfiguration: boolean,
2732
};
2833

2934
class TaskTypeCreateView extends React.PureComponent<Props, State> {
3035
state = {
3136
teams: [],
37+
useRecommendedConfiguration: false,
3238
};
3339

3440
componentDidMount() {
@@ -43,12 +49,22 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
4349
branchPointsAllowed: true,
4450
preferredMode: null,
4551
},
52+
recommendedConfiguration: jsonStringify(DEFAULT_RECOMMENDED_CONFIGURATION),
4653
};
4754
const taskType = this.props.taskTypeId ? await getTaskType(this.props.taskTypeId) : null;
4855
// Use merge which is deep _.extend
49-
// eslint-disable-next-line no-unused-vars
50-
const { recommendedConfiguration, ...formValues } = _.merge({}, defaultValues, taskType);
56+
const formValues = _.merge({}, defaultValues, taskType);
57+
if (formValues.recommendedConfiguration == null) {
58+
// A recommended configuration of null overrides the default configuration when using _.merge
59+
// If the task type has no recommended configuration, suggest the default one
60+
formValues.recommendedConfiguration = defaultValues.recommendedConfiguration;
61+
}
5162
this.props.form.setFieldsValue(formValues);
63+
64+
if (taskType != null && taskType.recommendedConfiguration != null) {
65+
// Only "activate" the recommended configuration checkbox if the existing task type contained one
66+
this.setState({ useRecommendedConfiguration: true });
67+
}
5268
}
5369

5470
async fetchData() {
@@ -57,6 +73,9 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
5773

5874
handleSubmit = e => {
5975
e.preventDefault();
76+
if (!this.state.useRecommendedConfiguration) {
77+
this.props.form.setFieldsValue({ recommendedConfiguration: null });
78+
}
6079
this.props.form.validateFields(async (err, formValues) => {
6180
if (!err) {
6281
if (this.props.taskTypeId) {
@@ -69,6 +88,10 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
6988
});
7089
};
7190

91+
onChangeUseRecommendedConfiguration = (useRecommendedConfiguration: boolean) => {
92+
this.setState({ useRecommendedConfiguration });
93+
};
94+
7295
render() {
7396
const { getFieldDecorator } = this.props.form;
7497
const titlePrefix = this.props.taskTypeId ? "Update " : "Create";
@@ -170,6 +193,14 @@ class TaskTypeCreateView extends React.PureComponent<Props, State> {
170193
)}
171194
</FormItem>
172195

196+
<FormItem>
197+
<RecommendedConfigurationView
198+
form={this.props.form}
199+
enabled={this.state.useRecommendedConfiguration}
200+
onChangeEnabled={this.onChangeUseRecommendedConfiguration}
201+
/>
202+
</FormItem>
203+
173204
<FormItem>
174205
<Button type="primary" htmlType="submit">
175206
{titlePrefix} Task Type

app/assets/javascripts/dashboard/dataset/dataset_import_view.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
updateDatasetTeams,
2525
} from "admin/admin_rest_api";
2626
import { handleGenericError } from "libs/error_handling";
27+
import { jsonStringify } from "libs/utils";
2728
import Toast from "libs/toast";
2829
import messages from "messages";
2930

@@ -35,8 +36,6 @@ import SimpleAdvancedDataForm from "./simple_advanced_data_form";
3536
const { TabPane } = Tabs;
3637
const FormItem = Form.Item;
3738

38-
const toJSON = json => JSON.stringify(json, null, " ");
39-
4039
type Props = {
4140
form: Object,
4241
datasetId: APIDatasetId,
@@ -97,7 +96,7 @@ class DatasetImportView extends React.PureComponent<Props, State> {
9796
throw new Error("No datasource received from server.");
9897
}
9998
this.props.form.setFieldsValue({
100-
dataSourceJson: toJSON(dataSource),
99+
dataSourceJson: jsonStringify(dataSource),
101100
dataset: {
102101
displayName: dataset.displayName || undefined,
103102
isPublic: dataset.isPublic || false,
@@ -325,7 +324,7 @@ class DatasetImportView extends React.PureComponent<Props, State> {
325324
// _.merge does a deep merge which mutates newDataSource
326325
_.merge(newDataSource, form.getFieldValue("dataSource"));
327326
form.setFieldsValue({
328-
dataSourceJson: toJSON(newDataSource),
327+
dataSourceJson: jsonStringify(newDataSource),
329328
});
330329
} else {
331330
// Copy from advanced to simple: update form values

app/assets/javascripts/dashboard/dataset/validation.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import jsonschema from "jsonschema";
44

55
import DatasourceSchema from "libs/datasource.schema.json";
6+
import UserSettingsSchema from "libs/user_settings.schema.json";
67

78
const validator = new jsonschema.Validator();
89
validator.addSchema(DatasourceSchema, "/");
10+
validator.addSchema(UserSettingsSchema, "/");
911

1012
const validateWithSchema = (type: string) => (rule: Object, value: string, callback: Function) => {
1113
try {
@@ -29,6 +31,7 @@ const validateWithSchema = (type: string) => (rule: Object, value: string, callb
2931

3032
export const validateDatasourceJSON = validateWithSchema("types::DatasourceConfiguration");
3133
export const validateLayerConfigurationJSON = validateWithSchema("types::LayerUserConfiguration");
34+
export const validateUserSettingsJSON = validateWithSchema("types::UserSettings");
3235

3336
export const isValidJSON = (json: string) => {
3437
try {

0 commit comments

Comments
 (0)