Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { skipToken } from "@reduxjs/toolkit/query";
import { useEffect } from "react";

import { DEFAULT_PERMISSIONS } from "../../permissionsV2/permissions.constants";
import type { Permissions } from "../../permissionsV2/permissions.types";
import type { PermissionsWithLoadingState } from "../../permissionsV2/permissions.types";
import { projectV2Api } from "../../projectsV2/api/projectV2.enhanced-api";

interface UseProjectPermissionsArgs {
Expand All @@ -29,7 +29,7 @@ interface UseProjectPermissionsArgs {

export default function useProjectPermissions({
projectId,
}: UseProjectPermissionsArgs): Permissions {
}: UseProjectPermissionsArgs): PermissionsWithLoadingState {
const { currentData, isLoading, isError, isUninitialized } =
projectV2Api.endpoints.getProjectsByProjectIdPermissions.useQueryState(
projectId ? { projectId } : skipToken,
Expand All @@ -43,13 +43,18 @@ export default function useProjectPermissions({
}
}, [fetchPermissions, isUninitialized, projectId]);

const isLoadingPermissions = isLoading || !!(projectId && isUninitialized);

if (isLoading || isError || !currentData) {
return DEFAULT_PERMISSIONS;
return {
...DEFAULT_PERMISSIONS,
isLoadingPermissions,
};
}

const permissions: Permissions = {
return {
...DEFAULT_PERMISSIONS,
...currentData,
isLoadingPermissions: false,
};
return permissions;
}
4 changes: 4 additions & 0 deletions client/src/features/permissionsV2/permissions.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ export type RequestedPermission = "write" | "delete" | "change_membership";
export type Permissions = {
[key in RequestedPermission]: boolean;
};

export type PermissionsWithLoadingState = Permissions & {
isLoadingPermissions: boolean;
};
105 changes: 39 additions & 66 deletions client/src/features/sessionsV2/SessionList/SessionLauncherCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,36 @@

import { skipToken } from "@reduxjs/toolkit/query";
import cx from "classnames";
import { useContext, useMemo } from "react";
import {
CircleFill,
Link45deg,
Pencil,
PlayCircle,
Trash,
} from "react-bootstrap-icons";
import { useMemo } from "react";
import { Link45deg, Pencil, PlayCircle, Trash } from "react-bootstrap-icons";
import { Card, CardBody, Col, DropdownItem, Row } from "reactstrap";

import SessionEnvironmentGitLabWarningBadge from "~/features/legacy/SessionEnvironmentGitLabWarnBadge";
import { useGetRepositoryQuery } from "~/features/repositories/api/repositories.api";
import { Loader } from "../../../components/Loader";
import AppContext from "../../../utils/context/appContext";
import { DEFAULT_APP_PARAMS } from "../../../utils/context/appParams.constants";
import PermissionsGuard from "../../permissionsV2/PermissionsGuard";
import useProjectPermissions from "../../ProjectPageV2/utils/useProjectPermissions.hook";
import { Project } from "../../projectsV2/api/projectV2.api";
import { computeResourcesApi } from "../api/computeResources.api";
import type { SessionLauncher } from "../api/sessionLaunchersV2.api";
import {
sessionLaunchersV2Api,
useGetEnvironmentsByEnvironmentIdBuildsQuery as useGetBuildsQuery,
} from "../api/sessionLaunchersV2.api";
import { useGetSessionsImagesQuery } from "../api/sessionsV2.api";
import { sessionLaunchersV2Api } from "../api/sessionLaunchersV2.api";
import {
BuildStatusBadge,
BuildStatusDescription,
} from "../components/BuildStatusComponents";
import { LauncherActions } from "../components/launcherActions/LauncherActions";
import {
EnvironmentIcon,
LauncherEnvironmentIcon,
} from "../components/SessionForm/LauncherEnvironmentIcon";
import { SessionLauncherButtons } from "../components/SessionLauncherButtons";
import SessionImageBadge from "../components/SessionStatus/SessionImageBadge";
import { SessionBadge } from "../components/SessionStatus/SessionStatus";
import {
getLauncherCategory,
getLauncherCategoryDefinitionByLauncher,
} from "../session.utils";
import { SessionV2 } from "../sessionsV2.types";
import useLauncherEnvironmentReadiness from "../useLauncherEnvironmentReadiness.hook";
import SessionCard from "./SessionCard";

import styles from "./Session.module.scss";
Expand All @@ -85,34 +74,30 @@ export default function SessionLauncherCard({
toggleSessionView,
toggleShareLink,
}: SessionLauncherCardProps) {
const { params } = useContext(AppContext);
const environment = launcher?.environment;
const imageBuildersEnabled =
params?.IMAGE_BUILDERS_ENABLED ?? DEFAULT_APP_PARAMS.IMAGE_BUILDERS_ENABLED;
const isCodeEnvironment = environment?.environment_image_source === "build";
const isExternalImageEnvironment =
environment?.environment_kind === "CUSTOM" &&
environment?.environment_image_source === "image";

const { data: builds, isLoading } = useGetBuildsQuery(
imageBuildersEnabled && isCodeEnvironment
? { environmentId: environment.id }
: skipToken,
);
const {
builds,
isBuildInProgress,
isCodeEnvironment,
isCustomImageEnvironment,
isGlobalEnvironment,
isLoadingBuilds,
isLoadingContainerImage,
lastBuild,
lastSuccessfulBuild,
useOldImage,
containerImage,
} = useLauncherEnvironmentReadiness({ launcher });

const lastBuild = builds?.at(0);
const lastSuccessfulBuild = builds?.find(
(build) => build.status === "succeeded" && build.id !== lastBuild?.id,
);
const environment = launcher?.environment;
const hasSession = !!sessions?.length;
const launcherDefinition =
launcher && getLauncherCategoryDefinitionByLauncher(launcher);
const LauncherTypeIcon = launcherDefinition?.icon || PlayCircle;
const launcherTypeLabel = launcherDefinition?.text.display || null;

sessionLaunchersV2Api.endpoints.getEnvironmentsByEnvironmentIdBuilds.useQuerySubscription(
isCodeEnvironment && lastBuild?.status === "in_progress"
? { environmentId: environment.id }
launcher && isBuildInProgress
? { environmentId: launcher.environment.id }
: skipToken,
{
pollingInterval: 1_000,
Expand Down Expand Up @@ -143,13 +128,6 @@ export default function SessionLauncherCard({
"text-muted",
];

const { data: containerImage, isLoading: isLoadingContainerImage } =
useGetSessionsImagesQuery(
environment?.container_image != null
? { imageUrl: environment.container_image }
: skipToken,
);

const {
data: imageRepositorySource,
isLoading: isLoadingImageRepositorySource,
Expand Down Expand Up @@ -215,7 +193,7 @@ export default function SessionLauncherCard({
</span>
</Col>
<Col xs={12} xl="auto">
{environment?.environment_kind === "GLOBAL" ? (
{isGlobalEnvironment ? (
<span className={cx(ENVIRONMENT_KIND_CLASSES)}>
<EnvironmentIcon type="global" />
Global environment
Expand All @@ -225,7 +203,7 @@ export default function SessionLauncherCard({
<EnvironmentIcon type="codeBased" size={16} />
Code based environment
</span>
) : isExternalImageEnvironment ? (
) : isCustomImageEnvironment ? (
<span className={cx(ENVIRONMENT_KIND_CLASSES)}>
<EnvironmentIcon type="custom" size={16} />
External image environment
Expand Down Expand Up @@ -260,7 +238,7 @@ export default function SessionLauncherCard({
<Row className="g-2">
<Col xs={12} xl={4}>
{isCodeEnvironment &&
(isLoading ||
(isLoadingBuilds ||
isLoadingContainerImage ||
isLoadingImageRepositorySource ||
isLoadingResourcePools) ? (
Expand Down Expand Up @@ -331,33 +309,28 @@ export default function SessionLauncherCard({
"gap-2",
)}
>
<SessionLauncherButtons
<LauncherActions
placement="launcher-card"
builds={builds}
hasSession={hasSession}
lastBuild={lastBuild}
launcher={launcher}
namespace={project.namespace}
otherActions={otherLauncherActions}
slug={project.slug}
useOldImage={
isCodeEnvironment &&
lastBuild?.status !== "succeeded" &&
!!lastSuccessfulBuild
}
/>
{isCodeEnvironment &&
lastBuild?.status !== "succeeded" &&
lastSuccessfulBuild && (
<BuildStatusDescription
isOldImage={true}
status={lastSuccessfulBuild?.status}
createdAt={lastSuccessfulBuild?.created_at}
completedAt={
lastSuccessfulBuild?.status === "succeeded"
? lastSuccessfulBuild?.result?.completed_at
: undefined
}
/>
)}
{useOldImage && lastSuccessfulBuild && (
<BuildStatusDescription
isOldImage={true}
status={lastSuccessfulBuild?.status}
createdAt={lastSuccessfulBuild?.created_at}
completedAt={
lastSuccessfulBuild?.status === "succeeded"
? lastSuccessfulBuild?.result?.completed_at
: undefined
}
/>
)}
</div>
)}
</Col>
Expand Down
12 changes: 7 additions & 5 deletions client/src/features/sessionsV2/SessionView/SessionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
useGetResourcePoolsQuery,
} from "../api/computeResources.api";
import type { SessionLauncher } from "../api/sessionLaunchersV2.api";
import { LauncherActions } from "../components/launcherActions/LauncherActions";
import ActiveSessionButton from "../components/SessionButton/ActiveSessionButton";
import { ModifyResourcesLauncherModal } from "../components/SessionModals/ModifyResourcesLauncher";
import UpdateSessionLauncherEnvironmentModal from "../components/SessionModals/UpdateSessionLauncherModal";
Expand All @@ -80,7 +81,6 @@ import {
} from "../session.utils";
import { getShowSessionUrlByProject, SessionV2Actions } from "../SessionsV2";
import { LauncherCategory, SessionV2 } from "../sessionsV2.types";
import StartSessionButton from "../StartSessionButton";
import EnvironmentItem from "./EnvironmentItem";
import EnvVariablesCard from "./EnvVariablesCard";
import EnvVariablesModal from "./EnvVariablesModal";
Expand Down Expand Up @@ -151,13 +151,14 @@ function SessionCard({
}

function SessionCardNotRunning({
hasSession,
launcher,
project,
}: {
hasSession?: boolean;
launcher: SessionLauncher;
project: Project;
}) {
const launcherCategory = getLauncherCategory(launcher);
return (
<SessionCardContent
color="dark"
Expand All @@ -183,12 +184,12 @@ function SessionCardNotRunning({
}
contentSession={
<div className="my-auto">
<StartSessionButton
<LauncherActions
placement="launcher-side-panel"
hasSession={hasSession}
launcher={launcher}
namespace={project.namespace}
slug={project.slug}
launcherCategory={launcherCategory}
isDisabledDropdownToggle={true}
/>
</div>
}
Expand Down Expand Up @@ -397,6 +398,7 @@ export function SessionView({
</p>
{launcher && (
<SessionCardNotRunning
hasSession={totalSession > 0}
project={project}
launcher={launcher}
/>
Expand Down
Loading
Loading