Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UX: Re-use left sub-menu for Settings and License page on admin dashboard #16427

Merged
merged 2 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 43 additions & 47 deletions components/dashboard/src/admin/License.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ReactComponent as LinkSvg } from "../images/external-link.svg";
import SolidCard from "../components/SolidCard";
import Card from "../components/Card";
import { isGitpodIo } from "../utils";
import { AdminPageHeader } from "./AdminPageHeader";
import { SettingsLayout } from "./Settings";

export default function License() {
const { license, setLicense } = useContext(LicenseContext);
Expand All @@ -42,54 +42,50 @@ export default function License() {

return (
<div>
<AdminPageHeader title="Admin" subtitle="Configure and manage instance settings.">
<div className="app-container mt-8">
<div className="flex flex-row space-x-4">
<Card className="w-72 h-64">
<span>
{licenseLevel}
{paid}
<div className="mt-4 font-semibold text-sm">Available features:</div>
<div className="flex flex-col items-start text-sm">
{features &&
features.map((feat: string) => (
<span className="inline-flex space-x-1">
{featureList?.includes(feat) ? (
<CheckSvg fill="currentColor" className="self-center mt-1" />
) : (
<XSvg fill="currentColor" className="self-center h-2 mt-1" />
)}
<span>{capitalizeInitials(feat)}</span>
</span>
))}
<SettingsLayout>
<div className="flex flex-row space-x-4">
<Card className="w-72 h-64">
<span>
{licenseLevel}
{paid}
<div className="mt-4 font-semibold text-sm">Available features:</div>
<div className="flex flex-col items-start text-sm">
{features &&
features.map((feat: string) => (
<span className="inline-flex space-x-1">
{featureList?.includes(feat) ? (
<CheckSvg fill="currentColor" className="self-center mt-1" />
) : (
<XSvg fill="currentColor" className="self-center h-2 mt-1" />
)}
<span>{capitalizeInitials(feat)}</span>
</span>
))}
</div>
</span>
</Card>
<SolidCard className="w-72 h-64">
<span>
<div className="my-2">{statusMessage}</div>
<p className="dark:text-gray-500 font-semibold">Registered Users</p>
<span className="dark:text-gray-300 text-lg">{license?.userCount || 0}</span>
<span className="dark:text-gray-500 text-gray-400 pt-1 text-lg"> / {userLimit} </span>
<p className="dark:text-gray-500 pt-2 font-semibold">License Type</p>
<h4 className="dark:text-gray-300 text-lg">{capitalizeInitials(license?.type || "")}</h4>
<a
className="gp-link flex flex-row mr-2 justify-end font-semibold space-x-2 mt-6"
href="https://www.gitpod.io/self-hosted"
target="_blank"
>
<span className="text-sm">Compare Plans</span>
<div className="self-end">
<LinkSvg />
</div>
</span>
</Card>
<SolidCard className="w-72 h-64">
<span>
<div className="my-2">{statusMessage}</div>
<p className="dark:text-gray-500 font-semibold">Registered Users</p>
<span className="dark:text-gray-300 text-lg">{license?.userCount || 0}</span>
<span className="dark:text-gray-500 text-gray-400 pt-1 text-lg"> / {userLimit} </span>
<p className="dark:text-gray-500 pt-2 font-semibold">License Type</p>
<h4 className="dark:text-gray-300 text-lg">
{capitalizeInitials(license?.type || "")}
</h4>
<a
className="gp-link flex flex-row mr-2 justify-end font-semibold space-x-2 mt-6"
href="https://www.gitpod.io/self-hosted"
target="_blank"
>
<span className="text-sm">Compare Plans</span>
<div className="self-end">
<LinkSvg />
</div>
</a>
</span>
</SolidCard>
</div>
</a>
</span>
</SolidCard>
</div>
</AdminPageHeader>
</SettingsLayout>
</div>
);
}
Expand Down
114 changes: 63 additions & 51 deletions components/dashboard/src/admin/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,29 @@
* See License.AGPL.txt in the project root for license information.
*/

import { useContext } from "react";
import React, { useContext } from "react";
import { TelemetryData, InstallationAdminSettings } from "@gitpod/gitpod-protocol";
import { AdminContext } from "../admin-context";
import CheckBox from "../components/CheckBox";
import { getGitpodService } from "../service/service";
import { useEffect, useState } from "react";
import InfoBox from "../components/InfoBox";
import { isGitpodIo } from "../utils";
import { AdminPageHeader } from "./AdminPageHeader";
import { PageWithSubMenu } from "../components/PageWithSubMenu";
import { getAdminTabs, getAdminSettingsMenu } from "./admin.routes";

export function SettingsLayout(props: { children: React.ReactNode }) {
return (
<PageWithSubMenu
subMenu={getAdminSettingsMenu()}
title="Admin"
subtitle="Configure and manage instance settings."
tabs={getAdminTabs()}
>
{props.children}
</PageWithSubMenu>
);
}

export default function Settings() {
const { adminSettings, setAdminSettings } = useContext(AdminContext);
Expand All @@ -38,55 +52,53 @@ export default function Settings() {

return (
<div>
<AdminPageHeader title="Admin" subtitle="Configure and manage instance settings.">
<div className="app-container mt-8">
<h3>Usage Statistics</h3>
<p className="text-base text-gray-500 pb-4 max-w-2xl">
We collect usage telemetry to gain insights on how you use your Gitpod instance, so we can
provide a better overall experience.
</p>
<p>
<a className="gp-link" href="https://www.gitpod.io/privacy">
Read our Privacy Policy
</a>
</p>
<CheckBox
title="Enable usage telemetry"
desc={
<span>
Enable usage telemetry on your Gitpod instance. A preview of your telemetry is available
below.
</span>
}
checked={adminSettings?.sendTelemetry ?? false}
onChange={(evt) =>
actuallySetTelemetryPrefs({
...adminSettings,
sendTelemetry: evt.target.checked,
} as InstallationAdminSettings)
}
/>
<CheckBox
title="Include customer ID in telemetry"
desc={
<span>
Include an optional customer ID in usage telemetry to provide individualized support.
</span>
}
checked={adminSettings?.sendCustomerID ?? false}
onChange={(evt) =>
actuallySetTelemetryPrefs({
...adminSettings,
sendCustomerID: evt.target.checked,
} as InstallationAdminSettings)
}
/>
<h3 className="mt-4">Telemetry preview</h3>
<InfoBox>
<pre>{JSON.stringify(telemetryData, null, 2)}</pre>
</InfoBox>
</div>
</AdminPageHeader>
<SettingsLayout>
<h3>Usage Statistics</h3>
<p className="text-base text-gray-500 pb-4 max-w-2xl">
We collect usage telemetry to gain insights on how you use your Gitpod instance, so we can provide a
better overall experience.
</p>
<p>
<a className="gp-link" href="https://www.gitpod.io/privacy">
Read our Privacy Policy
</a>
</p>
<CheckBox
title="Enable usage telemetry"
desc={
<span>
Enable usage telemetry on your Gitpod instance. A preview of your telemetry is available
below.
</span>
}
checked={adminSettings?.sendTelemetry ?? false}
onChange={(evt) =>
actuallySetTelemetryPrefs({
...adminSettings,
sendTelemetry: evt.target.checked,
} as InstallationAdminSettings)
}
/>
<CheckBox
title="Include customer ID in telemetry"
desc={
<span>
Include an optional customer ID in usage telemetry to provide individualized support.
</span>
}
checked={adminSettings?.sendCustomerID ?? false}
onChange={(evt) =>
actuallySetTelemetryPrefs({
...adminSettings,
sendCustomerID: evt.target.checked,
} as InstallationAdminSettings)
}
/>
<h3 className="mt-4">Telemetry preview</h3>
<InfoBox>
<pre>{JSON.stringify(telemetryData, null, 2)}</pre>
</InfoBox>
</SettingsLayout>
</div>
);
}
18 changes: 14 additions & 4 deletions components/dashboard/src/admin/admin.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,23 @@ export function getAdminTabs(): TabEntry[] {
title: "Blocked Repositories",
link: "/admin/blocked-repositories",
},
{
title: "License",
link: "/admin/license",
},
{
title: "Settings",
link: "/admin/settings",
alternatives: getAdminSettingsMenu().flatMap((e) => e.link),
},
];
}

export function getAdminSettingsMenu() {
return [
{
title: "General",
link: ["/admin/settings"],
},
{
title: "License",
link: ["/admin/license"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The Admin menu becomes inactive when navigating to the license page. Could we keep it active?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it! Was just a one-liner fix that was needed 😆
Actually, I completely missed out that updating the getAdminTabs function to remove the License page route might cause such an issue, as this function is being used to set the active state on the admin menu 😅.

},
];
}
10 changes: 9 additions & 1 deletion components/dashboard/src/menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,15 @@ export default function Menu() {
const adminMenu: Entry = {
title: "Admin",
link: "/admin",
alternatives: [...getAdminTabs().map((entry) => entry.link)],
alternatives: [
...getAdminTabs().reduce(
(prevEntry, currEntry) =>
currEntry.alternatives
? [...prevEntry, ...currEntry.alternatives, currEntry.link]
: [...prevEntry, currEntry.link],
[] as string[],
),
],
Comment on lines +71 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: Not sure about this piece of code here, but UX looks good. I'll loop in @gitpod-io/engineering-webapp for visibility in case this needs a revert or a different approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, no problem!
Basically, this code populates all the top-level admin dashboard routes obtained through the getAdminTabs function, which are then passed to the isSelected function to set the active state for the admin menu on these top-level routes. Hope this makes it a bit clear 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying, @Devansu-Yadav! 🙇

};

const handleFeedbackFormClick = () => {
Expand Down