Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
5 changes: 1 addition & 4 deletions components/dashboard/src/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,7 @@ const LoginContent = ({
</LoginButton>
))
)}
<SSOLoginForm
onSuccess={authorizeSuccessful}
singleOrgMode={!!authProviders.data && authProviders.data.length === 0}
/>
<SSOLoginForm onSuccess={authorizeSuccessful} />
</div>
{errorMessage && <ErrorMessage imgSrc={exclamation} message={errorMessage} />}
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/dashboard/src/data/featureflag-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ const featureFlags = {
// Default to true to enable on gitpod dedicated until ff support is added for dedicated
orgGitAuthProviders: true,
userGitAuthProviders: false,
enableDedicatedOnboardingFlow: false,
// Local SSH feature of VS Code Desktop Extension
gitpod_desktop_use_local_ssh_proxy: false,
enabledOrbitalDiscoveries: "",
repositoryFinderSearch: false,
// dummy specified dataops feature, default false
dataops: false,
enable_multi_org: false,
showBrowserExtensionPromotion: false,
enable_experimental_jbtb: false,
enabled_configuration_prebuild_full_clone: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ export const useInstallationDefaultWorkspaceImageQuery = () => {
},
});
};

export const useInstallationConfiguration = () => {
return useQuery({
queryKey: ["installation-configuration"],
staleTime: 1000 * 60 * 10, // 10 minute
queryFn: async () => {
const response = await installationClient.getInstallationConfiguration({});
return response.configuration;
},
});
};
23 changes: 22 additions & 1 deletion components/dashboard/src/data/organizations/orgs-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,31 @@ export function useCurrentOrg(): { data?: Organization; isLoading: boolean } {
return { data: undefined, isLoading: true };
}
let orgId = localStorage.getItem("active-org");
let org: Organization | undefined = undefined;
if (orgId) {
org = orgs.data.find((org) => org.id === orgId);
}

// 1. Check for org slug
const orgSlugParam = getOrgSlugFromQuery(location.search);
if (orgSlugParam) {
org = orgs.data.find((org) => org.slug === orgSlugParam);
}

// 2. Check for org id
// id is more speficic than slug, so it takes precedence
const orgIdParam = new URLSearchParams(location.search).get("org");
if (orgIdParam) {
orgId = orgIdParam;
org = orgs.data.find((org) => org.id === orgId);
}
let org = orgs.data.find((org) => org.id === orgId);

// 3. Fallback: pick the first org
if (!org) {
org = orgs.data[0];
}

// Persist the selected org
if (org) {
localStorage.setItem("active-org", org.id);
} else if (orgId && (orgs.isLoading || orgs.isStale)) {
Expand All @@ -79,3 +96,7 @@ export function useCurrentOrg(): { data?: Organization; isLoading: boolean } {
}
return { data: org, isLoading: false };
}

export function getOrgSlugFromQuery(search: string): string | undefined {
return new URLSearchParams(search).get("orgSlug") || undefined;
}
15 changes: 8 additions & 7 deletions components/dashboard/src/dedicated-setup/use-needs-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@
*/

import { useQuery } from "@tanstack/react-query";
import { useFeatureFlag } from "../data/featureflag-query";
import { noPersistence } from "../data/setup";
import { installationClient } from "../service/public-api";
import { GetOnboardingStateRequest } from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";

/**
* @description Returns a flage stating if the current installation still needs setup before it can be used. Also returns an isLoading indicator as the check is async
*/
export const useNeedsSetup = () => {
const { data: onboardingState, isLoading } = useOnboardingState();
const enableDedicatedOnboardingFlow = useFeatureFlag("enableDedicatedOnboardingFlow");
const { data: installationConfig } = useInstallationConfiguration();
const isDedicatedInstallation = !!installationConfig?.isDedicatedInstallation;

// This needs to only be true if we've loaded the onboarding state
let needsSetup = !isLoading && onboardingState && onboardingState.completed !== true;
Expand All @@ -25,14 +26,14 @@ export const useNeedsSetup = () => {
}

return {
needsSetup: enableDedicatedOnboardingFlow && needsSetup,
needsSetup: isDedicatedInstallation && needsSetup,
// disabled queries stay in `isLoading` state, so checking feature flag here too
isLoading: enableDedicatedOnboardingFlow && isLoading,
isLoading: isDedicatedInstallation && isLoading,
};
};

const useOnboardingState = () => {
const enableDedicatedOnboardingFlow = useFeatureFlag("enableDedicatedOnboardingFlow");
export const useOnboardingState = () => {
const { data: installationConfig } = useInstallationConfiguration();

return useQuery(
noPersistence(["onboarding-state"]),
Expand All @@ -42,7 +43,7 @@ const useOnboardingState = () => {
},
{
// Only query if feature flag is enabled
enabled: enableDedicatedOnboardingFlow,
enabled: !!installationConfig?.isDedicatedInstallation,
},
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
*/

import { useQueryParams } from "../hooks/use-query-params";
import { useFeatureFlag } from "../data/featureflag-query";
import { useCallback, useState } from "react";
import { isCurrentHostExcludedFromSetup, useNeedsSetup } from "./use-needs-setup";
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";

const FORCE_SETUP_PARAM = "dedicated-setup";
const FORCE_SETUP_PARAM_VALUE = "force";
Expand All @@ -21,7 +21,8 @@ export const useShowDedicatedSetup = () => {
// again in case onboarding state isn't updated right away
const [inProgress, setInProgress] = useState(false);

const enableDedicatedOnboardingFlow = useFeatureFlag("enableDedicatedOnboardingFlow");
const { data: installationConfig } = useInstallationConfiguration();
const enableDedicatedOnboardingFlow = !!installationConfig?.isDedicatedInstallation;
const params = useQueryParams();

const { needsSetup } = useNeedsSetup();
Expand Down
14 changes: 10 additions & 4 deletions components/dashboard/src/login/SSOLoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { useOnBlurError } from "../hooks/use-onblur-error";
import { openOIDCStartWindow } from "../provider-utils";
import { useFeatureFlag } from "../data/featureflag-query";
import { useLocation } from "react-router";
import { useOnboardingState } from "../dedicated-setup/use-needs-setup";
import { getOrgSlugFromQuery } from "../data/organizations/orgs-query";

type Props = {
singleOrgMode?: boolean;
onSuccess: () => void;
};

Expand All @@ -27,11 +28,16 @@ function getOrgSlugFromPath(path: string) {
return pathSegments[2];
}

export const SSOLoginForm: FC<Props> = ({ singleOrgMode, onSuccess }) => {
export const SSOLoginForm: FC<Props> = ({ onSuccess }) => {
const location = useLocation();
const { data: onboardingState } = useOnboardingState();
const singleOrgMode = (onboardingState?.organizationCountTotal || 0) < 2;

const [orgSlug, setOrgSlug] = useState(
getOrgSlugFromPath(location.pathname) || window.localStorage.getItem("sso-org-slug") || "",
getOrgSlugFromPath(location.pathname) ||
window.localStorage.getItem("sso-org-slug") ||
getOrgSlugFromQuery(location.search) ||
"",
);
const [error, setError] = useState("");

Expand Down Expand Up @@ -78,7 +84,7 @@ export const SSOLoginForm: FC<Props> = ({ singleOrgMode, onSuccess }) => {
<div className="mt-10 space-y-2 w-56">
{!singleOrgMode && (
<TextInputField
label="Organization Slug"
label="Organization"
placeholder="my-organization"
value={orgSlug}
onChange={setOrgSlug}
Expand Down
11 changes: 7 additions & 4 deletions components/dashboard/src/menu/OrganizationSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { useCurrentUser } from "../user-context";
import { useCurrentOrg, useOrganizations } from "../data/organizations/orgs-query";
import { useLocation } from "react-router";
import { useOrgBillingMode } from "../data/billing-mode/org-billing-mode-query";
import { useFeatureFlag } from "../data/featureflag-query";
import { useIsOwner, useListOrganizationMembers, useHasRolePermission } from "../data/organizations/members-query";
import { isOrganizationOwned } from "@gitpod/public-api-common/lib/user-utils";
import { isAllowedToCreateOrganization } from "@gitpod/public-api-common/lib/user-utils";
import { OrganizationRole } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
import { useInstallationConfiguration } from "../data/installation/default-workspace-image-query";
import { useFeatureFlag } from "../data/featureflag-query";

export default function OrganizationSelector() {
const user = useCurrentUser();
Expand All @@ -25,10 +26,12 @@ export default function OrganizationSelector() {
const hasMemberPermission = useHasRolePermission(OrganizationRole.MEMBER);
const { data: billingMode } = useOrgBillingMode();
const getOrgURL = useGetOrgURL();
const isDedicated = useFeatureFlag("enableDedicatedOnboardingFlow");
const { data: installationConfig } = useInstallationConfiguration();
const isDedicated = !!installationConfig?.isDedicatedInstallation;
const isMultiOrgEnabled = useFeatureFlag("enable_multi_org");

// we should have an API to ask for permissions, until then we duplicate the logic here
const canCreateOrgs = user && !isOrganizationOwned(user) && !isDedicated;
const canCreateOrgs = user && isAllowedToCreateOrganization(user, isDedicated, isMultiOrgEnabled);

const userFullName = user?.name || "...";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {
GetInstallationWorkspaceDefaultImageResponse,
GetOnboardingStateRequest,
GetOnboardingStateResponse,
GetInstallationConfigurationRequest,
GetInstallationConfigurationResponse,
} from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { getGitpodService } from "./service";
Expand Down Expand Up @@ -130,4 +132,14 @@ export class JsonRpcInstallationClient implements PromiseClient<typeof Installat
onboardingState: converter.toOnboardingState(info),
});
}

async getInstallationConfiguration(
request: Partial<GetInstallationConfigurationRequest>,
_options?: CallOptions | undefined,
): Promise<GetInstallationConfigurationResponse> {
const config = await getGitpodService().server.getConfiguration();
return new GetInstallationConfigurationResponse({
configuration: converter.toInstallationConfiguration(config),
});
}
}
2 changes: 1 addition & 1 deletion components/gitpod-db/src/team-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const TeamDB = Symbol("TeamDB");
export interface TeamDB extends TransactionalDB<TeamDB> {
findTeams(
offset: number,
limit: number,
limit: number | undefined,
orderBy: keyof Team,
orderDir: "ASC" | "DESC",
searchTerm?: string,
Expand Down
8 changes: 6 additions & 2 deletions components/gitpod-db/src/typeorm/team-db-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class TeamDBImpl extends TransactionalDBImpl<TeamDB> implements TeamDB {

public async findTeams(
offset: number,
limit: number,
limit: number | undefined,
orderBy: keyof Team,
orderDir: "DESC" | "ASC",
searchTerm?: string,
Expand All @@ -70,7 +70,11 @@ export class TeamDBImpl extends TransactionalDBImpl<TeamDB> implements TeamDB {
searchTerm: `%${searchTerm}%`,
});
}
queryBuilder = queryBuilder.andWhere("markedDeleted = 0").skip(offset).take(limit).orderBy(orderBy, orderDir);
queryBuilder = queryBuilder.andWhere("markedDeleted = 0").skip(offset);
if (limit) {
queryBuilder = queryBuilder.take(limit);
}
queryBuilder = queryBuilder.orderBy(orderBy, orderDir);

const [rows, total] = await queryBuilder.getManyAndCount();
return { total, rows };
Expand Down
3 changes: 1 addition & 2 deletions components/gitpod-protocol/go/gitpod-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1878,8 +1878,7 @@ type VSCodeConfig struct {

// Configuration is the Configuration message type
type Configuration struct {
DaysBeforeGarbageCollection float64 `json:"daysBeforeGarbageCollection,omitempty"`
GarbageCollectionStartDate float64 `json:"garbageCollectionStartDate,omitempty"`
IsDedicatedInstallation bool `json:"isDedicatedInstallation,omitempty"`
}

// EnvVar is the EnvVar message type
Expand Down
5 changes: 2 additions & 3 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,11 +489,10 @@ export namespace GitpodServer {
* Whether this Gitpod instance is already configured with SSO.
*/
readonly isCompleted: boolean;

/**
* Whether this Gitpod instance has at least one org.
* Total number of organizations.
*/
readonly hasAnyOrg: boolean;
readonly organizationCountTotal: number;
}
}

Expand Down
4 changes: 1 addition & 3 deletions components/gitpod-protocol/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1456,9 +1456,7 @@ export namespace AuthProviderEntry {
}

export interface Configuration {
readonly daysBeforeGarbageCollection: number;
readonly garbageCollectionStartDate: number;
readonly isSingleOrgInstallation: boolean;
readonly isDedicatedInstallation: boolean;
}

export interface StripeConfig {
Expand Down
15 changes: 15 additions & 0 deletions components/public-api/gitpod/v1/installation.proto
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ service InstallationService {

// GetOnboardingState returns the onboarding state of the installation.
rpc GetOnboardingState(GetOnboardingStateRequest) returns (GetOnboardingStateResponse) {}

// GetInstallationConfiguration returns configuration of the installation.
rpc GetInstallationConfiguration(GetInstallationConfigurationRequest) returns (GetInstallationConfigurationResponse) {}
}

message GetOnboardingStateRequest {}
Expand All @@ -39,7 +42,11 @@ message GetOnboardingStateResponse {
}

message OnboardingState {
// Whether at least one organization has completed the onboarding
bool completed = 1;

// The total number of organizations
int32 organization_count_total = 2;
}

message GetInstallationWorkspaceDefaultImageRequest {}
Expand Down Expand Up @@ -144,3 +151,11 @@ message BlockedEmailDomain {

bool negative = 3;
}

message GetInstallationConfigurationRequest {}
message GetInstallationConfigurationResponse {
InstallationConfiguration configuration = 1;
}
message InstallationConfiguration {
bool is_dedicated_installation = 1;
}
Loading
Loading