Skip to content

Commit

Permalink
Merge pull request #404 from bento-platform/feat/katsu-config
Browse files Browse the repository at this point in the history
feat(discovery): scoped discovery config forms
  • Loading branch information
v-rocheleau authored Sep 10, 2024
2 parents 265f437 + e06e506 commit 407da98
Show file tree
Hide file tree
Showing 34 changed files with 639 additions and 318 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
"@ant-design/icons": "^5.3.0",
"@ant-design/pro-components": "^2.7.0",
"@reduxjs/toolkit": "^1.9.7",
"ajv": "^8.12.0",
"ajv": "^8.13.0",
"ansi_up": "^6.0.2",
"antd": "^5.16.0",
"bento-auth-js": "^5.1.0",
"bento-auth-js": "^5.1.1",
"bento-charts": "~2.6.8",
"cross-fetch": "^4.0.0",
"dayjs": "^1.11.11",
Expand Down
4 changes: 2 additions & 2 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ import { Layout, message, Modal } from "antd";

import { BENTO_URL_NO_TRAILING_SLASH, OPENID_CONFIG_URL } from "@/config";
import eventHandler from "@/events";
import { useService } from "@/modules/services/hooks";
import { fetchUserDependentData } from "@/modules/user/actions";
import SessionWorker from "@/session.worker";
import { nop } from "@/utils/misc";
import { fetchUserDependentData } from "@/modules/user/actions";

import NotificationDrawer from "./notifications/NotificationDrawer";
import AutoAuthenticate from "./AutoAuthenticate";
import RequireAuth from "./RequireAuth";
import SiteHeader from "./SiteHeader";
import SiteFooter from "./SiteFooter";
import SitePageLoading from "./SitePageLoading";
import { useService } from "@/modules/services/hooks";

// Lazy-load route components
const OverviewContent = lazy(() => import("./OverviewContent"));
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/JsonView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ReactJson from "react18-json-view";
import type { Collapsed } from "react18-json-view/dist/types";
import type { JSONType } from "ajv";
import type { JSONType } from "@/types/json";

type JsonViewProps = {
src: JSONType | JSONType[] | Record<string, JSONType>;
Expand Down
58 changes: 26 additions & 32 deletions src/components/datasets/DatasetForm.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,49 @@
import PropTypes from "prop-types";

import { Form, Input } from "antd";
import { useMemo } from "react";

const { Item } = Form;

import DataUseInput from "../DataUseInput";

import { DATA_USE_PROP_TYPE_SHAPE, INITIAL_DATA_USE_VALUE } from "@/duo";
import { useDatsValidator } from "@/hooks";
import { useDiscoveryValidator } from "@/modules/metadata/hooks";
import { simpleDeepCopy } from "@/utils/misc";
import DropBoxJsonSelect from "../manager/dropBox/DropBoxJsonSelect";

const validateJson = (rule, value) => {
try {
JSON.parse(value);
return Promise.resolve();
} catch (e) {
return Promise.reject("Please enter valid JSON");
}
};
const DatasetForm = ({ initialValue, form }) => {
const discoveryValidator = useDiscoveryValidator();
const datsValidator = useDatsValidator();

const initialFormData = useMemo(() => {
return {
...initialValue,
data_use: initialValue?.data_use ?? simpleDeepCopy(INITIAL_DATA_USE_VALUE),
};
}, [initialValue]);

const DatasetForm = ({ initialValue, formRef }) => {
return (
<Form ref={formRef} layout="vertical">
<Item
label="Title"
name="title"
initialValue={initialValue?.title || ""}
rules={[{ required: true }, { min: 3 }]}
>
<Form form={form} layout="vertical" initialValues={initialFormData}>
<Item label="Title" name="title" rules={[{ required: true }, { min: 3 }]}>
<Input placeholder="My Dataset" size="large" />
</Item>
<Item
label="Description"
name="description"
initialValue={initialValue?.description || ""}
rules={[{ required: true }]}
>
<Item label="Description" name="description" rules={[{ required: true }]}>
<Input.TextArea placeholder="This is a dataset" />
</Item>
<Item label="Contact Information" name="contact_info" initialValue={initialValue?.contact_info ?? ""}>
<Item label="Contact Information" name="contact_info">
<Input.TextArea placeholder={"Name\[email protected]"} />
</Item>
<Item
label="DATS File"
name="dats_file"
initialValue={initialValue?.dats_file ? JSON.stringify(initialValue.dats_file, null, 2) : ""}
rules={[{ required: true }, { validator: validateJson }, { min: 2 }]}
>
<Input.TextArea />
<Item label="DATS File" name="dats_file" rules={[{ required: true }, { validator: datsValidator }]}>
<DropBoxJsonSelect initialValue={initialFormData?.dats_file} />
</Item>
<Item label="Discovery Configuration" name="discovery" rules={[{ validator: discoveryValidator }]}>
<DropBoxJsonSelect initialValue={initialFormData?.discovery} nullable={true} />
</Item>
<Item
label="Consent Code and Data Use Requirements"
name="data_use"
initialValue={initialValue?.data_use ?? simpleDeepCopy(INITIAL_DATA_USE_VALUE)}
rules={[
{ required: true },
(rule, value, callback) => {
Expand All @@ -75,8 +68,9 @@ DatasetForm.propTypes = {
contact_info: PropTypes.string,
data_use: DATA_USE_PROP_TYPE_SHAPE, // TODO: Shared shape for data use
dats_file: PropTypes.object,
discovery: PropTypes.object,
}),
formRef: PropTypes.object,
form: PropTypes.object,
};

export default DatasetForm;
31 changes: 18 additions & 13 deletions src/components/datasets/DatasetFormModal.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useCallback, useRef } from "react";
import { useDispatch } from "react-redux";
import { useCallback } from "react";
import PropTypes from "prop-types";

import { Button, Modal } from "antd";
import { Button, Form, Modal } from "antd";
import { PlusOutlined, SaveOutlined } from "@ant-design/icons";

import DatasetForm from "./DatasetForm";
Expand All @@ -12,37 +11,43 @@ import { addProjectDataset, saveProjectDataset, fetchProjectsWithDatasets } from
import { useProjects } from "@/modules/metadata/hooks";
import { datasetPropTypesShape, projectPropTypesShape, propTypesFormMode } from "@/propTypes";
import { nop } from "@/utils/misc";
import { useAppDispatch } from "@/store";

const DatasetFormModal = ({ project, mode, initialValue, onCancel, onOk, open }) => {
const dispatch = useDispatch();
const dispatch = useAppDispatch();

const {
isFetching: projectsFetching,
isAddingDataset: projectDatasetsAdding,
isSavingDataset: projectDatasetsSaving,
} = useProjects();

const formRef = useRef(null);
const [form] = Form.useForm();

const handleSuccess = useCallback(
async (values) => {
await dispatch(fetchProjectsWithDatasets()); // TODO: If needed / only this project...
await (onOk || nop)({ ...(initialValue || {}), values });
if (formRef.current && mode === FORM_MODE_ADD) formRef.current.resetFields();
form.resetFields();
},
[dispatch, initialValue, mode, onOk],
[dispatch, form, initialValue, onOk],
);

const handleCancel = useCallback(() => (onCancel || nop)(), [onCancel]);
const handleSubmit = useCallback(() => {
const form = formRef.current;
if (!form) return;
const handleCancel = useCallback(() => {
(onCancel || nop)();
form.resetFields();
}, [form, onCancel]);

const handleSubmit = useCallback(() => {
form
.validateFields()
.then((values) => {
const onSuccess = () => handleSuccess(values);

if (typeof values?.discovery === "string") {
values["discovery"] = JSON.parse(values["discovery"]);
}

return mode === FORM_MODE_ADD
? dispatch(addProjectDataset(project, values, onSuccess))
: dispatch(
Expand All @@ -61,7 +66,7 @@ const DatasetFormModal = ({ project, mode, initialValue, onCancel, onOk, open })
.catch((err) => {
console.error(err);
});
}, [dispatch, handleSuccess, mode, project, initialValue]);
}, [dispatch, form, handleSuccess, mode, project, initialValue]);

if (!project) return null;
return (
Expand All @@ -87,7 +92,7 @@ const DatasetFormModal = ({ project, mode, initialValue, onCancel, onOk, open })
]}
onCancel={handleCancel}
>
<DatasetForm formRef={formRef} initialValue={mode === FORM_MODE_ADD ? undefined : initialValue} />
<DatasetForm form={form} initialValue={mode === FORM_MODE_ADD ? undefined : initialValue} />
</Modal>
);
};
Expand Down
1 change: 0 additions & 1 deletion src/components/discovery/DiscoverySearchForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,6 @@ const DiscoverySearchForm = ({ onChange, dataType, setFormRef, handleVariantHidd
};

DiscoverySearchForm.propTypes = {
form: PropTypes.object,
onChange: PropTypes.func,
dataType: PropTypes.object, // TODO: Shape?
setFormRef: PropTypes.func,
Expand Down
4 changes: 2 additions & 2 deletions src/components/display/FileDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { useAuthorizationHeader } from "bento-auth-js";

import fetch from "cross-fetch";

import type { JSONType } from "ajv";

import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
import { a11yLight } from "react-syntax-highlighter/dist/esm/styles/hljs";

Expand All @@ -23,6 +21,8 @@ import xml from "react-syntax-highlighter/dist/esm/languages/hljs/xml";
import "react-pdf/dist/esm/Page/AnnotationLayer.css";
import "react-pdf/dist/esm/Page/TextLayer.css";

import type { JSONType } from "@/types/json";

import AudioDisplay from "./AudioDisplay";
import CsvDisplay from "./CsvDisplay";
import ImageBlobDisplay from "./ImageBlobDisplay";
Expand Down
11 changes: 8 additions & 3 deletions src/components/display/JsonDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useState } from "react";
import type { JSONType } from "ajv";
import { Collapse, Select, Typography } from "antd";

import JsonView from "@/components/common/JsonView";
import MonospaceText from "@/components/common/MonospaceText";
import type { JSONType } from "@/types/json";

const DEFAULT_JSON_VIEW_OPTIONS = {
collapsed: true,
Expand Down Expand Up @@ -98,12 +98,17 @@ const JsonObjectDisplay = ({ doc }: JsonObjectDisplayProps) => {
type JsonDisplayProps = {
jsonSrc?: JSONType;
showObjectWithReactJson?: boolean;
showArrayTitle?: boolean;
};

const JsonDisplay = ({ jsonSrc, showObjectWithReactJson }: JsonDisplayProps) => {
const JsonDisplay = ({ jsonSrc, showObjectWithReactJson, showArrayTitle }: JsonDisplayProps) => {
if (Array.isArray(jsonSrc)) {
// Special display for array nav
return <JsonArrayDisplay doc={jsonSrc || []} standalone />;
return <JsonArrayDisplay doc={jsonSrc || []} standalone={showArrayTitle ?? true} />;
}

if (jsonSrc === null) {
return <MonospaceText>null</MonospaceText>;
}

if (typeof jsonSrc === "object") {
Expand Down
16 changes: 8 additions & 8 deletions src/components/manager/ManagerDropBoxContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,26 @@ import { LAYOUT_CONTENT_STYLE } from "@/styles/layoutContent";

import ActionContainer from "./ActionContainer";
import DownloadButton from "../common/DownloadButton";
import DropBoxTreeSelect from "./DropBoxTreeSelect";
import DropBoxTreeSelect from "./dropBox/DropBoxTreeSelect";
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
4 changes: 2 additions & 2 deletions src/components/manager/RunSetupInputForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from "prop-types";
import Handlebars from "handlebars";

import { Button, Checkbox, Form, Input, Select, Spin } from "antd";
import { LeftOutlined, RightOutlined } from "@ant-design/icons";

import { FORM_LABEL_COL, FORM_WRAPPER_COL, FORM_BUTTON_COL } from "./workflowCommon";

Expand All @@ -13,9 +14,8 @@ import { workflowPropTypesShape } from "@/propTypes";
import { testFileAgainstPattern } from "@/utils/files";
import { nop } from "@/utils/misc";

import DropBoxTreeSelect from "./dropBox/DropBoxTreeSelect";
import DatasetTreeSelect, { ID_FORMAT_PROJECT_DATASET } from "./DatasetTreeSelect";
import DropBoxTreeSelect from "./DropBoxTreeSelect";
import { LeftOutlined, RightOutlined } from "@ant-design/icons";

const EnumSelect = forwardRef(({ mode, onChange, values: valuesConfig, value }, ref) => {
const isUrl = typeof valuesConfig === "string";
Expand Down
Loading

0 comments on commit 407da98

Please sign in to comment.