Skip to content
Open
1 change: 1 addition & 0 deletions client/src/components/entities/entities.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type EntityTypes =
| "code-repository"
| "data-connector"
| "job-launcher"
| "session-launcher";
15 changes: 10 additions & 5 deletions client/src/components/offcanvas/OffcanvasHeaderWithType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cx from "classnames";
import {
Database,
FileCode,
Gear,
PlayCircle,
QuestionCircle,
} from "react-bootstrap-icons";
Expand Down Expand Up @@ -29,6 +30,8 @@ export default function OffcanvasHeaderWithType({
<Database className="me-1" />
) : entityType === "session-launcher" ? (
<PlayCircle className="me-1" />
) : entityType === "job-launcher" ? (
<Gear className="me-1" />
) : entityType === "code-repository" ? (
<FileCode className="me-1" />
) : (
Expand All @@ -39,11 +42,13 @@ export default function OffcanvasHeaderWithType({
? _entityName
: entityType === "data-connector"
? "Data connector"
: entityType === "session-launcher"
? "Session launcher"
: entityType === "code-repository"
? "Code repository"
: "Unknown";
: entityType === "job-launcher"
? "Job launcher"
: entityType === "session-launcher"
? "Session launcher"
: entityType === "code-repository"
? "Code repository"
: "Unknown";

return (
<div>
Expand Down
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 { ProjectPermissions } 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): ProjectPermissions {
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 || isUninitialized;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic error:

Suggested change
const isLoadingPermissions = isLoading || isUninitialized;
const isLoadingPermissions = isLoading || (projectId && isUninitialized);

You can see above that we trigger fetching data when projectId && isUninitialized => this is when the projectId is set and the API fetch has not been triggered yet.

If you do not check for projectId (not null, not empty), then isUninitialized will be true forever.


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

const permissions: Permissions = {
return {
...DEFAULT_PERMISSIONS,
...currentData,
isLoadingPermissions: false,
};
return permissions;
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default function SessionEnvironmentAdvancedFields({
<AdvancedSettingsFields<SessionEnvironmentForm>
control={control}
errors={errors}
launcherCategory="session"
/>
</CollapseBody>
</Collapse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,8 @@ function UpdateSessionEnvironmentModal({
uid: data.uid ?? undefined,
working_directory: data.working_directory?.trim() || undefined,
strip_path_prefix: data.strip_path_prefix,
...(commandParsed.data
? { command: commandParsed.data }
: { command: null }),
...(argsParsed.data ? { args: argsParsed.data } : { args: null }),
command: commandParsed.data ?? undefined,
args: argsParsed.data ?? undefined,
},
});
},
Expand Down
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 ProjectPermissions = Permissions & {
isLoadingPermissions: boolean;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird. Why is it called ProjectPermissions when it is generic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type is really Permissions plus a loading flag from the hook, not the API's ProjectPermissions. I'll rename it to something like PermissionsWithLoadingState and keep it close to the hook so the generic permissions.types.ts stays API-aligned.

8 changes: 4 additions & 4 deletions client/src/features/sessionsV2/AddSessionLauncherButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { useCallback, useState } from "react";
import { PlusLg } from "react-bootstrap-icons";
import { Button } from "reactstrap";

import NewSessionLauncherModal from "./components/SessionModals/NewSessionLauncherModal";
import NewLauncherModal from "./components/SessionModals/NewLauncherModal";

export default function AddSessionLauncherButton({
"data-cy": dataCy,
Expand All @@ -38,9 +38,9 @@ export default function AddSessionLauncherButton({
return (
<>
{styleBtn === "iconTextBtn" ? (
<Button data-cy={dataCy} onClick={() => toggle()}>
<Button data-cy={dataCy} onClick={toggle}>
<PlusLg className={cx("me-2", "bi")} />
Add session
Add launcher
</Button>
) : (
<Button
Expand All @@ -52,7 +52,7 @@ export default function AddSessionLauncherButton({
<PlusLg className="bi" />
</Button>
)}
<NewSessionLauncherModal isOpen={isOpen} toggle={toggle} />
<NewLauncherModal isOpen={isOpen} toggle={toggle} />
</>
);
}
37 changes: 24 additions & 13 deletions client/src/features/sessionsV2/DeleteSessionLauncherModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import { WarnAlert } from "../../components/Alert";
import RtkOrDataServicesError from "../../components/errors/RtkOrDataServicesError";
import type { SessionLauncher } from "./api/sessionLaunchersV2.api";
import { useDeleteSessionLaunchersByLauncherIdMutation as useDeleteSessionLauncherMutation } from "./api/sessionLaunchersV2.api";
import {
getLauncherCategory,
getLauncherCategoryDefinition,
} from "./session.utils";

interface DeleteSessionLauncherModalProps {
isOpen: boolean;
Expand All @@ -40,6 +44,8 @@ export default function DeleteSessionLauncherModal({
sessionsLength,
}: DeleteSessionLauncherModalProps) {
const [deleteSessionLauncher, result] = useDeleteSessionLauncherMutation();
const launcherCategory = getLauncherCategory(launcher);
const launcherDefinition = getLauncherCategoryDefinition(launcherCategory);

const onDelete = useCallback(() => {
deleteSessionLauncher({
Expand Down Expand Up @@ -75,32 +81,37 @@ export default function DeleteSessionLauncherModal({
tag="h2"
toggle={toggle}
>
Delete session launcher
Delete {launcherDefinition.text.inline} launcher
</ModalHeader>
<ModalBody>
{result.error && <RtkOrDataServicesError error={result.error} />}
<p className="mb-3">
Are you sure you want to delete the <b>{launcher.name}</b> session
launcher?
Are you sure you want to delete the <b>{launcher.name}</b>{" "}
{launcherDefinition.text.inline} launcher?
</p>
{sessionsLength > 0 && (
<WarnAlert dismissible={false}>
<p>
You have a session running from this launcher. If you delete this
session launcher, that session will continue running, but it
become an orphan session and will not be able to be launched again
once stopped. If other RenkuLab users are running sessions from
this launcher, their sessions will become orphan sessions as well.
You have a {launcherDefinition.text.inline} running from this
launcher. If you delete this launcher, that{" "}
{launcherDefinition.text.inline} will continue running, but it
become an orphan {launcherDefinition.text.inline} and will not be
able to be launched again once stopped. If other RenkuLab users
are running {launcherDefinition.text.inline}s from this launcher,
their {launcherDefinition.text.inline}s will become orphan{" "}
{launcherDefinition.text.inline}s as well.
</p>
</WarnAlert>
)}
{sessionsLength === 0 && (
<WarnAlert dismissible={false}>
<p>
If other RenkuLab users are running sessions from this launcher,
their sessions will become orphan sessions. This means that their
sessions will continue running, but will not be able to be
launched again once stopped.
If other RenkuLab users are running{" "}
{launcherDefinition.text.inline}s from this launcher, their{" "}
{launcherDefinition.text.inline}s will become orphan{" "}
{launcherDefinition.text.inline}s. This means that their{" "}
{launcherDefinition.text.inline}s will continue running, but will
not be able to be launched again once stopped.
</p>
</WarnAlert>
)}
Expand All @@ -119,7 +130,7 @@ export default function DeleteSessionLauncherModal({
role="button"
>
<Trash className={cx("bi", "me-1")} />
Delete Session launcher
Delete {launcherDefinition.text.inline} launcher
</Button>
</ModalFooter>
</Modal>
Expand Down
Loading
Loading