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

[UBP] View without data or access #11554

Merged
merged 1 commit into from
Jul 22, 2022
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
265 changes: 146 additions & 119 deletions components/dashboard/src/teams/TeamUsage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { useContext, useEffect, useState } from "react";
import { Redirect, useLocation } from "react-router";
import { getCurrentTeam, TeamsContext } from "./teams-context";
import { PaymentContext } from "../payment-context";
import { getGitpodService } from "../service/service";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { BillableSession, BillableWorkspaceType } from "@gitpod/gitpod-protocol/lib/usage";
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
import { Item, ItemField, ItemsList } from "../components/ItemsList";
import moment from "moment";
import Pagination from "../components/Pagination";
import Header from "../components/Header";
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";

function TeamUsage() {
const { teams } = useContext(TeamsContext);
Expand All @@ -24,15 +25,22 @@ function TeamUsage() {
const [billedUsage, setBilledUsage] = useState<BillableSession[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [resultsPerPage] = useState(10);
const [errorMessage, setErrorMessage] = useState("");

useEffect(() => {
if (!team) {
return;
}
(async () => {
const attributionId = AttributionId.render({ kind: "team", teamId: team.id });
const billedUsageResult = await getGitpodService().server.listBilledUsage(attributionId);
setBilledUsage(billedUsageResult);
try {
const billedUsageResult = await getGitpodService().server.listBilledUsage(attributionId);
setBilledUsage(billedUsageResult);
} catch (error) {
if (error.code === ErrorCodes.PERMISSION_DENIED) {
setErrorMessage("Access to usage details is restricted to team owners.");
}
}
})();
}, [team]);

Expand Down Expand Up @@ -75,128 +83,147 @@ function TeamUsage() {
<>
<Header title="Usage" subtitle="Manage team usage." />
<div className="app-container pt-9">
<div className="flex space-x-16">
<div className="flex">
<div className="space-y-8 mb-6" style={{ width: "max-content" }}>
<div className="flex flex-col truncate">
<div className="text-base text-gray-500 truncate">Period</div>
<div className="text-lg text-gray-600 font-semibold truncate">June 2022</div>
</div>
<div className="flex flex-col truncate">
<div className="text-base text-gray-500">Total usage</div>
<div className="flex text-lg text-gray-600 font-semibold">
<svg
className="my-auto mr-1"
width="20"
height="20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5 2a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm5.2 11.4a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4Z"
fill="url(#a)"
/>
<defs>
<linearGradient
id="a"
x1="4.3"
y1="4.3"
x2="16.071"
y2="17.107"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#FFAD33" />
<stop offset="1" stop-color="#FF8A00" />
</linearGradient>
</defs>
</svg>
<span>{calculateTotalUsage()} Total Credits</span>
{errorMessage && <p className="text-base">{errorMessage}</p>}
{!errorMessage && (
<div className="flex space-x-16">
<div className="flex">
<div className="space-y-8 mb-6" style={{ width: "max-content" }}>
<div className="flex flex-col truncate">
<div className="text-base text-gray-500 truncate">Period</div>
<div className="text-lg text-gray-600 font-semibold truncate">June 2022</div>
</div>
<div className="flex flex-col truncate">
<div className="text-base text-gray-500">Total usage</div>
<div className="flex text-lg text-gray-600 font-semibold">
<svg
className="my-auto mr-1"
width="20"
height="20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5 2a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm5.2 11.4a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4Z"
fill="url(#a)"
/>
<defs>
<linearGradient
id="a"
x1="4.3"
y1="4.3"
x2="16.071"
y2="17.107"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#FFAD33" />
<stop offset="1" stop-color="#FF8A00" />
</linearGradient>
</defs>
</svg>
<span>{calculateTotalUsage()} Total Credits</span>
</div>
</div>
</div>
</div>
</div>
<div className="flex flex-col w-full mb-8">
<h3>All Usage</h3>
<span className="text-gray-500 mb-5">View usage details of all team members.</span>
<ItemsList className="mt-2 text-gray-500">
<Item header={false} className="grid grid-cols-5 bg-gray-100 mb-5">
<ItemField className="my-auto">
<span>Type</span>
</ItemField>
<ItemField className="my-auto">
<span>Class</span>
</ItemField>
<ItemField className="my-auto">
<span>Usage</span>
</ItemField>
<ItemField className="flex my-auto">
<svg
className="my-auto mr-1"
width="20"
height="20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5 2a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm5.2 11.4a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4Z"
fill="url(#a)"
/>
<defs>
<linearGradient
id="a"
x1="4.3"
y1="4.3"
x2="16.071"
y2="17.107"
gradientUnits="userSpaceOnUse"
{billedUsage.length === 0 && !errorMessage && (
<div className="flex flex-col w-full mb-8">
<h3 className="text-center text-gray-500 mt-8">No sessions found.</h3>
<p className="text-center text-gray-500 mt-1">
Have you started any
<a className="gp-link" href={gitpodHostUrl.asWorkspacePage().toString()}>
{" "}
workspaces
</a>{" "}
or checked your other teams?
</p>
</div>
)}
{billedUsage.length > 0 && (
<div className="flex flex-col w-full mb-8">
<h3>All Usage</h3>
<span className="text-gray-500 mb-5">View usage details of all team members.</span>
<ItemsList className="mt-2 text-gray-500">
<Item header={false} className="grid grid-cols-5 bg-gray-100 mb-5">
<ItemField className="my-auto">
<span>Type</span>
</ItemField>
<ItemField className="my-auto">
<span>Class</span>
</ItemField>
<ItemField className="my-auto">
<span>Usage</span>
</ItemField>
<ItemField className="flex my-auto">
<svg
className="my-auto mr-1"
width="20"
height="20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<stop stop-color="#FFAD33" />
<stop offset="1" stop-color="#FF8A00" />
</linearGradient>
</defs>
</svg>
<span>Credits</span>
</ItemField>
<ItemField className="my-auto" />
</Item>
{currentPaginatedResults.map((usage) => (
<div
key={usage.instanceId}
className="flex p-3 grid grid-cols-5 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
>
<div className="my-auto">
<span>{getType(usage.workspaceType)}</span>
</div>
<div className="my-auto">
<span className="text-gray-400">{usage.workspaceClass}</span>
</div>
<div className="my-auto">
<span className="text-gray-700">{getMinutes(usage)}</span>
</div>
<div className="my-auto">
<span className="text-gray-700">{usage.credits.toFixed(1)}</span>
</div>
<div className="my-auto">
<span className="text-gray-400">
{moment(new Date(usage.startTime).toDateString()).fromNow()}
</span>
</div>
</div>
))}
</ItemsList>
{billedUsage.length > resultsPerPage && (
<Pagination
currentPage={currentPage}
setCurrentPage={setCurrentPage}
numberOfPages={numberOfPages}
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5 2a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V5a3 3 0 0 0-3-3H5Zm5.2 11.4a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4Z"
fill="url(#a)"
/>
<defs>
<linearGradient
id="a"
x1="4.3"
y1="4.3"
x2="16.071"
y2="17.107"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#FFAD33" />
<stop offset="1" stop-color="#FF8A00" />
</linearGradient>
</defs>
</svg>
<span>Credits</span>
</ItemField>
<ItemField className="my-auto" />
</Item>
{currentPaginatedResults &&
currentPaginatedResults.map((usage) => (
<div
key={usage.instanceId}
className="flex p-3 grid grid-cols-5 justify-between transition ease-in-out rounded-xl focus:bg-gitpod-kumquat-light"
>
<div className="my-auto">
<span>{getType(usage.workspaceType)}</span>
</div>
<div className="my-auto">
<span className="text-gray-400">{usage.workspaceClass}</span>
</div>
<div className="my-auto">
<span className="text-gray-700">{getMinutes(usage)}</span>
</div>
<div className="my-auto">
<span className="text-gray-700">{usage.credits.toFixed(1)}</span>
</div>
<div className="my-auto">
<span className="text-gray-400">
{moment(new Date(usage.startTime).toDateString()).fromNow()}
Copy link
Member

Choose a reason for hiding this comment

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

👍

</span>
</div>
</div>
))}
</ItemsList>
{billedUsage.length > resultsPerPage && (
<Pagination
currentPage={currentPage}
setCurrentPage={setCurrentPage}
numberOfPages={numberOfPages}
/>
)}
</div>
)}
</div>
</div>
)}
</div>
</>
);
Expand Down
4 changes: 4 additions & 0 deletions components/gitpod-protocol/src/util/gitpod-host-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export class GitpodHostUrl {
return this.with((url) => ({ protocol: url.protocol === "https:" ? "wss:" : "ws:" }));
}

asWorkspacePage(): GitpodHostUrl {
return this.with((url) => ({ pathname: "/workspaces" }));
}

asDashboard(): GitpodHostUrl {
return this.with((url) => ({ pathname: "/" }));
}
Expand Down