From 09d7ca4f9982121daed581dbeefb21f8d9b455ad Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Tue, 7 Jun 2022 15:08:58 +0200 Subject: [PATCH 1/4] Fix broken context menu in dataset-view-mode when no segmentation layer is visible (#6259) * fix broken context menu in dataset-view-mode when no segmentation layer is visible * update changelog --- CHANGELOG.unreleased.md | 1 + .../javascripts/oxalis/view/context_menu.tsx | 16 +++++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 3e8e84141bd..0610b1d3b5d 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released ### Changed ### Fixed +- Fixed that the context menu broke webKnossos when opening it in dataset-view-mode while no segmentation layer was visible. [#6259](https://github.com/scalableminds/webknossos/pull/6259) ### Removed diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index 7cc2cf70c11..0d6008e0c17 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -350,11 +350,13 @@ function NodeContextMenuOptions({ volumeTracing, infoRows, allowUpdate, -}: NodeContextMenuOptionsProps) { +}: NodeContextMenuOptionsProps): JSX.Element { const dispatch = useDispatch(); if (skeletonTracing == null) { - return null; + throw new Error( + "NodeContextMenuOptions should not have been called without existing skeleton tracing.", + ); } const { userBoundingBoxes } = skeletonTracing; @@ -613,7 +615,7 @@ function getBoundingBoxMenuOptions({ ]; } -function NoNodeContextMenuOptions(props: NoNodeContextMenuProps) { +function NoNodeContextMenuOptions(props: NoNodeContextMenuProps): JSX.Element { const { skeletonTracing, volumeTracing, @@ -799,10 +801,6 @@ function NoNodeContextMenuOptions(props: NoNodeContextMenuProps) { allActions = nonSkeletonActions.concat(skeletonActions).concat(boundingBoxActions); } - if (allActions.length === 0) { - return null; - } - return ( Date: Mon, 13 Jun 2022 16:07:34 +0200 Subject: [PATCH 2/4] Fix benign error toast for public annotations and other improvements (#6271) * make modals lazy; don't show error toast when fetching token or teams failed; fix padding of share button * update changelog * rename makeModalLazy to makeComponentLazy; improve its typings and also use it for the ShareModalView, UserScriptsModal and MergeModalView * improve typing * don't try to fetch teams or token in sharing modal when not logged in --- CHANGELOG.unreleased.md | 1 + frontend/javascripts/admin/admin_rest_api.ts | 8 ++++-- .../admin/datastore_health_check.ts | 1 - .../dataset/team_selection_component.tsx | 22 ++++++++------- .../{react_helpers.ts => react_helpers.tsx} | 20 +++++++++++++- frontend/javascripts/libs/request.ts | 1 + frontend/javascripts/messages.ts | 1 + .../view/action-bar/dataset_position_view.tsx | 12 +++------ .../view/action-bar/download_modal_view.tsx | 25 +++++++++++++---- .../view/action-bar/merge_modal_view.tsx | 5 +++- .../view/action-bar/share_modal_view.tsx | 27 ++++++++++++------- .../action-bar/user_scripts_modal_view.tsx | 7 ++++- 12 files changed, 93 insertions(+), 37 deletions(-) rename frontend/javascripts/libs/{react_helpers.ts => react_helpers.tsx} (77%) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 0610b1d3b5d..a0a4c5ca35d 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -16,6 +16,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released ### Fixed - Fixed that the context menu broke webKnossos when opening it in dataset-view-mode while no segmentation layer was visible. [#6259](https://github.com/scalableminds/webknossos/pull/6259) +- Fixed benign error toast when viewing a public annotation without being logged in. [#6271](https://github.com/scalableminds/webknossos/pull/6271) ### Removed diff --git a/frontend/javascripts/admin/admin_rest_api.ts b/frontend/javascripts/admin/admin_rest_api.ts index 8d434a65626..91445291c3e 100644 --- a/frontend/javascripts/admin/admin_rest_api.ts +++ b/frontend/javascripts/admin/admin_rest_api.ts @@ -339,13 +339,17 @@ export function updateTaskType(taskTypeId: string, taskType: APITaskType): Promi // ### Teams export async function getTeams(): Promise> { - const teams = await Request.receiveJSON("/api/teams"); + const teams = await Request.receiveJSON("/api/teams", { + doNotInvestigate: true, + }); assertResponseLimit(teams); return teams; } export async function getEditableTeams(): Promise> { - const teams = await Request.receiveJSON("/api/teams?isEditable=true"); + const teams = await Request.receiveJSON("/api/teams?isEditable=true", { + doNotInvestigate: true, + }); assertResponseLimit(teams); return teams; } diff --git a/frontend/javascripts/admin/datastore_health_check.ts b/frontend/javascripts/admin/datastore_health_check.ts index 5366ccd614e..59d80994b1b 100644 --- a/frontend/javascripts/admin/datastore_health_check.ts +++ b/frontend/javascripts/admin/datastore_health_check.ts @@ -49,7 +49,6 @@ const pingDataStoreIfAppropriate = memoizedThrottle(async (requestedUrl: string) const healthEndpoint = `${url}/${path}/health`; Request.triggerRequest(healthEndpoint, { doNotInvestigate: true, - // @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ doNotInvestigate: true; mode: ... Remove this comment to see the full error message mode: "cors", timeout: 5000, }).then( diff --git a/frontend/javascripts/dashboard/dataset/team_selection_component.tsx b/frontend/javascripts/dashboard/dataset/team_selection_component.tsx index 652b4951cbd..591efd9ac32 100644 --- a/frontend/javascripts/dashboard/dataset/team_selection_component.tsx +++ b/frontend/javascripts/dashboard/dataset/team_selection_component.tsx @@ -41,16 +41,20 @@ class TeamSelectionComponent extends React.PureComponent { this.setState({ isFetchingData: true, }); - const possibleTeams = this.props.allowNonEditableTeams - ? await getTeams() - : await getEditableTeams(); - this.setState({ - possibleTeams, - isFetchingData: false, - }); + try { + const possibleTeams = this.props.allowNonEditableTeams + ? await getTeams() + : await getEditableTeams(); + this.setState({ + possibleTeams, + isFetchingData: false, + }); - if (this.props.afterFetchedTeams != null) { - this.props.afterFetchedTeams(possibleTeams); + if (this.props.afterFetchedTeams != null) { + this.props.afterFetchedTeams(possibleTeams); + } + } catch (exception) { + console.error("Could not load teams."); } } diff --git a/frontend/javascripts/libs/react_helpers.ts b/frontend/javascripts/libs/react_helpers.tsx similarity index 77% rename from frontend/javascripts/libs/react_helpers.ts rename to frontend/javascripts/libs/react_helpers.tsx index b33d6019205..37331844da6 100644 --- a/frontend/javascripts/libs/react_helpers.ts +++ b/frontend/javascripts/libs/react_helpers.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { useStore } from "react-redux"; import type { OxalisState } from "oxalis/store"; // From https://overreacted.io/making-setinterval-declarative-with-react-hooks/ @@ -67,4 +67,22 @@ export function usePolledState(callback: (arg0: OxalisState) => void, interval: callback(state); }, interval); } + +export function makeComponentLazy( + ComponentFn: React.ComponentType, +): React.ComponentType { + return function LazyModalWrapper(props: T) { + const [hasBeenInitialized, setHasBeenInitialized] = useState(false); + const isVisible = props.isVisible; + useEffect(() => { + setHasBeenInitialized(hasBeenInitialized || isVisible); + }, [isVisible]); + + if (isVisible || hasBeenInitialized) { + return ; + } + return null; + }; +} + export default {}; diff --git a/frontend/javascripts/libs/request.ts b/frontend/javascripts/libs/request.ts index c56f1eda644..ceb8f778aa3 100644 --- a/frontend/javascripts/libs/request.ts +++ b/frontend/javascripts/libs/request.ts @@ -21,6 +21,7 @@ export type RequestOptionsBase = { headers?: T; host?: string; method?: method; + mode?: RequestMode; params?: string | Record; showErrorToast?: boolean; timeout?: number; diff --git a/frontend/javascripts/messages.ts b/frontend/javascripts/messages.ts index 2a9575c0780..2b5594e6f5e 100644 --- a/frontend/javascripts/messages.ts +++ b/frontend/javascripts/messages.ts @@ -299,6 +299,7 @@ instead. Only enable this option if you understand its effect. All layers will n "This webKnossos instance is not configured to run TIFF export jobs on a dedicated background worker. To learn more about this feature please contact us at ", "annotation.python_do_not_share": "These snippets are pre-configured and contain your personal access token and annotation meta data. Do not share this information with anyone you do not trust!", + "annotation.register_for_token": "Please log in to get an access token for the script below.", "project.delete": "Do you really want to delete this project?", "project.increase_instances": "Do you really want to add one additional instance to all tasks of this project?", diff --git a/frontend/javascripts/oxalis/view/action-bar/dataset_position_view.tsx b/frontend/javascripts/oxalis/view/action-bar/dataset_position_view.tsx index affd0a71c80..0e325c5bb04 100644 --- a/frontend/javascripts/oxalis/view/action-bar/dataset_position_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/dataset_position_view.tsx @@ -29,10 +29,7 @@ const warningColors = { color: "rgb(255, 155, 85)", borderColor: "rgb(241, 122, 39)", }; -const copyPositionDefaultStyle = { - padding: "0 10px", -}; -const copyPositionErrorStyle = { ...copyPositionDefaultStyle, ...warningColors }; +const iconErrorStyle = { ...warningColors }; const positionInputDefaultStyle = { textAlign: "center", }; @@ -94,8 +91,7 @@ class DatasetPositionView extends PureComponent { render() { const position = V3.floor(getPosition(this.props.flycam)); const { isOutOfDatasetBounds, isOutOfTaskBounds } = this.isPositionOutOfBounds(position); - const copyPositionStyle = - isOutOfDatasetBounds || isOutOfTaskBounds ? copyPositionErrorStyle : copyPositionDefaultStyle; + const iconColoringStyle = isOutOfDatasetBounds || isOutOfTaskBounds ? iconErrorStyle : {}; const positionInputStyle = isOutOfDatasetBounds || isOutOfTaskBounds ? positionInputErrorStyle @@ -125,7 +121,7 @@ class DatasetPositionView extends PureComponent { @@ -140,7 +136,7 @@ class DatasetPositionView extends PureComponent { style={positionInputStyle} allowDecimals /> - + {isArbitraryMode ? ( (null); const [selectedBoundingBoxID, setSelectedBoundingBoxId] = useState(-1); + const activeUser = useSelector((state: OxalisState) => state.activeUser); const tracing = useSelector((state: OxalisState) => state.tracing); const dataset = useSelector((state: OxalisState) => state.dataset); const userBoundingBoxes = useSelector((state: OxalisState) => @@ -209,7 +210,9 @@ export default function DownloadModalView(props: Props): JSX.Element { }} type="warning" > - {messages["annotation.python_do_not_share"]} + {activeUser != null + ? messages["annotation.python_do_not_share"] + : messages["annotation.register_for_token"]} ); @@ -253,11 +256,20 @@ export default function DownloadModalView(props: Props): JSX.Element { lineHeight: "30px", }; - const authToken = useFetch(getAuthToken, "loading...", []); + const authToken = useFetch( + async () => { + if (activeUser != null) { + return getAuthToken(); + } + return null; + }, + "loading...", + [activeUser], + ); const wkInitSnippet = `import webknossos as wk with wk.webknossos_context( - token="${authToken}", + token="${authToken || ""}", url="${window.location.origin}" ): annotation = wk.Annotation.download( @@ -494,3 +506,6 @@ with wk.webknossos_context( ); } + +const DownloadModalView = makeComponentLazy(_DownloadModalView); +export default DownloadModalView; diff --git a/frontend/javascripts/oxalis/view/action-bar/merge_modal_view.tsx b/frontend/javascripts/oxalis/view/action-bar/merge_modal_view.tsx index 08b3be7954a..17888e1625e 100644 --- a/frontend/javascripts/oxalis/view/action-bar/merge_modal_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/merge_modal_view.tsx @@ -17,6 +17,7 @@ import Toast from "libs/toast"; import * as Utils from "libs/utils"; import api from "oxalis/api/internal_api"; import messages from "messages"; +import { makeComponentLazy } from "libs/react_helpers"; type ProjectInfo = { id: string; label: string; @@ -83,7 +84,7 @@ class ButtonWithCheckbox extends PureComponent { +class _MergeModalView extends PureComponent { state: MergeModalViewState = { projects: [], selectedProject: null, @@ -307,6 +308,8 @@ class MergeModalView extends PureComponent { } } +const MergeModalView = makeComponentLazy(_MergeModalView); + function mapStateToProps(state: OxalisState): StateProps { return { annotationId: state.tracing.annotationId, diff --git a/frontend/javascripts/oxalis/view/action-bar/share_modal_view.tsx b/frontend/javascripts/oxalis/view/action-bar/share_modal_view.tsx index 49fcdc019e5..f97642059eb 100644 --- a/frontend/javascripts/oxalis/view/action-bar/share_modal_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/share_modal_view.tsx @@ -26,6 +26,7 @@ import UrlManager from "oxalis/controller/url_manager"; import { setAnnotationVisibilityAction } from "oxalis/model/actions/annotation_actions"; import { setShareModalVisibilityAction } from "oxalis/model/actions/ui_actions"; import { ControlModeEnum } from "oxalis/constants"; +import { makeComponentLazy } from "libs/react_helpers"; const RadioGroup = Radio.Group; const sharingActiveNode = true; type Props = { @@ -44,13 +45,12 @@ function Hint({ children, style }: { children: React.ReactNode; style: React.CSS } export function useDatasetSharingToken(dataset: APIDataset) { + const activeUser = useSelector((state: OxalisState) => state.activeUser); const [datasetToken, setDatasetToken] = useState(""); const fetchAndSetToken = async () => { try { - const sharingToken = await getDatasetSharingToken(dataset, { - showErrorToast: false, - }); + const sharingToken = await getDatasetSharingToken(dataset); setDatasetToken(sharingToken); } catch (error) { console.error(error); @@ -58,8 +58,11 @@ export function useDatasetSharingToken(dataset: APIDataset) { }; useEffect(() => { + if (!activeUser) { + return; + } fetchAndSetToken(); - }, [dataset]); + }, [dataset, activeUser]); return datasetToken; } export function getUrl(sharingToken: string, includeToken: boolean) { @@ -123,7 +126,8 @@ export function ShareButton(props: { dataset: APIDataset; style?: Record ); } -export default function ShareModalView(props: Props) { + +function _ShareModalView(props: Props) { const { isVisible, onOk, annotationType, annotationId } = props; const dataset = useSelector((state: OxalisState) => state.dataset); const annotationVisibility = useSelector((state: OxalisState) => state.tracing.visibility); @@ -131,19 +135,21 @@ export default function ShareModalView(props: Props) { const [visibility, setVisibility] = useState(annotationVisibility); const [sharedTeams, setSharedTeams] = useState([]); const sharingToken = useDatasetSharingToken(dataset); + const activeUser = useSelector((state: OxalisState) => state.activeUser); const hasUpdatePermissions = restrictions.allowUpdate && restrictions.allowSave; useEffect(() => setVisibility(annotationVisibility), [annotationVisibility]); const fetchAndSetSharedTeams = async () => { - const fetchedSharedTeams = await getTeamsForSharedAnnotation(annotationType, annotationId, { - showErrorToast: false, - }); + if (!activeUser) { + return; + } + const fetchedSharedTeams = await getTeamsForSharedAnnotation(annotationType, annotationId); setSharedTeams(fetchedSharedTeams); }; useEffect(() => { fetchAndSetSharedTeams(); - }, [annotationType, annotationId]); + }, [annotationType, annotationId, activeUser]); const handleCheckboxChange = (event: RadioChangeEvent) => { setVisibility(event.target.value as any as APIAnnotationVisibility); @@ -348,3 +354,6 @@ export default function ShareModalView(props: Props) { ); } + +const ShareModalView = makeComponentLazy(_ShareModalView); +export default ShareModalView; diff --git a/frontend/javascripts/oxalis/view/action-bar/user_scripts_modal_view.tsx b/frontend/javascripts/oxalis/view/action-bar/user_scripts_modal_view.tsx index b4808f79f1b..9ee6e35b32f 100644 --- a/frontend/javascripts/oxalis/view/action-bar/user_scripts_modal_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/user_scripts_modal_view.tsx @@ -7,7 +7,10 @@ import { fetchGistContent } from "libs/gist"; import { handleGenericError } from "libs/error_handling"; import Request from "libs/request"; import messages from "messages"; +import { makeComponentLazy } from "libs/react_helpers"; + const { TextArea } = Input; + type UserScriptsModalViewProps = { onOK: (...args: Array) => any; isVisible: boolean; @@ -20,7 +23,7 @@ type State = { isLoading: boolean; }; -class UserScriptsModalView extends React.PureComponent { +class _UserScriptsModalView extends React.PureComponent { state: State = { code: "", isCodeChanged: false, @@ -147,4 +150,6 @@ class UserScriptsModalView extends React.PureComponent Date: Wed, 15 Jun 2022 15:46:40 +0200 Subject: [PATCH 3/4] don't show error toast when dataset sharing token could not be acquired (#6279) Co-authored-by: Daniel --- .../javascripts/oxalis/view/action-bar/share_modal_view.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/javascripts/oxalis/view/action-bar/share_modal_view.tsx b/frontend/javascripts/oxalis/view/action-bar/share_modal_view.tsx index f97642059eb..f55799b9789 100644 --- a/frontend/javascripts/oxalis/view/action-bar/share_modal_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/share_modal_view.tsx @@ -50,7 +50,9 @@ export function useDatasetSharingToken(dataset: APIDataset) { const fetchAndSetToken = async () => { try { - const sharingToken = await getDatasetSharingToken(dataset); + const sharingToken = await getDatasetSharingToken(dataset, { + doNotInvestigate: true, + }); setDatasetToken(sharingToken); } catch (error) { console.error(error); From 836b32e74d570f74cee625d2c80c30c3c330eb26 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Thu, 16 Jun 2022 10:04:13 +0200 Subject: [PATCH 4/4] update changelog for 22.06.1 --- CHANGELOG.released.md | 7 +++++++ CHANGELOG.unreleased.md | 4 +--- MIGRATIONS.released.md | 6 ++++++ MIGRATIONS.unreleased.md | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.released.md b/CHANGELOG.released.md index c5a37dca972..b062344ee94 100644 --- a/CHANGELOG.released.md +++ b/CHANGELOG.released.md @@ -7,6 +7,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Calendar Versioning](http://calver.org/) `0Y.0M.MICRO`. For upgrade instructions, please check the [migration guide](MIGRATIONS.released.md). +## [22.06.1](https://github.com/scalableminds/webknossos/releases/tag/22.06.1) - 2022-06-16 +[Commits](https://github.com/scalableminds/webknossos/compare/22.06.0...22.06.1) + +### Fixed +- Fixed that the context menu broke webKnossos when opening it in dataset-view-mode while no segmentation layer was visible. [#6259](https://github.com/scalableminds/webknossos/pull/6259) +- Fixed benign error toast when viewing a public annotation without being logged in. [#6271](https://github.com/scalableminds/webknossos/pull/6271) + ## [22.06.0](https://github.com/scalableminds/webknossos/releases/tag/22.06.0) - 2022-05-27 [Commits](https://github.com/scalableminds/webknossos/compare/22.05.1...22.06.0) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index a0a4c5ca35d..b9f4c80dd53 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -8,15 +8,13 @@ and this project adheres to [Calendar Versioning](http://calver.org/) `0Y.0M.MIC For upgrade instructions, please check the [migration guide](MIGRATIONS.released.md). ## Unreleased -[Commits](https://github.com/scalableminds/webknossos/compare/22.06.0...HEAD) +[Commits](https://github.com/scalableminds/webknossos/compare/22.06.1...HEAD) ### Added ### Changed ### Fixed -- Fixed that the context menu broke webKnossos when opening it in dataset-view-mode while no segmentation layer was visible. [#6259](https://github.com/scalableminds/webknossos/pull/6259) -- Fixed benign error toast when viewing a public annotation without being logged in. [#6271](https://github.com/scalableminds/webknossos/pull/6271) ### Removed diff --git a/MIGRATIONS.released.md b/MIGRATIONS.released.md index 19a1c8e260b..1df20f1d057 100644 --- a/MIGRATIONS.released.md +++ b/MIGRATIONS.released.md @@ -5,6 +5,12 @@ See `MIGRATIONS.unreleased.md` for the changes which are not yet part of an offi This project adheres to [Calendar Versioning](http://calver.org/) `0Y.0M.MICRO`. User-facing changes are documented in the [changelog](CHANGELOG.released.md). +## [22.06.1](https://github.com/scalableminds/webknossos/releases/tag/22.06.1) - 2022-06-16 +[Commits](https://github.com/scalableminds/webknossos/compare/22.06.0...22.06.1) + +### Postgres Evolutions: + + ## [22.06.0](https://github.com/scalableminds/webknossos/releases/tag/22.06.0) - 2022-05-27 [Commits](https://github.com/scalableminds/webknossos/compare/22.05.1...22.06.0) diff --git a/MIGRATIONS.unreleased.md b/MIGRATIONS.unreleased.md index cdd42d7dc54..49eb62e5377 100644 --- a/MIGRATIONS.unreleased.md +++ b/MIGRATIONS.unreleased.md @@ -6,6 +6,6 @@ This project adheres to [Calendar Versioning](http://calver.org/) `0Y.0M.MICRO`. User-facing changes are documented in the [changelog](CHANGELOG.released.md). ## Unreleased -[Commits](https://github.com/scalableminds/webknossos/compare/22.06.0...HEAD) +[Commits](https://github.com/scalableminds/webknossos/compare/22.06.1...HEAD) ### Postgres Evolutions: