Skip to content

[dashboard] Always start with options #16391

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
Feb 17, 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
9 changes: 8 additions & 1 deletion components/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License.AGPL.txt in the project root for license information.
*/

import React, { FunctionComponent, Suspense } from "react";
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";
Expand All @@ -13,13 +13,20 @@ import { useAnalyticsTracking } from "./hooks/use-analytics-tracking";
import { AppLoading } from "./app/AppLoading";
import { AppRoutes } from "./app/AppRoutes";
import { useCurrentTeam } from "./teams/teams-context";
import { useHistory } from "react-router";

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]);

// Setup analytics/tracking
useAnalyticsTracking();
Expand Down
81 changes: 44 additions & 37 deletions components/dashboard/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,47 @@
* 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, Team } from "@gitpod/gitpod-protocol";
import SelectIDEModal from "../user-settings/SelectIDEModal";
import { StartPage, StartPhase } from "../start/StartPage";
import { getURLHash, isGitpodIo, isLocalPreview } from "../utils";
import { shouldSeeWhatsNew, WhatsNew } from "../whatsnew/WhatsNew";
import { Redirect, Route, Switch } from "react-router";
import { Redirect, Route, Switch, useLocation } from "react-router";
import { AppNotifications } from "../AppNotifications";
import Menu from "../menu/Menu";
import OAuthClientApproval from "../OauthClientApproval";
import { projectsPathInstallGitHubApp, projectsPathNew } from "../projects/projects.routes";
import { StartPage, StartPhase } from "../start/StartPage";
import { parseProps } from "../start/StartWorkspace";
import { AppNotifications } from "../AppNotifications";
import { AdminRoute } from "./AdminRoute";
import { StartWorkspaceModal } from "../workspaces/StartWorkspaceModal";
import SelectIDEModal from "../user-settings/SelectIDEModal";
import {
settingsPathAccount,
settingsPathBilling,
settingsPathIntegrations,
settingsPathMain,
settingsPathNotifications,
settingsPathPersonalAccessTokenCreate,
settingsPathPersonalAccessTokenEdit,
settingsPathPersonalAccessTokens,
settingsPathPlans,
settingsPathPreferences,
settingsPathVariables,
settingsPathSSHKeys,
settingsPathVariables,
usagePathMain,
settingsPathPersonalAccessTokens,
settingsPathPersonalAccessTokenCreate,
settingsPathPersonalAccessTokenEdit,
} from "../user-settings/settings.routes";
import { projectsPathInstallGitHubApp, projectsPathNew } from "../projects/projects.routes";
import { getURLHash, isGitpodIo, isLocalPreview } from "../utils";
import { shouldSeeWhatsNew, WhatsNew } from "../whatsnew/WhatsNew";
import { StartWorkspaceModal } from "../workspaces/StartWorkspaceModal";
import { workspacesPathMain } from "../workspaces/workspaces.routes";
import { LocalPreviewAlert } from "./LocalPreviewAlert";
import OAuthClientApproval from "../OauthClientApproval";
import { AdminRoute } from "./AdminRoute";
import { Blocked } from "./Blocked";
import { LocalPreviewAlert } from "./LocalPreviewAlert";

// TODO: Can we bundle-split/lazy load these like other pages?
import { BlockedRepositories } from "../admin/BlockedRepositories";
import PersonalAccessTokenCreateView from "../user-settings/PersonalAccessTokensCreateView";
import { CreateWorkspacePage, useNewCreateWorkspacePage } from "../workspaces/CreateWorkspacePage";
import { StartWorkspaceModalContext } from "../workspaces/start-workspace-modal-context";
import { StartWorkspaceOptions } from "../start/start-workspace-options";
import { WebsocketClients } from "./WebsocketClients";
import { OrgRequiredRoute } from "./OrgRequiredRoute";
import { WebsocketClients } from "./WebsocketClients";
import { StartWorkspaceOptions } from "../start/start-workspace-options";

const Setup = React.lazy(() => import(/* webpackPrefetch: true */ "../Setup"));
const Workspaces = React.lazy(() => import(/* webpackPrefetch: true */ "../workspaces/Workspaces"));
Expand All @@ -61,7 +62,6 @@ const Preferences = React.lazy(() => import(/* webpackPrefetch: true */ "../user
const PersonalAccessTokens = React.lazy(
() => import(/* webpackPrefetch: true */ "../user-settings/PersonalAccessTokens"),
);
const Open = React.lazy(() => import(/* webpackPrefetch: true */ "../start/Open"));
const StartWorkspace = React.lazy(() => import(/* webpackPrefetch: true */ "../start/StartWorkspace"));
const CreateWorkspace = React.lazy(() => import(/* webpackPrefetch: true */ "../start/CreateWorkspace"));
const NewTeam = React.lazy(() => import(/* webpackPrefetch: true */ "../teams/NewTeam"));
Expand Down Expand Up @@ -97,6 +97,8 @@ export const AppRoutes: FunctionComponent<AppRoutesProps> = ({ user, teams }) =>
const hash = getURLHash();
const { startWorkspaceModalProps, setStartWorkspaceModalProps } = useContext(StartWorkspaceModalContext);
const [isWhatsNewShown, setWhatsNewShown] = useState(shouldSeeWhatsNew(user));
const newCreateWsPage = useNewCreateWorkspacePage();
const location = useLocation();

// Prefix with `/#referrer` will specify an IDE for workspace
// We don't need to show IDE preference in this case
Expand All @@ -105,12 +107,12 @@ export const AppRoutes: FunctionComponent<AppRoutesProps> = ({ user, teams }) =>
);

// TODO: Add a Route for this instead of inspecting location manually
if (window.location.pathname.startsWith("/blocked")) {
if (location.pathname.startsWith("/blocked")) {
return <Blocked />;
}

// TODO: Add a Route for this instead of inspecting location manually
if (window.location.pathname.startsWith("/oauth-approval")) {
if (location.pathname.startsWith("/oauth-approval")) {
return <OAuthClientApproval />;
}

Expand All @@ -119,27 +121,16 @@ export const AppRoutes: FunctionComponent<AppRoutesProps> = ({ user, teams }) =>
}

// TODO: Try and encapsulate this in a route for "/" (check for hash in route component, render or redirect accordingly)
const isCreation = window.location.pathname === "/" && hash !== "";
const isCreation = location.pathname === "/" && hash !== "";
if (isCreation) {
if (showUserIdePreference) {
return (
<StartPage phase={StartPhase.Checking}>
<SelectIDEModal location="workspace_start" onClose={() => setShowUserIdePreference(false)} />
</StartPage>
);
} else if (new URLSearchParams(window.location.search).has("showOptions")) {
const props = StartWorkspaceOptions.parseSearchParams(window.location.search);
return (
<StartWorkspaceModal
{...{
contextUrl: hash,
ide: props?.ideSettings?.defaultIde,
uselatestIde: props?.ideSettings?.useLatestVersion,
workspaceClass: props.workspaceClass,
onClose: undefined,
}}
/>
);
} else if (new URLSearchParams(location.search).has("showOptions") || newCreateWsPage) {
return <Redirect to={"/new" + location.pathname + location.search + location.hash} />;
} else {
return <CreateWorkspace contextUrl={hash} />;
}
Expand All @@ -160,15 +151,31 @@ export const AppRoutes: FunctionComponent<AppRoutesProps> = ({ user, teams }) =>
return <div></div>;
}

if (newCreateWsPage && startWorkspaceModalProps) {
const search = StartWorkspaceOptions.toSearchParams({
ideSettings: {
defaultIde: startWorkspaceModalProps.ide,
useLatestVersion: startWorkspaceModalProps.uselatestIde,
},
workspaceClass: startWorkspaceModalProps.workspaceClass,
});
const hash = startWorkspaceModalProps.contextUrl ? "#" + startWorkspaceModalProps.contextUrl : "";
setStartWorkspaceModalProps(undefined);
return <Redirect to={"/new/" + search + hash} />;
}

return (
<Route>
<div className="container">
<Menu />
{isLocalPreview() && <LocalPreviewAlert />}
<AppNotifications />
<Switch>
<Route path="/new" exact component={CreateWorkspacePage} />
<Route path={projectsPathNew} exact component={NewProject} />
<Route path="/open" exact component={Open} />
<Route path="/open">
<Redirect to="/new" />
</Route>
<Route path="/setup" exact component={Setup} />
<Route path={workspacesPathMain} exact component={Workspaces} />
<Route path={settingsPathAccount} exact component={Account} />
Expand Down Expand Up @@ -297,7 +304,7 @@ export const AppRoutes: FunctionComponent<AppRoutesProps> = ({ user, teams }) =>
}}
></Route>
</Switch>
{startWorkspaceModalProps && (
{startWorkspaceModalProps && !newCreateWsPage && (
<StartWorkspaceModal
{...startWorkspaceModalProps}
onClose={startWorkspaceModalProps.onClose || (() => setStartWorkspaceModalProps(undefined))}
Expand Down
5 changes: 5 additions & 0 deletions components/dashboard/src/contexts/FeatureFlagContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface FeatureFlagConfig {
}

const FeatureFlagContext = createContext<{
startWithOptions: boolean;
showUsageView: boolean;
isUsageBasedBillingEnabled: boolean;
showUseLastSuccessfulPrebuild: boolean;
Expand All @@ -23,6 +24,7 @@ const FeatureFlagContext = createContext<{
oidcServiceEnabled: boolean;
orgGitAuthProviders: boolean;
}>({
startWithOptions: false,
showUsageView: false,
isUsageBasedBillingEnabled: false,
showUseLastSuccessfulPrebuild: false,
Expand All @@ -37,6 +39,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
const teams = useTeams();
const { project } = useContext(ProjectContext);
const team = useCurrentTeam();
const [startWithOptions, setStartWithOptions] = useState<boolean>(false);
const [showUsageView, setShowUsageView] = useState<boolean>(false);
const [isUsageBasedBillingEnabled, setIsUsageBasedBillingEnabled] = useState<boolean>(false);
const [showUseLastSuccessfulPrebuild, setShowUseLastSuccessfulPrebuild] = useState<boolean>(false);
Expand All @@ -49,6 +52,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
if (!user) return;
(async () => {
const featureFlags: FeatureFlagConfig = {
start_with_options: { defaultValue: false, setter: setStartWithOptions },
usage_view: { defaultValue: false, setter: setShowUsageView },
isUsageBasedBillingEnabled: { defaultValue: false, setter: setIsUsageBasedBillingEnabled },
showUseLastSuccessfulPrebuild: { defaultValue: false, setter: setShowUseLastSuccessfulPrebuild },
Expand Down Expand Up @@ -98,6 +102,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
return (
<FeatureFlagContext.Provider
value={{
startWithOptions,
showUsageView,
isUsageBasedBillingEnabled,
showUseLastSuccessfulPrebuild,
Expand Down
3 changes: 2 additions & 1 deletion components/dashboard/src/projects/NewProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import exclamation from "../images/exclamation.svg";
import ErrorMessage from "../components/ErrorMessage";
import Spinner from "../icons/Spinner.svg";
import { useRefreshProjects } from "../data/projects/list-projects-query";
import { projectsPathNew } from "./projects.routes";

export default function NewProject() {
const currentTeam = useCurrentTeam();
Expand Down Expand Up @@ -625,7 +626,7 @@ function GitProviders(props: {

async function openReconfigureWindow(params: { account?: string; onSuccess: (p: any) => void }) {
const { account, onSuccess } = params;
const state = btoa(JSON.stringify({ from: "/reconfigure", next: "/new" }));
const state = btoa(JSON.stringify({ from: "/reconfigure", next: projectsPathNew }));
const url = gitpodHostUrl
.withApi({
pathname: "/apps/github/reconfigure",
Expand Down
25 changes: 16 additions & 9 deletions components/dashboard/src/projects/Project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import NoAccess from "../icons/NoAccess.svg";
import { ReactComponent as Spinner } from "../icons/Spinner.svg";
import { openAuthorizeWindow } from "../provider-utils";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { useNewCreateWorkspacePage } from "../workspaces/CreateWorkspacePage";
import { StartWorkspaceModalContext } from "../workspaces/start-workspace-modal-context";
import { prebuildStatusIcon, prebuildStatusLabel } from "./Prebuilds";
import { useCurrentProject } from "./project-context";
Expand All @@ -39,6 +40,8 @@ export default function ProjectsPage() {

const [showAuthBanner, setShowAuthBanner] = useState<{ host: string } | undefined>(undefined);

const isNewCreateWsPage = useNewCreateWorkspacePage();

useEffect(() => {
// project changed, reset state
setBranches([]);
Expand Down Expand Up @@ -375,15 +378,19 @@ export default function ProjectsPage() {
<ItemFieldContextMenu
className="py-0.5"
menuEntries={[
{
title: "New Workspace ...",
onClick: () =>
setStartWorkspaceModalProps({
contextUrl: branch.url,
allowContextUrlChange: true,
}),
separator: true,
},
...(isNewCreateWsPage
? []
: [
{
title: "New Workspace ...",
onClick: () =>
setStartWorkspaceModalProps({
contextUrl: branch.url,
allowContextUrlChange: true,
}),
separator: true,
},
]),
prebuild?.status === "queued" ||
prebuild?.status === "building"
? {
Expand Down
24 changes: 15 additions & 9 deletions components/dashboard/src/projects/ProjectListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { prebuildStatusIcon } from "./Prebuilds";
import { gitpodHostUrl } from "../service/service";
import { useLatestProjectPrebuildQuery } from "../data/prebuilds/latest-project-prebuild-query";
import { StartWorkspaceModalContext } from "../workspaces/start-workspace-modal-context";
import { useNewCreateWorkspacePage } from "../workspaces/CreateWorkspacePage";

type ProjectListItemProps = {
project: Project;
Expand All @@ -25,6 +26,7 @@ export const ProjectListItem: FunctionComponent<ProjectListItemProps> = ({ proje
const [showRemoveModal, setShowRemoveModal] = useState(false);
const { data: prebuild, isLoading } = useLatestProjectPrebuildQuery({ projectId: project.id });
const { setStartWorkspaceModalProps } = useContext(StartWorkspaceModalContext);
const isNewCreateWsPage = useNewCreateWorkspacePage();

return (
<div key={`project-${project.id}`} className="h-52">
Expand All @@ -41,15 +43,19 @@ export const ProjectListItem: FunctionComponent<ProjectListItemProps> = ({ proje
href: gitpodHostUrl.withContext(`${project.cloneUrl}`).toString(),
separator: true,
},
{
title: "New Workspace ...",
onClick: () =>
setStartWorkspaceModalProps({
contextUrl: project.cloneUrl,
allowContextUrlChange: true,
}),
separator: true,
},
...(isNewCreateWsPage
? []
: [
{
title: "New Workspace ...",
onClick: () =>
setStartWorkspaceModalProps({
contextUrl: project.cloneUrl,
allowContextUrlChange: true,
}),
separator: true,
},
]),
{
title: "Remove Project",
customFontStyle:
Expand Down
Loading