Skip to content

[dashboard] query cache orgs #16685

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

Merged
merged 1 commit into from
Mar 7, 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
25 changes: 9 additions & 16 deletions components/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,22 @@
* See License.AGPL.txt in the project root for license information.
*/

import React, { FunctionComponent, Suspense, useEffect } from "react";
import * as GitpodCookie from "@gitpod/gitpod-protocol/lib/util/gitpod-cookie";
import { Login } from "./Login";
import { isGitpodIo } from "./utils";
import { useUserAndTeamsLoader } from "./hooks/use-user-and-teams-loader";
import { useAnalyticsTracking } from "./hooks/use-analytics-tracking";
import React, { FunctionComponent, Suspense } from "react";
import { AppLoading } from "./app/AppLoading";
import { AppRoutes } from "./app/AppRoutes";
import { useCurrentTeam } from "./teams/teams-context";
import { useHistory } from "react-router";
import { useCurrentOrg } from "./data/organizations/orgs-query";
import { useAnalyticsTracking } from "./hooks/use-analytics-tracking";
import { useUserAndTeamsLoader } from "./hooks/use-user-and-teams-loader";
import { Login } from "./Login";
import { isGitpodIo } from "./utils";

const Setup = React.lazy(() => import(/* webpackPrefetch: true */ "./Setup"));

// Top level Dashboard App component
const App: FunctionComponent = () => {
const { user, teams, isSetupRequired, loading } = useUserAndTeamsLoader();
const currentOrg = useCurrentTeam();
const history = useHistory();
useEffect(() => {
return history.listen((location, action) => {
console.log(location, action);
});
}, [history]);
const { user, isSetupRequired, loading } = useUserAndTeamsLoader();
const currentOrg = useCurrentOrg().data;

// Setup analytics/tracking
useAnalyticsTracking();
Expand Down Expand Up @@ -63,7 +56,7 @@ const App: FunctionComponent = () => {
return (
<Suspense fallback={<AppLoading />}>
{/* Use org id as key to force re-render on org change */}
<AppRoutes key={currentOrg?.id ?? "no-org"} user={user} teams={teams} />
<AppRoutes key={currentOrg?.id ?? "no-org"} />
</Suspense>
);
};
Expand Down
2 changes: 1 addition & 1 deletion components/dashboard/src/AppNotifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function AppNotifications() {
const [notifications, setNotifications] = useState<AppNotification[]>([]);

useEffect(() => {
let localState = getLocalStorageObject(KEY_APP_NOTIFICATIONS);
const localState = getLocalStorageObject(KEY_APP_NOTIFICATIONS);
if (Array.isArray(localState)) {
setNotifications(convertToAppNotification(localState));
return;
Expand Down
9 changes: 1 addition & 8 deletions components/dashboard/src/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { AuthProviderInfo } from "@gitpod/gitpod-protocol";
import * as GitpodCookie from "@gitpod/gitpod-protocol/lib/util/gitpod-cookie";
import { useContext, useEffect, useMemo, useState } from "react";
import { UserContext } from "./user-context";
import { TeamsContext } from "./teams/teams-context";
import { getGitpodService } from "./service/service";
import { iconForAuthProvider, openAuthorizeWindow, simplifyProviderName, getSafeURLRedirect } from "./provider-utils";
import gitpod from "./images/gitpod.svg";
Expand All @@ -23,7 +22,6 @@ import prebuild from "./images/welcome/prebuild.svg";
import exclamation from "./images/exclamation.svg";
import { getURLHash } from "./utils";
import ErrorMessage from "./components/ErrorMessage";
import { publicApiTeamsToProtocol, teamsService } from "./service/public-api";
import { Heading1, Heading2, Subheading } from "./components/typography/headings";

function Item(props: { icon: string; iconSize?: string; text: string }) {
Expand All @@ -50,7 +48,6 @@ export function hasVisitedMarketingWebsiteBefore() {

export function Login() {
const { setUser } = useContext(UserContext);
const { setTeams } = useContext(TeamsContext);

const urlHash = useMemo(() => getURLHash(), []);

Expand Down Expand Up @@ -100,12 +97,8 @@ export function Login() {

const updateUser = async () => {
await getGitpodService().reconnect();
const [user, teams] = await Promise.all([
getGitpodService().server.getLoggedInUser(),
publicApiTeamsToProtocol((await teamsService.listTeams({})).teams),
]);
const [user] = await Promise.all([getGitpodService().server.getLoggedInUser()]);
setUser(user);
setTeams(teams);
markLoggedIn();
};

Expand Down
8 changes: 4 additions & 4 deletions components/dashboard/src/SwitchToPAYG.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import { resetAllNotifications } from "./AppNotifications";
import { Plans } from "@gitpod/gitpod-protocol/lib/plans";
import ContextMenu, { ContextMenuEntry } from "./components/ContextMenu";
import CaretDown from "./icons/CaretDown.svg";
import { TeamsContext, useCurrentTeam } from "./teams/teams-context";
import { Team } from "@gitpod/gitpod-protocol";
import { OrgEntry } from "./menu/OrganizationSelector";
import { useCurrentOrg, useOrganizations } from "./data/organizations/orgs-query";

/**
* Keys of known page params
Expand Down Expand Up @@ -62,8 +62,8 @@ function SwitchToPAYG() {
phase: "call-to-action",
});

const currentOrg = useCurrentTeam();
const { teams } = useContext(TeamsContext);
const currentOrg = useCurrentOrg().data;
const orgs = useOrganizations().data;
const [errorMessage, setErrorMessage] = useState<string | undefined>();
const [selectedOrganization, setSelectedOrganization] = useState<Team | undefined>(undefined);
const [showBillingSetupModal, setShowBillingSetupModal] = useState<boolean>(false);
Expand Down Expand Up @@ -427,7 +427,7 @@ function SwitchToPAYG() {

const planName = pageState.old?.planName || "Legacy Plan";
const planDescription = pageState.old?.planDetails || "";
const selectorEntries = getOrganizationSelectorEntries(teams || [], setSelectedOrganization);
const selectorEntries = getOrganizationSelectorEntries(orgs || [], setSelectedOrganization);
return (
<div className="flex flex-col max-h-screen max-w-3xl mx-auto items-center w-full mt-24">
<h1>{`Update your ${titleModifier}`}</h1>
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/src/Usage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
import UsageView from "./components/UsageView";
import { useCurrentTeam } from "./teams/teams-context";
import { useCurrentOrg } from "./data/organizations/orgs-query";
import { useCurrentUser } from "./user-context";

function Usage() {
const user = useCurrentUser();
const org = useCurrentTeam();
const org = useCurrentOrg().data;

if (!user) {
return <></>;
Expand Down
1 change: 1 addition & 0 deletions components/dashboard/src/admin/BlockedRepositories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function BlockedRepositoriesList(props: Props) {
};
useEffect(() => {
search(); // Initial list
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const add = () => {
Expand Down
1 change: 1 addition & 0 deletions components/dashboard/src/admin/License.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default function License() {
const data = await getGitpodService().server.adminGetLicense();
setLicense(data);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const featureList = license?.enabledFeatures;
Expand Down
6 changes: 2 additions & 4 deletions components/dashboard/src/admin/ProjectsSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,12 @@ export function ProjectsSearch() {
if (currentProject) {
if (currentProject.userId) {
const owner = await getGitpodService().server.adminGetUser(currentProject.userId);
if (owner) {
setCurrentProjectOwner(owner?.name);
}
setCurrentProjectOwner(owner.name);
}
if (currentProject.teamId) {
const owner = await getGitpodService().server.adminGetTeamById(currentProject.teamId);
if (owner) {
setCurrentProjectOwner(owner?.name);
setCurrentProjectOwner(owner.name);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/src/admin/TeamDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ export default function TeamDetail(props: { team: Team }) {
<svg xmlns="http://www.w3.org/2000/svg" fill="none" className="h-4 w-4" viewBox="0 0 16 16">
<path
fill="#A8A29E"
fill-rule="evenodd"
fillRule="evenodd"
d="M13.366 8.234a.8.8 0 010 1.132l-4.8 4.8a.8.8 0 01-1.132 0l-4.8-4.8a.8.8 0 111.132-1.132L7.2 11.67V2.4a.8.8 0 111.6 0v9.269l3.434-3.435a.8.8 0 011.132 0z"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
</ItemField>
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/src/admin/TeamsSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ export function TeamsSearch() {
<svg xmlns="http://www.w3.org/2000/svg" fill="none" className="h-4 w-4" viewBox="0 0 16 16">
<path
fill="#A8A29E"
fill-rule="evenodd"
fillRule="evenodd"
d="M13.366 8.234a.8.8 0 010 1.132l-4.8 4.8a.8.8 0 01-1.132 0l-4.8-4.8a.8.8 0 111.132-1.132L7.2 11.67V2.4a.8.8 0 111.6 0v9.269l3.434-3.435a.8.8 0 011.132 0z"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
</div>
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/src/admin/UserSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ export default function UserSearch() {
<svg xmlns="http://www.w3.org/2000/svg" fill="none" className="h-4 w-4" viewBox="0 0 16 16">
<path
fill="#A8A29E"
fill-rule="evenodd"
fillRule="evenodd"
d="M13.366 8.234a.8.8 0 010 1.132l-4.8 4.8a.8.8 0 01-1.132 0l-4.8-4.8a.8.8 0 111.132-1.132L7.2 11.67V2.4a.8.8 0 111.6 0v9.269l3.434-3.435a.8.8 0 011.132 0z"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
</div>
Expand Down
18 changes: 10 additions & 8 deletions components/dashboard/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* See License.AGPL.txt in the project root for license information.
*/

import { ContextURL, Team, User } from "@gitpod/gitpod-protocol";
import React, { FunctionComponent, useContext, useState } from "react";
import { ContextURL, User } from "@gitpod/gitpod-protocol";
import React, { useContext, useState } from "react";
import { Redirect, Route, Switch, useLocation } from "react-router";
import { AppNotifications } from "../AppNotifications";
import Menu from "../menu/Menu";
Expand Down Expand Up @@ -49,6 +49,7 @@ import { StartWorkspaceOptions } from "../start/start-workspace-options";
import { useFeatureFlags } from "../contexts/FeatureFlagContext";
import { FORCE_ONBOARDING_PARAM, FORCE_ONBOARDING_PARAM_VALUE } from "../onboarding/UserOnboarding";
import { Heading1, Subheading } from "../components/typography/headings";
import { useCurrentUser } from "../user-context";

const Setup = React.lazy(() => import(/* webpackPrefetch: true */ "../Setup"));
const Workspaces = React.lazy(() => import(/* webpackPrefetch: true */ "../workspaces/Workspaces"));
Expand Down Expand Up @@ -95,17 +96,18 @@ const Usage = React.lazy(() => import(/* webpackPrefetch: true */ "../Usage"));
const UserOnboarding = React.lazy(() => import(/* webpackPrefetch: true */ "../onboarding/UserOnboarding"));
const SwitchToPAYG = React.lazy(() => import(/* webpackPrefetch: true */ "../SwitchToPAYG"));

type AppRoutesProps = {
user: User;
teams?: Team[];
};
export const AppRoutes: FunctionComponent<AppRoutesProps> = ({ user, teams }) => {
export const AppRoutes = () => {
const hash = getURLHash();
const user = useCurrentUser();
const { startWorkspaceModalProps, setStartWorkspaceModalProps } = useContext(StartWorkspaceModalContext);
const [isWhatsNewShown, setWhatsNewShown] = useState(shouldSeeWhatsNew(user));
const [isWhatsNewShown, setWhatsNewShown] = useState(user && shouldSeeWhatsNew(user));
const newCreateWsPage = useNewCreateWorkspacePage();
const location = useLocation();
const { newSignupFlow } = useFeatureFlags();

if (!user) {
return <></>;
}
const search = new URLSearchParams(location.search);

// TODO: Add a Route for this instead of inspecting location manually
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/src/app/OrgRequiredRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
*/

import { Redirect, Route } from "react-router";
import { useCurrentTeam } from "../teams/teams-context";
import { useCurrentOrg } from "../data/organizations/orgs-query";

// A wrapper for <Route> that redirects to "/" if there is not an organization currently selected
// Having a check for an active org at the route level allows us to avoid any org-dependant api calls we might make
// in page level components, and having to check for an org there
export function OrgRequiredRoute({ component }: any) {
const org = useCurrentTeam();
const org = useCurrentOrg().data;

return <Route render={() => (!!org ? <Route component={component} /> : <Redirect to={"/"} />)} />;
}
57 changes: 19 additions & 38 deletions components/dashboard/src/components/BillingAccountSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,26 @@
* See License.AGPL.txt in the project root for license information.
*/

import { useContext, useEffect, useState } from "react";
import { Team, TeamMemberInfo } from "@gitpod/gitpod-protocol";
import { Team } from "@gitpod/gitpod-protocol";
import { AttributionId, AttributionTarget } from "@gitpod/gitpod-protocol/lib/attribution";
import { getGitpodService } from "../service/service";
import { useTeams } from "../teams/teams-context";
import { UserContext } from "../user-context";
import { useContext, useEffect, useState } from "react";
import SelectableCardSolid from "../components/SelectableCardSolid";
import { OrganizationInfo, useOrganizations } from "../data/organizations/orgs-query";
import { ReactComponent as Spinner } from "../icons/Spinner.svg";
import { getGitpodService } from "../service/service";
import { UserContext } from "../user-context";
import Alert from "./Alert";
import { publicApiTeamMembersToProtocol, teamsService } from "../service/public-api";
import { Subheading } from "./typography/headings";

export function BillingAccountSelector(props: { onSelected?: () => void }) {
const { user, setUser } = useContext(UserContext);
const teams = useTeams();
const [teamsAvailableForAttribution, setTeamsAvailableForAttribution] = useState<Team[] | undefined>();
const [membersByTeam, setMembersByTeam] = useState<Record<string, TeamMemberInfo[]>>({});
const orgs = useOrganizations();
const [teamsAvailableForAttribution, setTeamsAvailableForAttribution] = useState<OrganizationInfo[]>([]);
const [errorMessage, setErrorMessage] = useState<string | undefined>();

useEffect(() => {
if (!teams) {
setTeamsAvailableForAttribution(undefined);
if (orgs.isLoading) {
setTeamsAvailableForAttribution([]);
return;
}

Expand All @@ -38,7 +36,7 @@ export function BillingAccountSelector(props: { onSelected?: () => void }) {
if (attrId?.kind !== "team") {
continue;
}
const team = teams.find((t) => t.id === attrId.teamId);
const team = orgs.data?.find((t) => t.id === attrId.teamId);
if (team) {
teamsAvailableForAttribution.push(team);
}
Expand All @@ -51,20 +49,7 @@ export function BillingAccountSelector(props: { onSelected?: () => void }) {
console.error("Could not get list of available billing accounts.", error);
setErrorMessage(`Could not get list of available billing accounts. ${error?.message || String(error)}`);
});

const members: Record<string, TeamMemberInfo[]> = {};
Promise.all(
teams.map(async (team) => {
try {
members[team.id] = publicApiTeamMembersToProtocol(
(await teamsService.getTeam({ teamId: team!.id })).team?.members || [],
);
} catch (error) {
console.warn("Could not get members of org", team, error);
}
}),
).then(() => setMembersByTeam(members));
}, [teams]);
}, [orgs]);

const setUsageAttributionTeam = async (team?: Team) => {
if (!user) {
Expand Down Expand Up @@ -94,8 +79,8 @@ export function BillingAccountSelector(props: { onSelected?: () => void }) {
{errorMessage}
</Alert>
)}
{teamsAvailableForAttribution === undefined && <Spinner className="m-2 h-5 w-5 animate-spin" />}
{teamsAvailableForAttribution && (
{orgs.isLoading && <Spinner className="m-2 h-5 w-5 animate-spin" />}
{orgs.data && (
<div>
<Subheading className="text-gray-500">
Associate usage without a project to the billing account below.{" "}
Expand Down Expand Up @@ -127,24 +112,20 @@ export function BillingAccountSelector(props: { onSelected?: () => void }) {
</div>
</SelectableCardSolid>
)}
{teamsAvailableForAttribution.map((t) => (
{teamsAvailableForAttribution.map((org) => (
<SelectableCardSolid
className="h-18"
title={t.name}
selected={isSelected("team", t.id)}
onClick={() => setUsageAttributionTeam(t)}
title={org.name}
selected={isSelected("team", org.id)}
onClick={() => setUsageAttributionTeam(org)}
>
<div className="flex-grow flex items-end px-1">
<span
className={`text-sm text-gray-400${
isSelected("team", t.id) ? " dark:text-gray-600" : ""
isSelected("team", org.id) ? " dark:text-gray-600" : ""
}`}
>
{!!membersByTeam[t.id]
? `${membersByTeam[t.id].length} member${
membersByTeam[t.id].length === 1 ? "" : "s"
}`
: "..."}
{`${org.members.length} member${org.members.length === 1 ? "" : "s"}`}
</span>
</div>
</SelectableCardSolid>
Expand Down
Loading