Skip to content

Commit

Permalink
feat(ui-debug): protect expansion folder in user directory from deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
hdinia committed Dec 9, 2024
1 parent 13924e6 commit 4b7a4ec
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 61 deletions.
32 changes: 14 additions & 18 deletions webapp/src/components/App/Singlestudy/explore/Debug/Data/Folder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
type TreeFolder,
type DataCompProps,
isFolder,
canEditFile,
isProtected,
} from "../utils";
import { Fragment, useState } from "react";
import EmptyView from "../../../../../common/page/SimpleContent";
Expand All @@ -45,23 +45,19 @@ import useConfirm from "../../../../../../hooks/useConfirm";
import { deleteFile } from "../../../../../../services/api/studies/raw";
import useEnqueueErrorSnackbar from "../../../../../../hooks/useEnqueueErrorSnackbar";
import { toError } from "../../../../../../utils/fnUtils";
import { useOutletContext } from "react-router";
import type { StudyMetadata } from "../../../../../../common/types";
import { useSnackbar } from "notistack";

function Folder(props: DataCompProps) {
const {
filename,
filePath,
treeData,
canEdit,
setSelectedFile,
reloadTreeData,
studyId,
} = props;

const { t } = useTranslation();
const { study } = useOutletContext<{ study: StudyMetadata }>();
const replaceAction = useConfirm();
const deleteAction = useConfirm();
const { enqueueSnackbar } = useSnackbar();
Expand Down Expand Up @@ -126,14 +122,12 @@ function Folder(props: DataCompProps) {
<ListSubheader>
<Menubar>
<Filename>{filename}</Filename>
{canEdit && (
<UploadFileButton
studyId={studyId}
path={(file) => `${filePath}/${file.name}`}
onUploadSuccessful={reloadTreeData}
validate={handleValidateUpload}
/>
)}
<UploadFileButton
studyId={studyId}
path={(file) => `${filePath}/${file.name}`}
onUploadSuccessful={reloadTreeData}
validate={handleValidateUpload}
/>
</Menubar>
</ListSubheader>
}
Expand All @@ -159,7 +153,7 @@ function Folder(props: DataCompProps) {
<Fragment key={filename}>
<ListItem
secondaryAction={
canEditFile(study, path) && (
!isProtected(path) && (
<IconButton
edge="end"
size="small"
Expand Down Expand Up @@ -212,10 +206,12 @@ function Folder(props: DataCompProps) {
open={!!menuData}
onClose={handleMenuClose}
>
<MenuItem onClick={handleDeleteClick}>
<DeleteIcon sx={{ mr: 1 }} fontSize="small" />
{t("global.delete")}
</MenuItem>
{menuData && !isProtected(menuData.filePath) && (
<MenuItem onClick={handleDeleteClick}>
<DeleteIcon sx={{ mr: 1 }} fontSize="small" />
{t("global.delete")}
</MenuItem>
)}
</Menu>
{/* Confirm file replacement */}
<ConfirmationDialog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { useEffect, useState } from "react";
import { Filename, Flex, Menubar } from "./styles";
import UploadFileButton from "../../../../../common/buttons/UploadFileButton";

function Json({ filePath, filename, studyId, canEdit }: DataCompProps) {
function Json({ filePath, filename, studyId }: DataCompProps) {
const [t] = useTranslation();
const { enqueueSnackbar } = useSnackbar();
const [currentJson, setCurrentJson] = useState<JSONEditorProps["json"]>();
Expand Down Expand Up @@ -82,14 +82,12 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) {
<Flex>
<Menubar>
<Filename>{filename}</Filename>
{canEdit && (
<UploadFileButton
studyId={studyId}
path={filePath}
accept={{ "application/json": [".json"] }}
onUploadSuccessful={handleUploadSuccessful}
/>
)}
<UploadFileButton
studyId={studyId}
path={filePath}
accept={{ "application/json": [".json"] }}
onUploadSuccessful={handleUploadSuccessful}
/>
<DownloadButton onClick={handleDownload} />
</Menubar>
<JSONEditor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import Matrix from "../../../../../common/Matrix";
import type { DataCompProps } from "../utils";

function DebugMatrix({ filename, filePath, canEdit }: DataCompProps) {
return <Matrix title={filename} url={filePath} canImport={!canEdit} />;
function DebugMatrix({ filename, filePath }: DataCompProps) {
return <Matrix title={filename} url={filePath} />;
}

export default DebugMatrix;
18 changes: 8 additions & 10 deletions webapp/src/components/App/Singlestudy/explore/Debug/Data/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function getSyntaxProps(data: string | string[]): SyntaxHighlighterProps {
};
}

function Text({ studyId, filePath, filename, canEdit }: DataCompProps) {
function Text({ studyId, filePath, filename }: DataCompProps) {
const { t } = useTranslation();
const theme = useTheme();

Expand Down Expand Up @@ -115,20 +115,18 @@ function Text({ studyId, filePath, filename, canEdit }: DataCompProps) {
<Flex>
<Menubar>
<Filename>{filename}</Filename>
{canEdit && (
<UploadFileButton
studyId={studyId}
path={filePath}
accept={{ "text/plain": [".txt"] }}
onUploadSuccessful={handleUploadSuccessful}
/>
)}
<UploadFileButton
studyId={studyId}
path={filePath}
accept={{ "text/plain": [".txt"] }}
onUploadSuccessful={handleUploadSuccessful}
/>
<DownloadButton
onClick={handleDownload}
disabled={isEmptyContent(text)}
/>
</Menubar>
{isEmptyContent(text) ? (
{isEmptyContent(text) ? ( // TODO: remove this when the files become editable
<EmptyView icon={GridOffIcon} title={t("study.results.noData")} />
) : (
<Box sx={{ overflow: "auto" }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import Text from "./Text";
import Unsupported from "./Unsupported";
import Matrix from "./Matrix";
import Folder from "./Folder";
import { canEditFile, type FileInfo, type FileType } from "../utils";
import { type FileInfo, type FileType } from "../utils";
import type { DataCompProps } from "../utils";
import ViewWrapper from "../../../../../common/page/ViewWrapper";
import type { StudyMetadata } from "../../../../../../common/types";
Expand Down Expand Up @@ -45,7 +45,6 @@ function Data({ study, setSelectedFile, reloadTreeData, ...fileInfo }: Props) {
<DataViewer
{...fileInfo}
studyId={study.id}
canEdit={canEditFile(study, fileInfo.filePath)}
setSelectedFile={setSelectedFile}
reloadTreeData={reloadTreeData}
/>
Expand Down
38 changes: 18 additions & 20 deletions webapp/src/components/App/Singlestudy/explore/Debug/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import FolderIcon from "@mui/icons-material/Folder";
import DatasetIcon from "@mui/icons-material/Dataset";
import { SvgIconComponent } from "@mui/icons-material";
import * as RA from "ramda-adjunct";
import type { StudyMetadata } from "../../../../../common/types";

////////////////////////////////////////////////////////////////
// Types
Expand All @@ -44,7 +43,6 @@ export interface FileInfo {

export interface DataCompProps extends FileInfo {
studyId: string;
canEdit: boolean;
setSelectedFile: (file: FileInfo) => void;
reloadTreeData: () => void;
}
Expand All @@ -59,7 +57,14 @@ const URL_SCHEMES = {
FILE: "file://",
} as const;

const SUPPORTED_EXTENSIONS = [".txt", ".log", ".csv", ".tsv", ".ini"] as const;
const SUPPORTED_EXTENSIONS = [
".txt",
".log",
".csv",
".tsv",
".ini",
".yml",
] as const;

// Maps file types to their corresponding icon components.
const iconByFileType: Record<FileType, SvgIconComponent> = {
Expand Down Expand Up @@ -108,29 +113,22 @@ export function getFileType(treeData: TreeData): FileType {
// All files except matrices and json-formatted content use this prefix
// We filter to only allow extensions that can be properly displayed (.txt, .log, .csv, .tsv, .ini)
// Other extensions (like .RDS or .xlsx) are marked as unsupported since they can't be shown in the UI
if (
treeData.startsWith(URL_SCHEMES.FILE) &&
return treeData.startsWith(URL_SCHEMES.FILE) &&
SUPPORTED_EXTENSIONS.some((ext) => treeData.endsWith(ext))
) {
return "text";
}
? "text"
: "unsupported";
}

return "unsupported";
return "text";
}

/**
* Checks if a study's file can be edited.
* Checks if a path is protected from deletion
*
* @param study - The study where the file is located.
* @param filePath - The path of the file.
* @returns True if the file can be edited, false otherwise.
* @param path - Path to check
* @returns Whether the path is protected
*/
export function canEditFile(study: StudyMetadata, filePath: string): boolean {
return (
!study.archived &&
(filePath === "user" || filePath.startsWith("user/")) &&
// To remove when Xpansion tool configuration will be moved to "input/expansion" directory
!(filePath === "user/expansion" || filePath.startsWith("user/expansion/"))
);
export function isProtected(path: string): boolean {
// user/expansion folder and its contents must not be deleted
return path === "user/expansion" || path.startsWith("user/expansion/");
}

0 comments on commit 4b7a4ec

Please sign in to comment.