Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refact: move drop-box-related code into drop box module #425

Merged
merged 3 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 10 additions & 4 deletions src/components/manager/DropBoxTreeSelect.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useMemo, useEffect } from "react";
import React, { useMemo, useEffect, useState } from "react";
import PropTypes from "prop-types";
import { TreeSelect, Form } from "antd";

import { BENTO_DROP_BOX_FS_BASE_PATH } from "@/config";
import { useDropBoxFileContent } from "@/hooks";
import { useDropBox } from "@/modules/manager/hooks";
import { useDropBox, useDropBoxFileContent } from "@/modules/dropBox/hooks";
import { dropBoxTreeNodeEnabledJson } from "@/utils/files";
import { getTrue } from "@/utils/misc";

Expand Down Expand Up @@ -80,7 +79,14 @@ export const DropBoxJsonSelect = ({ form, name, labels, initialValue, rules }) =
const pathName = name + "Path";
const filePath = Form.useWatch(pathName, form);
const fileContent = useDropBoxFileContent(filePath);
const currentFieldData = fileContent || initialValue;
const [currentFieldData, setCurrentFieldData] = useState(initialValue); // string | undefined

useEffect(() => {
if (fileContent !== null) {
fileContent.text().then((t) => setCurrentFieldData(t));
}
setCurrentFieldData(initialValue);
}, [fileContent, initialValue]);

const contentLabel = filePath && labels?.updatedContent ? labels.updatedContent : labels.defaultContent;

Expand Down
14 changes: 7 additions & 7 deletions src/components/manager/ManagerDropBoxContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,21 @@ import FileModal from "../display/FileModal";
import ForbiddenContent from "../ForbiddenContent";

import { BENTO_DROP_BOX_FS_BASE_PATH } from "@/config";
import { useStartIngestionFlow } from "./workflowCommon";
import { testFileAgainstPattern } from "@/utils/files";
import { getFalse } from "@/utils/misc";
import { useResourcePermissionsWrapper } from "@/hooks";
import {
beginDropBoxPuttingObjects,
endDropBoxPuttingObjects,
putDropBoxObject,
deleteDropBoxObject,
invalidateDropBoxTree,
} from "@/modules/manager/actions";
import { useDropBox } from "@/modules/manager/hooks";
} from "@/modules/dropBox/actions";
import { useDropBox } from "@/modules/dropBox/hooks";
import { useService, useWorkflows } from "@/modules/services/hooks";
import { testFileAgainstPattern } from "@/utils/files";
import { getFalse } from "@/utils/misc";

import { VIEWABLE_FILE_EXTENSIONS } from "../display/FileDisplay";
import { useResourcePermissionsWrapper } from "@/hooks";
import { useService, useWorkflows } from "@/modules/services/hooks";
import { useStartIngestionFlow } from "./workflowCommon";

const DROP_BOX_CONTENT_CONTAINER_STYLE = { display: "flex", flexDirection: "column", gap: 8 };
const DROP_BOX_INFO_CONTAINER_STYLE = { display: "flex", gap: "2em", paddingTop: 8 };
Expand Down
24 changes: 17 additions & 7 deletions src/components/manager/projects/Project.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import PropTypes from "prop-types";

import { Button, Col, Empty, Row, Space, Tabs, Typography, Form } from "antd";
import { Button, Col, Empty, Form, Row, Space, Tabs, Typography } from "antd";
import { CheckOutlined, CloseOutlined, DeleteOutlined, EditOutlined, PlusOutlined } from "@ant-design/icons";

import { createDataset, deleteProject, editProject, makeProjectResource, RESOURCE_EVERYTHING } from "bento-auth-js";

import { INITIAL_DATA_USE_VALUE } from "@/duo";
import { useHasResourcePermissionWrapper, useResourcePermissionsWrapper } from "@/hooks";
import { useDropBoxFileContent } from "@/modules/dropBox/hooks";
import { projectPropTypesShape } from "@/propTypes";
import { nop, simpleDeepCopy } from "@/utils/misc";

import Dataset from "../../datasets/Dataset";
import ProjectForm from "./ProjectForm";
import { nop, simpleDeepCopy } from "@/utils/misc";
import { projectPropTypesShape } from "@/propTypes";
import ProjectJsonSchema from "./ProjectJsonSchema";
import { useDropBoxFileContent, useHasResourcePermissionWrapper, useResourcePermissionsWrapper } from "@/hooks";
import { createDataset, deleteProject, editProject, makeProjectResource, RESOURCE_EVERYTHING } from "bento-auth-js";
import { INITIAL_DATA_USE_VALUE } from "@/duo";

const SUB_TAB_KEYS = {
DATASETS: "project-datasets",
Expand Down Expand Up @@ -58,7 +61,13 @@ const Project = ({

const [editingForm] = Form.useForm();
const newDiscoveryFile = Form.useWatch("discoveryPath", editingForm);
const newDiscoveryContent = useDropBoxFileContent(newDiscoveryFile);

const newDiscoveryBlob = useDropBoxFileContent(newDiscoveryFile);
const [newDiscoveryContent, setNewDiscoveryContent] = useState(null);

useEffect(() => {
if (newDiscoveryBlob) newDiscoveryBlob.text().then((t) => setNewDiscoveryContent(t));
}, [newDiscoveryBlob]);

const [selectedKey, setSelectedKey] = useState(SUB_TAB_KEYS.DATASETS);

Expand Down Expand Up @@ -160,6 +169,7 @@ const Project = ({
))}
</>
)}

<Tabs onChange={(tab) => setSelectedKey(tab)} activeKey={selectedKey} items={SUB_TAB_ITEMS} size="large" />

{selectedKey === SUB_TAB_KEYS.DATASETS ? (
Expand Down
69 changes: 7 additions & 62 deletions src/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { useSelector } from "react-redux";
import { useCallback, useEffect, useState } from "react";
import Ajv, { SchemaObject } from "ajv";

import {
RESOURCE_EVERYTHING,
useAuthorizationHeader,
useHasResourcePermission,
useResourcePermissions,
type Resource,
} from "bento-auth-js";

import { type RootState, useAppSelector } from "@/store";
import { useCallback } from "react";
import type { SchemaObject } from "ajv";
import Ajv from "ajv";

import { RESOURCE_EVERYTHING, useHasResourcePermission, useResourcePermissions, type Resource } from "bento-auth-js";

import { type RootState } from "@/store";
import { useService } from "@/modules/services/hooks";
import { ARRAY_BUFFER_FILE_EXTENSIONS, BLOB_FILE_EXTENSIONS } from "@/components/display/FileDisplay";

// AUTHORIZATION:
// Wrapper hooks for bento-auth-js permissions hooks, which expect a 'authzUrl' argument.
Expand Down Expand Up @@ -81,55 +75,6 @@ export const useOpenIDConfigNotLoaded = (): boolean => {
return openIdConfigHasAttempted === false || openIdConfigFetching;
};

export const useDropBoxFileContent = (filePath?: string) => {
const file = useSelector((state: RootState) =>
state.dropBox.tree.find((f: { filePath: string | undefined }) => f?.filePath === filePath),
);
const authHeader = useAuthorizationHeader();

const [fileContents, setFileContents] = useState(null);

const fileExt = filePath?.split(".").slice(-1)[0].toLowerCase();

// fetch effect
useEffect(() => {
setFileContents(null);
(async () => {
if (!file || !fileExt) return;
if (!file?.uri) {
console.error(`Files: something went wrong while trying to load ${file?.name}`);
return;
}
if (fileExt === "pdf") {
console.error("Cannot retrieve PDF with useDropBoxFileContent");
return;
}

try {
const r = await fetch(file.uri, { headers: authHeader });
if (r.ok) {
let content;
if (ARRAY_BUFFER_FILE_EXTENSIONS.includes(fileExt)) {
content = await r.arrayBuffer();
} else if (BLOB_FILE_EXTENSIONS.includes(fileExt)) {
content = await r.blob();
} else {
const text = await r.text();
content = fileExt === "json" ? JSON.parse(text) : text;
}
setFileContents(content);
} else {
console.error(`Could not load file: ${r.body}`);
}
} catch (e) {
console.error(e);
}
})();
}, [file, fileExt, authHeader]);

return fileContents;
};

export const useJsonSchemaValidator = (schema: SchemaObject, acceptFalsyValue: boolean) => {
const ajv = new Ajv();
return useCallback(
Expand Down
53 changes: 53 additions & 0 deletions src/modules/dropBox/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { message } from "antd";
import { basicAction, createFlowActionTypes, createNetworkActionTypes, networkAction } from "@/utils/actions";

export const FETCH_DROP_BOX_TREE = createNetworkActionTypes("FETCH_DROP_BOX_TREE");
export const INVALIDATE_DROP_BOX_TREE = "INVALIDATE_DROP_BOX_TREE";

export const PUT_DROP_BOX_OBJECT = createNetworkActionTypes("PUT_DROP_BOX_OBJECT");
export const DELETE_DROP_BOX_OBJECT = createNetworkActionTypes("DELETE_DROP_BOX_OBJECT");

export const DROP_BOX_PUTTING_OBJECTS = createFlowActionTypes("DROP_BOX_PUTTING_OBJECTS");

export const fetchDropBoxTree = networkAction(() => (_dispatch, getState) => ({
types: FETCH_DROP_BOX_TREE,
check: (state) =>
state.services.dropBoxService &&
!state.dropBox.isFetching &&
(!state.dropBox.tree.length || state.dropBox.isInvalid),
url: `${getState().services.dropBoxService.url}/tree`,
err: "Error fetching drop box file tree",
}));

export const invalidateDropBoxTree = basicAction(INVALIDATE_DROP_BOX_TREE);

const dropBoxObjectPath = (getState, path) =>
`${getState().services.dropBoxService.url}/objects/${path.replace(/^\//, "")}`;

export const putDropBoxObject = networkAction((path, file) => async (_dispatch, getState) => ({
types: PUT_DROP_BOX_OBJECT,
url: dropBoxObjectPath(getState, path),
req: {
method: "PUT",
body: await file.arrayBuffer(),
},
onSuccess: () => {
message.success(`Successfully uploaded file to drop box path: ${path}`);
},
err: `Error uploading file to drop box path: ${path}`,
}));

export const beginDropBoxPuttingObjects = basicAction(DROP_BOX_PUTTING_OBJECTS.BEGIN);
export const endDropBoxPuttingObjects = basicAction(DROP_BOX_PUTTING_OBJECTS.END);

export const deleteDropBoxObject = networkAction((path) => async (dispatch, getState) => ({
types: DELETE_DROP_BOX_OBJECT,
url: dropBoxObjectPath(getState, path),
req: { method: "DELETE" },
onSuccess: () => {
message.success(`Successfully deleted file at drop box path: ${path}`);
dispatch(invalidateDropBoxTree());
return dispatch(fetchDropBoxTree());
},
err: `Error deleting file at drop box path: ${path}`,
}));
63 changes: 63 additions & 0 deletions src/modules/dropBox/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useEffect, useState } from "react";
import { RESOURCE_EVERYTHING, useAuthorizationHeader, viewDropBox } from "bento-auth-js";
import { useHasResourcePermissionWrapper } from "@/hooks";
import { useService } from "@/modules/services/hooks";
import { RootState, useAppDispatch, useAppSelector } from "@/store";
import { fetchDropBoxTree } from "./actions";

export const useDropBox = () => {
const dispatch = useAppDispatch();

const dropBox = useService("drop-box"); // TODO: associate this with the network action somehow
const { hasPermission } = useHasResourcePermissionWrapper(RESOURCE_EVERYTHING, viewDropBox);

useEffect(() => {
// If hasPermission changes to true, this will automatically dispatch the drop box tree fetch method.
if (hasPermission) {
dispatch(fetchDropBoxTree()).catch((err) => console.error(err));
}
}, [dispatch, dropBox, hasPermission]);

return useAppSelector((state) => state.dropBox);
};

export const useDropBoxFileContent = (filePath?: string): Blob | null => {
const file = useAppSelector((state: RootState) =>
state.dropBox.tree.find((f: { filePath: string | undefined }) => f?.filePath === filePath),
);
const authHeader = useAuthorizationHeader();

const [fileContents, setFileContents] = useState<Blob | null>(null);

const fileExt = filePath?.split(".").slice(-1)[0].toLowerCase();

// fetch effect
useEffect(() => {
setFileContents(null);
(async () => {
if (!file || !fileExt) return;
if (!file?.uri) {
console.error(`Files: something went wrong while trying to load ${file?.name}`);
return;
}
if (fileExt === "pdf") {
console.error("Cannot retrieve PDF with useDropBoxFileContent");
return;
}

try {
const r = await fetch(file.uri, { headers: authHeader });
if (r.ok) {
const content = await r.blob();
setFileContents(content);
} else {
console.error(`Could not load file: ${r.body}`);
}
} catch (e) {
console.error(e);
}
})();
}, [file, fileExt, authHeader]);

return fileContents;
};
51 changes: 51 additions & 0 deletions src/modules/dropBox/reducers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
FETCH_DROP_BOX_TREE,
PUT_DROP_BOX_OBJECT,
DROP_BOX_PUTTING_OBJECTS,
DELETE_DROP_BOX_OBJECT,
INVALIDATE_DROP_BOX_TREE,
} from "./actions";

export const dropBox = (
state = {
isFetching: false,
isPutting: false,
isPuttingFlow: false,
isDeleting: false,
isInvalid: false,
hasAttempted: false,
tree: [],
},
action,
) => {
switch (action.type) {
case FETCH_DROP_BOX_TREE.REQUEST:
return { ...state, isFetching: true };
case FETCH_DROP_BOX_TREE.RECEIVE:
return { ...state, tree: action.data };
case FETCH_DROP_BOX_TREE.FINISH:
return { ...state, isFetching: false, hasAttempted: true, isInvalid: false };

case INVALIDATE_DROP_BOX_TREE:
return { ...state, isInvalid: true };

case PUT_DROP_BOX_OBJECT.REQUEST:
return { ...state, isPutting: true };
case PUT_DROP_BOX_OBJECT.FINISH:
return { ...state, isPutting: false };

case DROP_BOX_PUTTING_OBJECTS.BEGIN:
return { ...state, isPuttingFlow: true };
case DROP_BOX_PUTTING_OBJECTS.END:
case DROP_BOX_PUTTING_OBJECTS.TERMINATE:
return { ...state, isPuttingFlow: false };

case DELETE_DROP_BOX_OBJECT.REQUEST:
return { ...state, isDeleting: true };
case DELETE_DROP_BOX_OBJECT.FINISH:
return { ...state, isDeleting: false };

default:
return state;
}
};
File renamed without changes.
Loading
Loading