Skip to content

Commit

Permalink
Add upgrade motivation banner (#7768)
Browse files Browse the repository at this point in the history
* WIP: start implementing upgrade banner

* style banner and add more conditions

* adjust styling more

* WIP: use themeprovider to adjust nested theme

* fix avatar margin if banner is shown above navbar

* remove dev edits

* improve styling and add testing options

* fix commit date format

* add helper to parse date

* add changelog

* address review

* add WK link and improve style

* remove testing residue

* rename navbarHeight var and banners module

* improve scenario where both upgrade and maintenance banner are shown; allow theme change with banner; address review

* change order in AI job modal to make mito inferral second

* add tooltip again

* revert application.conf edits
  • Loading branch information
dieknolle3333 authored May 16, 2024
1 parent 35e2889 commit 0357463
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Minor improvements for the timetracking overview (faster data loading, styling). [#7789](https://github.com/scalableminds/webknossos/pull/7789)
- Updated several backend dependencies for optimized stability and performance. [#7782](https://github.com/scalableminds/webknossos/pull/7782)
- Voxelytics workflows can be searched by name and hash. [#7790](https://github.com/scalableminds/webknossos/pull/7790)
- If a self-hosted WEBKNOSSOS instance has not been updated for six months or more, a closable banner proposes an upgrade to webknossos.org. [#7768](https://github.com/scalableminds/webknossos/pull/7768)

### Changed
- Non-admin or -manager users can no longer start long-running jobs that create datasets. This includes annotation materialization and AI inferrals. [#7753](https://github.com/scalableminds/webknossos/pull/7753)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import {
getBuildInfo,
listCurrentAndUpcomingMaintenances,
updateNovelUserExperienceInfos,
} from "admin/admin_rest_api";
import { Alert } from "antd";
import { Alert, Button, Space } from "antd";
import FormattedDate from "components/formatted_date";
import { useInterval } from "libs/react_helpers";
import dayjs from "dayjs";
import { useFetch, useInterval } from "libs/react_helpers";
import { parseCTimeDefaultDate } from "libs/utils";
import _ from "lodash";
import constants from "oxalis/constants";
import { setNavbarHeightAction } from "oxalis/model/actions/ui_actions";
Expand All @@ -14,15 +17,17 @@ import { OxalisState } from "oxalis/store";
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { MaintenanceInfo } from "types/api_flow_types";
import * as Utils from "libs/utils";

const INITIAL_DELAY = 5000;
const INTERVAL_TO_FETCH_MAINTENANCES_MS = 60000; // 1min
const UPGRADE_BANNER_DISMISSAL_TIMESTAMP_LOCAL_STORAGE_KEY = "upgradeBannerWasClickedAway";

const BANNER_STYLE: React.CSSProperties = {
position: "absolute",
top: 0,
left: 0,
height: constants.MAINTENANCE_BANNER_HEIGHT,
height: constants.BANNER_HEIGHT,
};

function setNavbarHeight(newNavbarHeight: number) {
Expand Down Expand Up @@ -114,16 +119,27 @@ export function MaintenanceBanner() {
setClosestUpcomingMaintenance(_.first(closestUpcomingMaintenance));
}

const [shouldShowUpcomingMaintenanceBanner, setShouldShowUpcomingMaintenanceBanner] =
useState(false);

useEffect(() => {
if (currentMaintenance || closestUpcomingMaintenance) {
setNavbarHeight(constants.DEFAULT_NAVBAR_HEIGHT + constants.MAINTENANCE_BANNER_HEIGHT);
const newShouldShowUpcomingMaintenanceBanner =
closestUpcomingMaintenance != null && activeUser != null;
if (newShouldShowUpcomingMaintenanceBanner !== shouldShowUpcomingMaintenanceBanner) {
setShouldShowUpcomingMaintenanceBanner(newShouldShowUpcomingMaintenanceBanner);
}
}, [closestUpcomingMaintenance, activeUser, shouldShowUpcomingMaintenanceBanner]);

useEffect(() => {
if (currentMaintenance || shouldShowUpcomingMaintenanceBanner) {
setNavbarHeight(constants.DEFAULT_NAVBAR_HEIGHT + constants.BANNER_HEIGHT);
}

if (currentMaintenance == null && closestUpcomingMaintenance == null) {
// Reset Navbar height if maintenance is over
setNavbarHeight(constants.DEFAULT_NAVBAR_HEIGHT);
}
}, [currentMaintenance, closestUpcomingMaintenance]);
}, [currentMaintenance, closestUpcomingMaintenance, shouldShowUpcomingMaintenanceBanner]);

useEffect(() => {
// Do an initial fetch of the maintenance status so that users are notified
Expand All @@ -144,3 +160,104 @@ export function MaintenanceBanner() {

return null;
}

export function UpgradeVersionBanner() {
const white = "var(--ant-color-text-primary)";
const blue = "var(--ant-color-primary)";
const UPGRADE_BANNER_STYLE: React.CSSProperties = {
position: "absolute",
top: 0,
left: 0,
height: constants.BANNER_HEIGHT,
textAlign: "center",
backgroundColor: blue,
color: white,
fontSize: "medium",
minWidth: "fit-content",
zIndex: 999,
};
const currentDate = dayjs();

const activeUser = useSelector((state: OxalisState) => state.activeUser);

const isVersionOutdated = useFetch(
async () => {
if (!activeUser) return false;
await Utils.sleep(INITIAL_DELAY);
let buildInfo = await getBuildInfo();
const lastCommitDate = parseCTimeDefaultDate(buildInfo.webknossos.commitDate);
const needsUpdate = currentDate.diff(lastCommitDate, "month") >= 6;
return needsUpdate;
},
false,
[activeUser],
);

const [shouldBannerBeShown, setShouldBannerBeShown] = useState(false);

useEffect(() => {
if (!isVersionOutdated || activeUser == null) {
setShouldBannerBeShown(false);
return;
}
const lastTimeBannerWasClickedAway = localStorage.getItem(
UPGRADE_BANNER_DISMISSAL_TIMESTAMP_LOCAL_STORAGE_KEY,
);
if (lastTimeBannerWasClickedAway == null) {
setShouldBannerBeShown(true);
return;
}

const parsedDate = dayjs(lastTimeBannerWasClickedAway);
setShouldBannerBeShown(currentDate.diff(parsedDate, "day") >= 3);
}, [activeUser, isVersionOutdated, currentDate]);

useEffect(() => {
if (shouldBannerBeShown) {
setNavbarHeight(constants.DEFAULT_NAVBAR_HEIGHT + constants.BANNER_HEIGHT);
} else {
setNavbarHeight(constants.DEFAULT_NAVBAR_HEIGHT);
}
}, [shouldBannerBeShown]);

return shouldBannerBeShown ? (
<Alert
className="upgrade-banner"
message={
<Space size="middle">
<Space size="small">
You are using an outdated version of WEBKNOSSOS. Switch to
<a
className="upgrade-banner-wk-link"
target="_blank"
href="https://webknossos.org"
rel="noreferrer noopener"
>
webknossos.org
</a>
for automatic updates and exclusive features!
</Space>
<Button
className="upgrade-banner-button"
href="https://webknossos.org/self-hosted-upgrade"
size="small"
>
Learn more
</Button>
</Space>
}
banner
style={UPGRADE_BANNER_STYLE}
closable
onClose={() => {
localStorage.setItem(
UPGRADE_BANNER_DISMISSAL_TIMESTAMP_LOCAL_STORAGE_KEY,
dayjs().toISOString(),
);
setNavbarHeight(constants.DEFAULT_NAVBAR_HEIGHT);
}}
type="info"
showIcon={false}
/>
) : null;
}
2 changes: 2 additions & 0 deletions frontend/javascripts/libs/format_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import duration from "dayjs/plugin/duration";
import updateLocale from "dayjs/plugin/updateLocale";
import relativeTime from "dayjs/plugin/relativeTime";
import localizedFormat from "dayjs/plugin/localizedFormat";
import customParseFormat from "dayjs/plugin/customParseFormat";
import calendar from "dayjs/plugin/calendar";
import utc from "dayjs/plugin/utc";
import weekday from "dayjs/plugin/weekday";
Expand All @@ -22,6 +23,7 @@ dayjs.extend(relativeTime);
dayjs.extend(utc);
dayjs.extend(calendar);
dayjs.extend(weekday);
dayjs.extend(customParseFormat);
dayjs.extend(localeData);
dayjs.extend(localizedFormat);
dayjs.updateLocale("en", {
Expand Down
10 changes: 10 additions & 0 deletions frontend/javascripts/libs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
} from "oxalis/constants";
import window, { document, location } from "libs/window";
import { ArbitraryObject, Comparator } from "types/globals";
import dayjs from "dayjs";

type UrlParams = Record<string, string>;
// Fix JS modulo bug
Expand Down Expand Up @@ -563,6 +564,15 @@ export function isFileExtensionEqualTo(
return passedExtension === extensionOrExtensions;
}

// Parses dates in format "Thu Jan 1 00:00:00 1970 +0000".
export function parseCTimeDefaultDate(dateString: string) {
const commitDateWithoutWeekday = dateString.replace(
/(Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)|(Sun)\w*/,
"",
);
return dayjs(commitDateWithoutWeekday, "MMM D HH:mm:ss YYYY ZZ");
}

// Only use this function if you really need a busy wait (useful
// for testing performance-related edge cases). Prefer `sleep`
// otherwise.
Expand Down
30 changes: 15 additions & 15 deletions frontend/javascripts/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Tag,
Input,
InputRef,
ConfigProvider,
} from "antd";
import _ from "lodash";
import {
Expand All @@ -25,7 +26,7 @@ import {
import { useHistory, Link } from "react-router-dom";

import classnames from "classnames";
import { connect } from "react-redux";
import { connect, useSelector } from "react-redux";
import React, { useState, useEffect, useRef } from "react";
import Toast from "libs/toast";
import type {
Expand Down Expand Up @@ -61,8 +62,8 @@ import { PricingEnforcedSpan } from "components/pricing_enforcers";
import { ItemType, MenuItemType, SubMenuType } from "antd/lib/menu/hooks/useItems";
import { MenuClickEventHandler } from "rc-menu/lib/interface";
import constants from "oxalis/constants";
import { MaintenanceBanner } from "maintenance_banner";
import { getSystemColorTheme } from "theme";
import { MaintenanceBanner, UpgradeVersionBanner } from "banners";
import { getAntdTheme, getSystemColorTheme } from "theme";

const { Header } = Layout;

Expand Down Expand Up @@ -482,8 +483,7 @@ function NotificationIcon({
position: "relative",
display: "flex",
marginRight: 12,
paddingTop:
navbarHeight > constants.DEFAULT_NAVBAR_HEIGHT ? constants.MAINTENANCE_BANNER_HEIGHT : 0,
paddingTop: navbarHeight > constants.DEFAULT_NAVBAR_HEIGHT ? constants.BANNER_HEIGHT : 0,
}}
>
<Tooltip title="See what's new in WEBKNOSSOS" placement="bottomLeft">
Expand Down Expand Up @@ -610,8 +610,7 @@ function LoggedInAvatar({
selectedKeys={["prevent highlighting of this menu"]}
mode="horizontal"
style={{
paddingTop:
navbarHeight > constants.DEFAULT_NAVBAR_HEIGHT ? constants.MAINTENANCE_BANNER_HEIGHT : 0,
paddingTop: navbarHeight > constants.DEFAULT_NAVBAR_HEIGHT ? constants.BANNER_HEIGHT : 0,
lineHeight: `${constants.DEFAULT_NAVBAR_HEIGHT}px`,
}}
theme="dark"
Expand Down Expand Up @@ -701,6 +700,9 @@ function LoggedInAvatar({
}

function AnonymousAvatar() {
const bannerHeight = useSelector(
(state: OxalisState) => state.uiInformation.navbarHeight - constants.DEFAULT_NAVBAR_HEIGHT,
);
return (
<Popover
placement="bottomRight"
Expand All @@ -722,6 +724,7 @@ function AnonymousAvatar() {
icon={<UserOutlined />}
style={{
marginLeft: 8,
marginTop: bannerHeight,
}}
/>
</Popover>
Expand Down Expand Up @@ -899,15 +902,15 @@ function Navbar({
})}
>
<MaintenanceBanner />
<ConfigProvider theme={{ ...getAntdTheme("light") }}>
<UpgradeVersionBanner />
</ConfigProvider>
<Menu
mode="horizontal"
selectedKeys={selectedKeys}
onOpenChange={(openKeys) => setIsHelpMenuOpen(openKeys.includes(HELP_MENU_KEY))}
style={{
paddingTop:
navbarHeight > constants.DEFAULT_NAVBAR_HEIGHT
? constants.MAINTENANCE_BANNER_HEIGHT
: 0,
paddingTop: navbarHeight > constants.DEFAULT_NAVBAR_HEIGHT ? constants.BANNER_HEIGHT : 0,
lineHeight: `${constants.DEFAULT_NAVBAR_HEIGHT}px`,
}}
theme="dark"
Expand All @@ -930,10 +933,7 @@ function Navbar({
style={{
flex: 1,
display: "flex",
paddingTop:
navbarHeight > constants.DEFAULT_NAVBAR_HEIGHT
? constants.MAINTENANCE_BANNER_HEIGHT
: 0,
paddingTop: navbarHeight > constants.DEFAULT_NAVBAR_HEIGHT ? constants.BANNER_HEIGHT : 0,
}}
/>

Expand Down
2 changes: 1 addition & 1 deletion frontend/javascripts/oxalis/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ const Constants = {
BUCKET_SIZE: 32 ** 3,
VIEWPORT_WIDTH,
DEFAULT_NAVBAR_HEIGHT: 48,
MAINTENANCE_BANNER_HEIGHT: 38,
BANNER_HEIGHT: 38,
// For reference, the area of a large brush size (let's say, 300px) corresponds to
// pi * 300 ^ 2 == 282690.
// We multiply this with 5, since the labeling is not done
Expand Down
24 changes: 12 additions & 12 deletions frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -438,17 +438,17 @@ export function StartAIJobModal({ aIJobModalState }: StartAIJobModalProps) {
<Tooltip title="Coming soon">
<Radio.Button
className="aIJobSelection"
disabled
checked={aIJobModalState === "nuclei_inferral"}
onClick={() => Store.dispatch(setAIJobModalStateAction("nuclei_inferral"))}
checked={aIJobModalState === "mitochondria_inferral"}
disabled={!Store.getState().activeUser?.isSuperUser}
onClick={() => Store.dispatch(setAIJobModalStateAction("mitochondria_inferral"))}
>
<Card bordered={false}>
<Space direction="vertical" size="small">
<Row className="ai-job-title">Nuclei detection</Row>
<Row className="ai-job-title">Mitochondria detection</Row>
<Row>
<img
src={`/assets/images/${jobNameToImagePath.nuclei_inferral}`}
alt={"Nuclei detection example"}
src={`/assets/images/${jobNameToImagePath.mitochondria_inferral}`}
alt={"Mitochondria detection example"}
style={centerImageStyle}
/>
</Row>
Expand All @@ -459,17 +459,17 @@ export function StartAIJobModal({ aIJobModalState }: StartAIJobModalProps) {
<Tooltip title="Coming soon">
<Radio.Button
className="aIJobSelection"
checked={aIJobModalState === "mitochondria_inferral"}
disabled={!Store.getState().activeUser?.isSuperUser}
onClick={() => Store.dispatch(setAIJobModalStateAction("mitochondria_inferral"))}
disabled
checked={aIJobModalState === "nuclei_inferral"}
onClick={() => Store.dispatch(setAIJobModalStateAction("nuclei_inferral"))}
>
<Card bordered={false}>
<Space direction="vertical" size="small">
<Row className="ai-job-title">Mitochondria detection</Row>
<Row className="ai-job-title">Nuclei detection</Row>
<Row>
<img
src={`/assets/images/${jobNameToImagePath.mitochondria_inferral}`}
alt={"Mitochondria detection example"}
src={`/assets/images/${jobNameToImagePath.nuclei_inferral}`}
alt={"Nuclei detection example"}
style={centerImageStyle}
/>
</Row>
Expand Down
Loading

0 comments on commit 0357463

Please sign in to comment.