Skip to content

Commit

Permalink
[dashboard] Downtime in-app announcements
Browse files Browse the repository at this point in the history
  • Loading branch information
geropl committed Sep 20, 2023
1 parent e5d03e1 commit 7ec5410
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 4 deletions.
107 changes: 107 additions & 0 deletions components/dashboard/src/AppNotifications.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License-AGPL.txt in the project root for license information.
*/

import { useCallback, useEffect, useState } from "react";
import Alert, { AlertType } from "./components/Alert";
import { useFeatureFlag } from "./data/featureflag-query";

const KEY_APP_DISMISSED_NOTIFICATIONS = "gitpod-app-notifications-dismissed";

interface Notification {
id: string;
type: AlertType;
message: JSX.Element;
}

const SCHEDULED_DOWNTIME: Notification = {
id: "230924-scheduled-downtime",
type: "info",
message: (
<span className="text-md">
To improve the reliability of gitpod.io we are performing a scheduled maintenance this{" "}
<span className="font-semibold">Sunday, Sept. 24th, 05:00-06:00 UTC</span>.{" "}
<a
className="gp-link"
href="https://www.gitpodstatus.com/incidents/0d5vlgxcp27v"
target="_blank"
rel="noreferrer"
>
Learn more
</a>
</span>
),
};

export function AppNotifications() {
const [topNotification, setTopNotification] = useState<Notification | undefined>(undefined);

const downtimeNotificationEnabled = useFeatureFlag("scheduled_downtime_notification");

useEffect(() => {
const notifications = [];
if (downtimeNotificationEnabled) {
notifications.push(SCHEDULED_DOWNTIME);
}

const dismissedNotifications = getDismissedNotifications();
const topNotification = notifications.find((n) => !dismissedNotifications.includes(n.id));
setTopNotification(topNotification);
}, [downtimeNotificationEnabled, setTopNotification]);

const dismissNotification = useCallback(() => {
if (!topNotification) {
return;
}

const dismissedNotifications = getDismissedNotifications();
dismissedNotifications.push(topNotification.id);
setDismissedNotifications(dismissedNotifications);
setTopNotification(undefined);
}, [topNotification, setTopNotification]);

if (!topNotification) {
return <></>;
}

return (
<div className="app-container pt-2">
<Alert
type={topNotification.type}
closable={true}
onClose={() => dismissNotification()}
showIcon={true}
className="flex rounded mb-2 w-full"
>
<span>{topNotification.message}</span>
</Alert>
</div>
);
}

function getDismissedNotifications(): string[] {
try {
const str = window.localStorage.getItem(KEY_APP_DISMISSED_NOTIFICATIONS);
const parsed = JSON.parse(str || "[]");
if (!Array.isArray(parsed)) {
window.localStorage.removeItem(KEY_APP_DISMISSED_NOTIFICATIONS);
return [];
}
return parsed;
} catch (err) {
console.debug("Failed to parse dismissed notifications", err);
window.localStorage.removeItem(KEY_APP_DISMISSED_NOTIFICATIONS);
return [];
}
}

function setDismissedNotifications(ids: string[]) {
try {
window.localStorage.setItem(KEY_APP_DISMISSED_NOTIFICATIONS, JSON.stringify(ids));
} catch (err) {
console.debug("Failed to set dismissed notifications", err);
window.localStorage.removeItem(KEY_APP_DISMISSED_NOTIFICATIONS);
}
}
2 changes: 2 additions & 0 deletions components/dashboard/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import PersonalAccessTokenCreateView from "../user-settings/PersonalAccessTokens
import { CreateWorkspacePage } from "../workspaces/CreateWorkspacePage";
import { WebsocketClients } from "./WebsocketClients";
import { BlockedEmailDomains } from "../admin/BlockedEmailDomains";
import { AppNotifications } from "../AppNotifications";

const Workspaces = React.lazy(() => import(/* webpackPrefetch: true */ "../workspaces/Workspaces"));
const Account = React.lazy(() => import(/* webpackPrefetch: true */ "../user-settings/Account"));
Expand Down Expand Up @@ -121,6 +122,7 @@ export const AppRoutes = () => {
<Route>
<div className="container">
<Menu />
<AppNotifications />
<Switch>
<Route path="/new" exact component={CreateWorkspacePage} />
<Route path={projectsPathNew} exact component={NewProject} />
Expand Down
9 changes: 5 additions & 4 deletions components/dashboard/src/components/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ReactComponent as InfoSvg } from "../images/info.svg";
import { ReactComponent as XSvg } from "../images/x.svg";
import { ReactComponent as Check } from "../images/check-circle.svg";
import classNames from "classnames";
import { Button } from "./Button";

export type AlertType =
// Green
Expand Down Expand Up @@ -122,14 +123,14 @@ export default function Alert(props: AlertProps) {
{props.closable && (
<span className={`mt-1 ml-4`}>
{/* Use an IconButton component once we make it */}
<button
type="button"
className="bg-transparent p-1"
<Button
type="secondary"
className="bg-transparent hover:bg-transparent"
onClick={handleClose}
autoFocus={autoFocusClose}
>
<XSvg className="w-3 h-4 cursor-pointer" />
</button>
</Button>
</span>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions components/dashboard/src/data/featureflag-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const featureFlags = {
publicApiExperimentalWorkspaceService: false,
personalAccessTokensEnabled: false,
oidcServiceEnabled: false,
scheduled_downtime_notification: false,
// Default to true to enable on gitpod dedicated until ff support is added for dedicated
orgGitAuthProviders: true,
userGitAuthProviders: false,
Expand Down

0 comments on commit 7ec5410

Please sign in to comment.