Skip to content

Commit

Permalink
Allow to download NMLs of older versions for read-only tracings (#3660)
Browse files Browse the repository at this point in the history
* allow version restore view in read-only tracings, offer to download NML of older versions

* use correct version parameters for nml download

* annotation download: pass on version parameters to tracingstore

* scalafmt

* rename tracingType and APITracingType to annotationType where appropriate

* update changelog

* apply PR feedback
  • Loading branch information
daniel-wer authored Jan 23, 2019
1 parent aa770ac commit 0bd7c7b
Show file tree
Hide file tree
Showing 38 changed files with 245 additions and 195 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
### Added

- Added the possibility to disable that the current layout is saved automatically when changing it. Instead, the layout can be saved explicitly. [#3620](https://github.com/scalableminds/webknossos/pull/3620)
- Added the possibility to open the version restore view for read-only tracings. Older versions can be previewed and be downloaded as NML. [#3660](https://github.com/scalableminds/webknossos/pull/3660)

### Changed

Expand Down
47 changes: 32 additions & 15 deletions app/assets/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import _ from "lodash";
import {
type APIActiveUser,
type APIAnnotation,
type APIAnnotationTypeCompact,
type APIAnnotationCompact,
type APIAnnotationWithTask,
type APIBuildInfo,
type APIDataSource,
Expand All @@ -30,8 +30,8 @@ import {
type APITimeInterval,
type APITimeTracking,
type APITracingStore,
type APITracingType,
APITracingTypeEnum,
type APIAnnotationType,
APIAnnotationTypeEnum,
type APIUpdateActionBatch,
type APIUser,
type APIUserLoggedTime,
Expand Down Expand Up @@ -422,7 +422,7 @@ export async function updateTask(taskId: string, task: NewTask): Promise<APITask
}

export function finishTask(annotationId: string): Promise<APIAnnotation> {
return finishAnnotation(annotationId, APITracingTypeEnum.Task);
return finishAnnotation(annotationId, APIAnnotationTypeEnum.Task);
}

export function transferTask(annotationId: string, userId: string): Promise<APIAnnotation> {
Expand Down Expand Up @@ -454,7 +454,7 @@ export async function getUsersWithActiveTasks(projectName: string): Promise<Arra
export function getCompactAnnotations(
isFinished: boolean,
pageNumber: number = 0,
): Promise<Array<APIAnnotationTypeCompact>> {
): Promise<Array<APIAnnotationCompact>> {
return Request.receiveJSON(
`/api/user/annotations?isFinished=${isFinished.toString()}&pageNumber=${pageNumber}`,
);
Expand All @@ -464,15 +464,15 @@ export function getCompactAnnotationsForUser(
userId: string,
isFinished: boolean,
pageNumber: number = 0,
): Promise<Array<APIAnnotationTypeCompact>> {
): Promise<Array<APIAnnotationCompact>> {
return Request.receiveJSON(
`/api/users/${userId}/annotations?isFinished=${isFinished.toString()}&pageNumber=${pageNumber}`,
);
}

export function reOpenAnnotation(
annotationId: string,
annotationType: APITracingType,
annotationType: APIAnnotationType,
): Promise<APIAnnotation> {
return Request.receiveJSON(`/api/annotations/${annotationType}/${annotationId}/reopen`, {
method: "PATCH",
Expand All @@ -488,7 +488,7 @@ export type EditableAnnotation = {

export function editAnnotation(
annotationId: string,
annotationType: APITracingType,
annotationType: APIAnnotationType,
data: $Shape<EditableAnnotation>,
): Promise<void> {
return Request.sendJSONReceiveJSON(`/api/annotations/${annotationType}/${annotationId}/edit`, {
Expand All @@ -499,7 +499,7 @@ export function editAnnotation(

export function finishAnnotation(
annotationId: string,
annotationType: APITracingType,
annotationType: APIAnnotationType,
): Promise<APIAnnotation> {
return Request.receiveJSON(`/api/annotations/${annotationType}/${annotationId}/finish`, {
method: "PATCH",
Expand All @@ -508,7 +508,7 @@ export function finishAnnotation(

export function resetAnnotation(
annotationId: string,
annotationType: APITracingType,
annotationType: APIAnnotationType,
): Promise<APIAnnotation> {
return Request.receiveJSON(`/api/annotations/${annotationType}/${annotationId}/reset`, {
method: "PUT",
Expand All @@ -517,7 +517,7 @@ export function resetAnnotation(

export function deleteAnnotation(
annotationId: string,
annotationType: APITracingType,
annotationType: APIAnnotationType,
): Promise<{ messages: Array<Message> }> {
return Request.receiveJSON(`/api/annotations/${annotationType}/${annotationId}`, {
method: "DELETE",
Expand All @@ -537,17 +537,17 @@ export function finishAllAnnotations(

export function copyAnnotationToUserAccount(
annotationId: string,
tracingType: APITracingType,
annotationType: APIAnnotationType,
): Promise<APIAnnotation> {
const url = `/api/annotations/${tracingType}/${annotationId}/duplicate`;
const url = `/api/annotations/${annotationType}/${annotationId}/duplicate`;
return Request.receiveJSON(url, { method: "POST" });
}

export function getAnnotationInformation(
annotationId: string,
tracingType: APITracingType,
annotationType: APIAnnotationType,
): Promise<APIAnnotation> {
const infoUrl = `/api/annotations/${tracingType}/${annotationId}/info`;
const infoUrl = `/api/annotations/${annotationType}/${annotationId}/info`;
return Request.receiveJSON(infoUrl);
}

Expand Down Expand Up @@ -625,6 +625,23 @@ export function convertToHybridTracing(annotationId: string): Promise<void> {
});
}

export async function downloadNml(
annotationId: string,
annotationType: APIAnnotationType,
versions?: Versions = {},
) {
const possibleVersionString = Object.entries(versions)
// $FlowFixMe Flow returns val as mixed here due to the use of Object.entries
.map(([key, val]) => `${key}Version=${val}`)
.join("&");
const win = window.open("about:blank", "_blank");
win.document.body.innerHTML = messages["download.wait"];

const downloadUrl = `/api/annotations/${annotationType}/${annotationId}/download?${possibleVersionString}`;
win.location.href = downloadUrl;
win.document.body.innerHTML = messages["download.close_window"];
}

// ### Datasets
export async function getDatasets(): Promise<Array<APIMaybeUnimportedDataset>> {
const datasets = await Request.receiveJSON("/api/datasets");
Expand Down
14 changes: 7 additions & 7 deletions app/assets/javascripts/admin/api_flow_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export type APISettings = {|
+somaClickingAllowed: boolean,
|};

export const APITracingTypeEnum = Enum.make({
export const APIAnnotationTypeEnum = Enum.make({
Explorational: "Explorational",
Task: "Task",
View: "View",
Expand All @@ -183,7 +183,7 @@ export const APITracingTypeEnum = Enum.make({
CompoundTaskType: "CompoundTaskType",
});

export type APITracingType = $Keys<typeof APITracingTypeEnum>;
export type APIAnnotationType = $Keys<typeof APIAnnotationTypeEnum>;

export type APITaskType = {
+id: string,
Expand Down Expand Up @@ -266,7 +266,7 @@ export type APITask = {
+directLinks?: Array<string>,
};

export type APIAnnotationTypeCompact = {
export type APIAnnotationCompact = {
+tracing: {
+skeleton: ?string,
+volume: ?string,
Expand All @@ -283,7 +283,7 @@ export type APIAnnotationTypeCompact = {
+stats: SkeletonTracingStats | {||},
+tags: Array<string>,
+tracingTime: ?number,
+typ: APITracingType,
+typ: APIAnnotationType,
};

export type LocalMeshMetaData = {|
Expand All @@ -304,7 +304,7 @@ export type MeshMetaData = {|
...RemoteMeshMetaData,
|};

type APIAnnotationTypeBase = APIAnnotationTypeCompact & {
type APIAnnotationBase = APIAnnotationCompact & {
+dataStore: APIDataStore,
+tracingStore: APITracingStore,
+restrictions: APIRestrictions,
Expand All @@ -313,11 +313,11 @@ type APIAnnotationTypeBase = APIAnnotationTypeCompact & {
+meshes: Array<MeshMetaData>,
};

export type APIAnnotation = APIAnnotationTypeBase & {
export type APIAnnotation = APIAnnotationBase & {
+task: ?APITask,
};

export type APIAnnotationWithTask = APIAnnotationTypeBase & {
export type APIAnnotationWithTask = APIAnnotationBase & {
+task: APITask,
};

Expand Down
28 changes: 14 additions & 14 deletions app/assets/javascripts/dashboard/explorative_annotations_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as React from "react";
import _ from "lodash";
import update from "immutability-helper";

import type { APIAnnotationTypeCompact } from "admin/api_flow_types";
import type { APIAnnotationCompact } from "admin/api_flow_types";
import { AnnotationContentTypes } from "oxalis/constants";
import { AsyncLink } from "components/async_clickables";
import {
Expand All @@ -36,11 +36,11 @@ import { trackAction } from "oxalis/model/helpers/analytics";
const { Column } = Table;
const { Search } = Input;

const typeHint: APIAnnotationTypeCompact[] = [];
const typeHint: APIAnnotationCompact[] = [];
const pageLength: number = 1000;

export type TracingModeState = {
tracings: Array<APIAnnotationTypeCompact>,
tracings: Array<APIAnnotationCompact>,
lastLoadedPage: number,
loadedAllTracings: boolean,
};
Expand Down Expand Up @@ -178,7 +178,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
);
};

finishOrReopenTracing = async (type: "finish" | "reopen", tracing: APIAnnotationTypeCompact) => {
finishOrReopenTracing = async (type: "finish" | "reopen", tracing: APIAnnotationCompact) => {
const newTracing =
type === "finish"
? await finishAnnotation(tracing.id, tracing.typ)
Expand All @@ -205,7 +205,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
}
};

renderActions = (tracing: APIAnnotationTypeCompact) => {
renderActions = (tracing: APIAnnotationCompact) => {
if (tracing.typ !== "Explorational") {
return null;
}
Expand Down Expand Up @@ -240,15 +240,15 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
}
};

getCurrentTracings(): Array<APIAnnotationTypeCompact> {
getCurrentTracings(): Array<APIAnnotationCompact> {
return this.getCurrentModeState().tracings;
}

handleSearch = (event: SyntheticInputEvent<>): void => {
this.setState({ searchQuery: event.target.value });
};

renameTracing(tracing: APIAnnotationTypeCompact, name: string) {
renameTracing(tracing: APIAnnotationCompact, name: string) {
const tracings = this.getCurrentTracings();

const newTracings = tracings.map(currentTracing => {
Expand Down Expand Up @@ -310,7 +310,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
};

editTagFromAnnotation = (
annotation: APIAnnotationTypeCompact,
annotation: APIAnnotationCompact,
shouldAddTag: boolean,
tag: string,
event: SyntheticInputEvent<>,
Expand Down Expand Up @@ -359,7 +359,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
);
}

renderNameWithDescription(tracing: APIAnnotationTypeCompact) {
renderNameWithDescription(tracing: APIAnnotationCompact) {
const hasDescription = tracing.description !== "";
const markdownDescription = (
<div style={{ maxWidth: 400 }}>
Expand Down Expand Up @@ -403,21 +403,21 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
<Column
title="ID"
dataIndex="id"
render={(__, tracing: APIAnnotationTypeCompact) => formatHash(tracing.id)}
render={(__, tracing: APIAnnotationCompact) => formatHash(tracing.id)}
sorter={Utils.localeCompareBy(typeHint, annotation => annotation.id)}
className="monospace-id"
/>
<Column
title="Name"
dataIndex="name"
sorter={Utils.localeCompareBy(typeHint, annotation => annotation.name)}
render={(name: string, tracing: APIAnnotationTypeCompact) =>
render={(name: string, tracing: APIAnnotationCompact) =>
this.renderNameWithDescription(tracing)
}
/>
<Column
title="Stats"
render={(__, annotation: APIAnnotationTypeCompact) =>
render={(__, annotation: APIAnnotationCompact) =>
// Flow doesn't recognize that stats must contain the nodeCount if the treeCount is != null
annotation.stats.treeCount != null &&
annotation.stats.nodeCount != null &&
Expand Down Expand Up @@ -446,7 +446,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
title="Tags"
dataIndex="tags"
width={500}
render={(tags: Array<string>, annotation: APIAnnotationTypeCompact) => (
render={(tags: Array<string>, annotation: APIAnnotationCompact) => (
<div>
{tags.map(tag => (
<Tag
Expand Down Expand Up @@ -481,7 +481,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
title="Actions"
className="nowrap"
key="action"
render={(__, tracing: APIAnnotationTypeCompact) => this.renderActions(tracing)}
render={(__, tracing: APIAnnotationCompact) => this.renderActions(tracing)}
/>
</Table>
);
Expand Down
10 changes: 5 additions & 5 deletions app/assets/javascripts/oxalis/api/api_latest.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import type {
Tracing,
SkeletonTracing,
VolumeTracing,
TracingTypeTracing,
AnnotationType,
Mapping,
TreeGroupTypeFlat,
} from "oxalis/store";
Expand Down Expand Up @@ -368,11 +368,11 @@ class TracingApi {

this.isFinishing = true;
const state = Store.getState();
const { tracingType, annotationId } = state.tracing;
const { annotationType, annotationId } = state.tracing;
const { task } = state;

await Model.save();
await finishAnnotation(annotationId, tracingType);
await finishAnnotation(annotationId, annotationType);
try {
const annotation = await requestTask();

Expand Down Expand Up @@ -409,7 +409,7 @@ class TracingApi {
*
*/
async restart(
newTracingType: TracingTypeTracing,
newAnnotationType: AnnotationType,
newAnnotationId: string,
newControlMode: ControlMode,
versions?: Versions,
Expand All @@ -420,7 +420,7 @@ class TracingApi {
Store.dispatch(restartSagaAction());
UrlManager.reset();
await Model.fetch(
newTracingType,
newAnnotationType,
{ annotationId: newAnnotationId, type: newControlMode },
false,
versions,
Expand Down
Loading

0 comments on commit 0bd7c7b

Please sign in to comment.