Skip to content

Commit

Permalink
[dashboard] remember options per repo (#18835)
Browse files Browse the repository at this point in the history
  • Loading branch information
svenefftinge authored Sep 28, 2023
1 parent 83493bd commit ead0ba0
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 77 deletions.
10 changes: 5 additions & 5 deletions components/dashboard/src/user-settings/Preferences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ export default function Preferences() {
[toast, setUser, workspaceTimeout, billingMode],
);

const clearAutostartWorkspaceOptions = useCallback(async () => {
const clearCreateWorkspaceOptions = useCallback(async () => {
if (!user) {
return;
}
AdditionalUserData.set(user, { workspaceAutostartOptions: [] });
setUser(user);
await getGitpodService().server.updateLoggedInUser(user);
toast("Your autostart options were cleared.");
toast("Workspace options have been cleared.");
}, [setUser, toast, user]);

return (
Expand All @@ -107,9 +107,9 @@ export default function Preferences() {
</a>
</Subheading>
<SelectIDE location="preferences" />
<Heading3 className="mt-12">Autostart Options</Heading3>
<Subheading>Forget any saved autostart options for all repositories.</Subheading>
<Button className="mt-4" type="secondary" onClick={clearAutostartWorkspaceOptions}>
<Heading3 className="mt-12">Workspace Options</Heading3>
<Subheading>Clear last used options for creating workspaces.</Subheading>
<Button className="mt-4" type="secondary" onClick={clearCreateWorkspaceOptions}>
Reset Options
</Button>

Expand Down
119 changes: 47 additions & 72 deletions components/dashboard/src/workspaces/CreateWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,33 @@ import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { Deferred } from "@gitpod/gitpod-protocol/lib/util/deferred";
import { FC, FunctionComponent, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { useHistory, useLocation } from "react-router";
import { Link } from "react-router-dom";
import Alert from "../components/Alert";
import { AuthorizeGit, useNeedsGitAuthorization } from "../components/AuthorizeGit";
import { Button } from "../components/Button";
import { LinkButton } from "../components/LinkButton";
import Modal from "../components/Modal";
import RepositoryFinder from "../components/RepositoryFinder";
import SelectIDEComponent from "../components/SelectIDEComponent";
import SelectWorkspaceClassComponent from "../components/SelectWorkspaceClassComponent";
import { UsageLimitReachedModal } from "../components/UsageLimitReachedModal";
import { CheckboxInputField } from "../components/forms/CheckboxInputField";
import { InputField } from "../components/forms/InputField";
import { Heading1 } from "../components/typography/headings";
import { useAuthProviders } from "../data/auth-providers/auth-provider-query";
import { useCurrentOrg } from "../data/organizations/orgs-query";
import { useListProjectsQuery } from "../data/projects/list-projects-query";
import { useCreateWorkspaceMutation } from "../data/workspaces/create-workspace-mutation";
import { useListWorkspacesQuery } from "../data/workspaces/list-workspaces-query";
import { useWorkspaceContext } from "../data/workspaces/resolve-context-query";
import { useDirtyState } from "../hooks/use-dirty-state";
import { openAuthorizeWindow } from "../provider-utils";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { StartWorkspaceError } from "../start/StartPage";
import { VerifyModal } from "../start/VerifyModal";
import { StartWorkspaceOptions } from "../start/start-workspace-options";
import { UserContext, useCurrentUser } from "../user-context";
import { SelectAccountModal } from "../user-settings/SelectAccountModal";
import { settingsPathPreferences } from "../user-settings/settings.routes";
import { WorkspaceEntry } from "./WorkspaceEntry";
import { AuthorizeGit, useNeedsGitAuthorization } from "../components/AuthorizeGit";
import { settingsPathIntegrations } from "../user-settings/settings.routes";
import { useDirtyState } from "../hooks/use-dirty-state";
import { LinkButton } from "../components/LinkButton";
import { InputField } from "../components/forms/InputField";
import Alert from "../components/Alert";
import { WorkspaceEntry } from "./WorkspaceEntry";

export function CreateWorkspacePage() {
const { user, setUser } = useContext(UserContext);
Expand Down Expand Up @@ -68,12 +65,12 @@ export function CreateWorkspacePage() {
const [contextURL, setContextURL] = useState<string | undefined>(
StartWorkspaceOptions.parseContextUrl(location.hash),
);
const [optionsLoaded, setOptionsLoaded] = useState(false);
// Currently this tracks if the user has selected a project from the dropdown
// Need to make sure we initialize this to a project if the url hash value maps to a project's repo url
// Will need to handle multiple projects w/ same repo url
const [selectedProjectID, setSelectedProjectID] = useState<string | undefined>(undefined);
const workspaceContext = useWorkspaceContext(contextURL);
const [rememberOptions, setRememberOptions] = useState(false);
const needsGitAuthorization = useNeedsGitAuthorization();

const storeAutoStartOptions = useCallback(async () => {
Expand All @@ -88,27 +85,25 @@ export function CreateWorkspacePage() {
(e) => !(e.cloneURL === cloneURL && e.organizationId === currentOrg.id),
);

// we only keep the last 20 options
// we only keep the last 40 options
workspaceAutoStartOptions = workspaceAutoStartOptions.slice(-40);

if (rememberOptions) {
workspaceAutoStartOptions.push({
cloneURL,
organizationId: currentOrg.id,
ideSettings: {
defaultIde: selectedIde,
useLatestVersion: useLatestIde,
},
workspaceClass: selectedWsClass,
});
}
// remember options
workspaceAutoStartOptions.push({
cloneURL,
organizationId: currentOrg.id,
ideSettings: {
defaultIde: selectedIde,
useLatestVersion: useLatestIde,
},
workspaceClass: selectedWsClass,
});
AdditionalUserData.set(user, {
workspaceAutostartOptions: workspaceAutoStartOptions,
});
setUser(user);
await getGitpodService().server.updateLoggedInUser(user);
console.log("Stored autostart options", workspaceAutoStartOptions);
}, [currentOrg, rememberOptions, selectedIde, selectedWsClass, setUser, useLatestIde, user, workspaceContext.data]);
}, [currentOrg, selectedIde, selectedWsClass, setUser, useLatestIde, user, workspaceContext.data]);

// see if we have a matching project based on context url and project's repo url
const project = useMemo(() => {
Expand Down Expand Up @@ -273,23 +268,22 @@ export function CreateWorkspacePage() {

// listen on auto start changes
useEffect(() => {
if (!autostart) {
if (!autostart || !optionsLoaded) {
return;
}
createWorkspace();
}, [autostart, createWorkspace]);
}, [autostart, optionsLoaded, createWorkspace]);

// when workspaceContext is available, we look up if options are remembered
useEffect(() => {
const cloneURL = CommitContext.is(workspaceContext.data) && workspaceContext.data.repository.cloneUrl;
if (!cloneURL || autostart) {
if (!cloneURL) {
return undefined;
}
const rememberedOptions = (user?.additionalData?.workspaceAutostartOptions || []).find(
(e) => e.cloneURL === cloneURL && e.organizationId === currentOrg?.id,
);
if (rememberedOptions) {
setRememberOptions(true);
if (!selectedIdeIsDirty) {
setSelectedIde(rememberedOptions.ideSettings?.defaultIde, false);
setUseLatestIde(!!rememberedOptions.ideSettings?.useLatestVersion);
Expand All @@ -298,20 +292,20 @@ export function CreateWorkspacePage() {
if (!selectedWsClassIsDirty) {
setSelectedWsClass(rememberedOptions.workspaceClass, false);
}
if (autostart === undefined) {
setAutostart(true);
}
} else {
setRememberOptions(false);
// reset the ide settings to the user's default IF they haven't changed it manually
if (!selectedIdeIsDirty) {
setSelectedIde(defaultIde, false);
setUseLatestIde(defaultLatestIde);
}
if (!selectedWsClassIsDirty) {
setSelectedWsClass(defaultWorkspaceClass, false);
}
}
setOptionsLoaded(true);
// we only update the remembered options when the workspaceContext changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workspaceContext.data]);
}, [workspaceContext.data, setOptionsLoaded]);

// Need a wrapper here so we call createWorkspace w/o any arguments
const onClickCreate = useCallback(() => createWorkspace(), [createWorkspace]);
Expand All @@ -326,9 +320,24 @@ export function CreateWorkspacePage() {
}
}, [selectedIdeIsDirty, setSelectedIde, workspaceContext.data]);

// on error we disable auto start and consider options loaded
useEffect(() => {
if (workspaceContext.error || createWorkspaceMutation.error) {
setAutostart(false);
setOptionsLoaded(true);
}
}, [workspaceContext.error, createWorkspaceMutation.error]);

// Derive if the continue button is disabled based on current state
const continueButtonDisabled = useMemo(() => {
if (workspaceContext.isLoading || !contextURL || contextURL.length === 0 || !!errorIde || !!errorWsClass) {
if (
autostart ||
workspaceContext.isLoading ||
!contextURL ||
contextURL.length === 0 ||
!!errorIde ||
!!errorWsClass
) {
return true;
}
if (workspaceContext.error) {
Expand All @@ -341,7 +350,7 @@ export function CreateWorkspacePage() {
}

return false;
}, [contextURL, errorIde, errorWsClass, workspaceContext.error, workspaceContext.isLoading]);
}, [autostart, contextURL, errorIde, errorWsClass, workspaceContext.error, workspaceContext.isLoading]);

if (SelectAccountPayload.is(selectAccountError)) {
return (
Expand Down Expand Up @@ -408,7 +417,7 @@ export function CreateWorkspacePage() {
selectedIdeOption={selectedIde}
useLatest={useLatestIde}
disabled={createWorkspaceMutation.isStarting}
loading={workspaceContext.isLoading}
loading={workspaceContext.isLoading || !optionsLoaded}
/>
</InputField>

Expand All @@ -418,7 +427,7 @@ export function CreateWorkspacePage() {
setError={setErrorWsClass}
selectedWorkspaceClass={selectedWsClass}
disabled={createWorkspaceMutation.isStarting}
loading={workspaceContext.isLoading}
loading={workspaceContext.isLoading || !optionsLoaded}
/>
</InputField>
</div>
Expand All @@ -427,20 +436,13 @@ export function CreateWorkspacePage() {
onClick={onClickCreate}
autoFocus={true}
size="block"
loading={createWorkspaceMutation.isStarting}
loading={createWorkspaceMutation.isStarting || autostart}
disabled={continueButtonDisabled}
>
{createWorkspaceMutation.isStarting ? "Opening Workspace ..." : "Continue"}
</Button>
</div>

{workspaceContext.data && (
<RememberOptions
disabled={workspaceContext.isLoading || createWorkspaceMutation.isStarting}
checked={rememberOptions}
onChange={setRememberOptions}
/>
)}
{existingWorkspaces.length > 0 && !createWorkspaceMutation.isStarting && (
<div className="w-full flex flex-col justify-end px-6">
<p className="mt-6 text-center text-base">Running workspaces on this revision</p>
Expand All @@ -462,33 +464,6 @@ export function CreateWorkspacePage() {
);
}

function RememberOptions(params: { disabled?: boolean; checked: boolean; onChange: (checked: boolean) => void }) {
const { disabled, checked, onChange } = params;

return (
<>
<div className={"w-full flex justify-center mt-3 px-8 mx-2"}>
<CheckboxInputField
label="Autostart with these options for this repository."
checked={checked}
disabled={disabled}
topMargin={false}
onChange={onChange}
/>
</div>
<div className={"w-full flex justify-center px-8 mx-2"}>
<p className="text-gray-400 dark:text-gray-500 text-sm">
Don't worry, you can reset this anytime in your{" "}
<Link to={settingsPathPreferences} className="gp-link">
preferences
</Link>
.
</p>
</div>
</>
);
}

function tryAuthorize(host: string, scopes?: string[]): Promise<SelectAccountPayload | undefined> {
const result = new Deferred<SelectAccountPayload | undefined>();
openAuthorizeWindow({
Expand Down

0 comments on commit ead0ba0

Please sign in to comment.