Merge branch '21225-project-panel-tabs' into main. Closes #21225
[arvados.git] / services / workbench2 / src / views-components / data-explorer / renderers.tsx
index 0ebe96ef3acb56f0ea29b3fdbdcdf3d40276b738..e337423802e178887dfc614b6fb028fa90006a4f 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from "react";
-import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from "@material-ui/core";
+import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip, withTheme } from "@material-ui/core";
 import { FavoriteStar, PublicFavoriteStar } from "../favorite-star/favorite-star";
 import { Resource, ResourceKind, TrashableResource } from "models/resource";
 import {
@@ -21,6 +21,7 @@ import {
     ActiveIcon,
     SetupIcon,
     InactiveIcon,
+    ErrorIcon,
 } from "components/icon/icon";
 import { formatDate, formatFileSize, formatTime } from "common/formatters";
 import { resourceLabel } from "common/labels";
@@ -36,7 +37,6 @@ import { ResourceStatus as WorkflowStatus } from "views/workflow-panel/workflow-
 import { getUuidPrefix, openRunProcess } from "store/workflow-panel/workflow-panel-actions";
 import { openSharingDialog } from "store/sharing-dialog/sharing-dialog-actions";
 import { getUserFullname, getUserDisplayName, User, UserResource } from "models/user";
-import { toggleIsAdmin } from "store/users/users-actions";
 import { LinkClass, LinkResource } from "models/link";
 import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from "store/navigation/navigation-action";
 import { withResourceData } from "views-components/data-explorer/with-resources";
@@ -53,6 +53,20 @@ import { VirtualMachinesResource } from "models/virtual-machines";
 import { CopyToClipboardSnackbar } from "components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar";
 import { ProjectResource } from "models/project";
 import { ProcessResource } from "models/process";
+import { ServiceRepository } from "services/services";
+import { loadUsersPanel } from "store/users/users-actions";
+import { InlinePulser } from "components/loading/inline-pulser";
+import { ProcessTypeFilter } from "store/resource-type-filters/resource-type-filters";
+
+export const toggleIsAdmin = (uuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const { resources } = getState();
+        const data = getResource<UserResource>(uuid)(resources);
+        const isAdmin = data!.isAdmin;
+        const newActivity = await services.userService.update(uuid, { isAdmin: !isAdmin });
+        dispatch<any>(loadUsersPanel());
+        return newActivity;
+    };
 
 const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
     const navFunc = "groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
@@ -68,7 +82,10 @@ const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
                 <Typography
                     color="primary"
                     style={{ width: "auto", cursor: "pointer" }}
-                    onClick={() => dispatch<any>(navFunc(item.uuid))}
+                    onClick={(ev) => {
+                        ev.stopPropagation()
+                        dispatch<any>(navFunc(item.uuid))
+                    }}
                 >
                     {item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION ? <IllegalNamingWarning name={item.name} /> : null}
                     {item.name}
@@ -85,7 +102,7 @@ const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
     );
 };
 
-const FrozenProject = (props: { item: ProjectResource }) => {
+export const FrozenProject = (props: { item: ProjectResource }) => {
     const [fullUsername, setFullusername] = React.useState<any>(null);
     const getFullName = React.useCallback(() => {
         if (props.item.frozenByUuid) {
@@ -444,8 +461,6 @@ export const TokenApiToken = withResourceData("apiToken", renderCommonData);
 
 export const TokenCreatedByIpAddress = withResourceData("createdByIpAddress", renderCommonDate);
 
-export const TokenDefaultOwnerUuid = withResourceData("defaultOwnerUuid", renderCommonData);
-
 export const TokenExpiresAt = withResourceData("expiresAt", renderCommonDate);
 
 export const TokenLastUsedAt = withResourceData("lastUsedAt", renderCommonDate);
@@ -523,9 +538,9 @@ const renderResourceLink = (dispatch: Dispatch, item: Resource ) => {
             onClick={() => {
                 item.kind === ResourceKind.GROUP && (item as GroupResource).groupClass === "role"
                     ? dispatch<any>(navigateToGroupDetails(item.uuid))
-                    : item.kind === ResourceKind.USER 
+                    : item.kind === ResourceKind.USER
                     ? dispatch<any>(navigateToUserProfile(item.uuid))
-                    : dispatch<any>(navigateTo(item.uuid)); 
+                    : dispatch<any>(navigateTo(item.uuid));
             }}
         >
             {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || "" : "")}:{" "}
@@ -898,7 +913,6 @@ const _resourceWithName = withStyles(
             <Typography
                 style={{ color: theme.palette.primary.main }}
                 inline
-                noWrap
             >
                 {uuid}
             </Typography>
@@ -909,7 +923,6 @@ const _resourceWithName = withStyles(
         <Typography
             style={{ color: theme.palette.primary.main }}
             inline
-            noWrap
         >
             {userFullname} ({uuid})
         </Typography>
@@ -1023,7 +1036,18 @@ const renderType = (type: string, subtype: string) => <Typography noWrap>{resour
 
 export const ResourceType = connect((state: RootState, props: { uuid: string }) => {
     const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
-    return { type: resource ? resource.kind : "", subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : "" };
+    return {
+        type: resource ? resource.kind : "",
+        subtype: resource
+            ? resource.kind === ResourceKind.GROUP
+                ? resource.groupClass
+                : resource.kind === ResourceKind.PROCESS
+                    ? resource.requestingContainerUuid
+                        ? ProcessTypeFilter.CHILD_PROCESS
+                        : ProcessTypeFilter.MAIN_PROCESS
+                    : ""
+            : ""
+    };
 })((props: { type: string; subtype: string }) => renderType(props.type, props.subtype));
 
 export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => {
@@ -1132,3 +1156,30 @@ export const ContainerRunTime = connect((state: RootState, props: { uuid: string
         }
     }
 );
+
+export const GroupMembersCount = connect(
+    (state: RootState, props: { uuid: string }) => {
+        const group = getResource<GroupResource>(props.uuid)(state.resources);
+
+        return {
+            value: group?.memberCount,
+        };
+
+    }
+)(withTheme()((props: {value: number | null | undefined, theme:ArvadosTheme}) => {
+    if (props.value === undefined) {
+        // Loading
+        return <Typography component={"div"}>
+            <InlinePulser />
+        </Typography>;
+    } else if (props.value === null) {
+        // Error
+        return <Typography>
+            <Tooltip title="Failed to load member count">
+                <ErrorIcon style={{color: props.theme.customs.colors.greyL}}/>
+            </Tooltip>
+        </Typography>;
+    } else {
+        return <Typography children={props.value} />;
+    }
+}));