Skip to content

Commit

Permalink
Merge pull request #282 from bento-platform/feat/dataset-workflow-input
Browse files Browse the repository at this point in the history
[15] feat: implement new workflow system
  • Loading branch information
davidlougheed authored Nov 21, 2023
2 parents fc4d5c4 + 7aed7c4 commit 4282ac5
Show file tree
Hide file tree
Showing 26 changed files with 769 additions and 693 deletions.
4 changes: 1 addition & 3 deletions src/components/datasets/Dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ class Dataset extends Component {
isPrivate={isPrivate} />,
data_types: <DatasetDataTypes dataset={this.state}
project={this.props.project}
isPrivate={isPrivate}
onDatasetIngest={this.props.onDatasetIngest}/>,
isPrivate={isPrivate} />,
linked_field_sets: (
<>
<Typography.Title level={4}>
Expand Down Expand Up @@ -295,7 +294,6 @@ Dataset.propTypes = {
value: datasetPropTypesShape,

onEdit: PropTypes.func,
onDatasetIngest: PropTypes.func,

addLinkedFieldSet: PropTypes.func,
deleteProjectDataset: PropTypes.func,
Expand Down
237 changes: 132 additions & 105 deletions src/components/datasets/DatasetDataTypes.js
Original file line number Diff line number Diff line change
@@ -1,130 +1,157 @@
import React, {useCallback, useMemo, useState} from "react";
import { useSelector, useDispatch } from "react-redux";
import { Button, Col, Row, Table, Typography } from "antd";

import PropTypes from "prop-types";

import { Button, Col, Dropdown, Icon, Menu, Row, Table, Typography } from "antd";

import { useWorkflows } from "../../hooks";
import { useStartIngestionFlow } from "../manager/workflowCommon";
import { datasetPropTypesShape, projectPropTypesShape } from "../../propTypes";
import { clearDatasetDataType } from "../../modules/metadata/actions";
import { fetchDatasetDataTypesSummariesIfPossible } from "../../modules/datasets/actions";

import genericConfirm from "../ConfirmationModal";
import DataTypeSummaryModal from "./datatype/DataTypeSummaryModal";
import { nop } from "../../utils/misc";

const NA_TEXT = <span style={{ color: "#999", fontStyle: "italic" }}>N/A</span>;

const DatasetDataTypes = React.memo(
({isPrivate, project, dataset, onDatasetIngest}) => {
const dispatch = useDispatch();
const datasetDataTypes = useSelector((state) => Object.values(
state.datasetDataTypes.itemsByID[dataset.identifier]?.itemsByID ?? {}));
const datasetSummaries = useSelector((state) => state.datasetSummaries.itemsByID[dataset.identifier]);
const isFetchingDataset = useSelector(
(state) => state.datasetDataTypes.itemsByID[dataset.identifier]?.isFetching);

const [datatypeSummaryVisible, setDatatypeSummaryVisible] = useState(false);
const [selectedDataType, setSelectedDataType] = useState(null);

const selectedSummary = datasetSummaries?.data?.[selectedDataType?.id] ?? {};

const handleClearDataType = useCallback((dataType) => {
genericConfirm({
title: `Are you sure you want to delete the "${dataType.label || dataType.id}" data type?`,
content: "Deleting this means all instances of this data type contained in the dataset " +
"will be deleted permanently, and will no longer be available for exploration.",
onOk: async () => {
await dispatch(clearDatasetDataType(dataset.identifier, dataType.id));
await dispatch(fetchDatasetDataTypesSummariesIfPossible(dataset.identifier));
},
});
}, [dispatch, dataset]);
const DatasetDataTypes = React.memo(({ isPrivate, project, dataset }) => {
const dispatch = useDispatch();
const datasetDataTypes = useSelector((state) => Object.values(
state.datasetDataTypes.itemsByID[dataset.identifier]?.itemsByID ?? {}));
const datasetSummaries = useSelector((state) => state.datasetSummaries.itemsByID[dataset.identifier]);
const isFetchingDataset = useSelector(
(state) => state.datasetDataTypes.itemsByID[dataset.identifier]?.isFetching);

const showDataTypeSummary = useCallback((dataType) => {
setSelectedDataType(dataType);
setDatatypeSummaryVisible(true);
}, []);
const { workflowsByType } = useWorkflows();
const ingestionWorkflows = workflowsByType.ingestion.items;

const dataTypesColumns = useMemo(() => [
{
title: "Name",
key: "label",
render: (dt) =>
isPrivate ? (
<a onClick={() => showDataTypeSummary(dt)}>
{dt.label ?? NA_TEXT}
</a>
) : dt.label ?? NA_TEXT,
defaultSortOrder: "ascend",
sorter: (a, b) => a.label.localeCompare(b.label),
const [datatypeSummaryVisible, setDatatypeSummaryVisible] = useState(false);
const [selectedDataType, setSelectedDataType] = useState(null);

const selectedSummary = datasetSummaries?.data?.[selectedDataType?.id] ?? {};

const handleClearDataType = useCallback((dataType) => {
genericConfirm({
title: `Are you sure you want to delete the "${dataType.label || dataType.id}" data type?`,
content: "Deleting this means all instances of this data type contained in the dataset " +
"will be deleted permanently, and will no longer be available for exploration.",
onOk: async () => {
await dispatch(clearDatasetDataType(dataset.identifier, dataType.id));
await dispatch(fetchDatasetDataTypesSummariesIfPossible(dataset.identifier));
},
});
}, [dispatch, dataset]);

const showDataTypeSummary = useCallback((dataType) => {
setSelectedDataType(dataType);
setDatatypeSummaryVisible(true);
}, []);

const startIngestionFlow = useStartIngestionFlow();

const dataTypesColumns = useMemo(() => [
{
title: "Name",
key: "label",
render: (dt) =>
isPrivate ? (
<a onClick={() => showDataTypeSummary(dt)}>
{dt.label ?? NA_TEXT}
</a>
) : dt.label ?? NA_TEXT,
defaultSortOrder: "ascend",
sorter: (a, b) => a.label.localeCompare(b.label),
},
{
title: "Count",
dataIndex: "count",
render: (c) => (c ?? NA_TEXT),
},
...(isPrivate ? [
{
title: "Count",
dataIndex: "count",
render: (c) => (c ?? NA_TEXT),
},
...(isPrivate ? [
{
title: "Actions",
key: "actions",
width: 230,
render: (dt) => (
<Row gutter={10}>
<Col span={12}>
<Button
icon="import"
style={{ width: "100%" }}
onClick={() => (onDatasetIngest || nop)(project, dataset, dt)}
>
Ingest
</Button>
</Col>
<Col span={12}>
<Button
type="danger"
icon="delete"
disabled={ !dt.count || dt.count && dt.count === 0}
onClick={() => handleClearDataType(dt)}
style={{ width: "100%" }}
>
Clear
title: "Actions",
key: "actions",
width: 240,
render: (dt) => {
const dtIngestionWorkflows = ingestionWorkflows
.filter((wf) => wf.data_type === dt.id || (wf.tags ?? []).includes(dt.id));
const dtIngestionWorkflowsByID = Object.fromEntries(
dtIngestionWorkflows.map((wf) => [wf.id, wf]));

const ingestMenu = (
<Menu onClick={(i) => startIngestionFlow(dtIngestionWorkflowsByID[i.key], {
// TODO: this requires that exactly this input is present, and may break in the future
// in a bit of a non-obvious way.
"project_dataset": `${project.identifier}:${dataset.identifier}`,
})}>
{dtIngestionWorkflows.map((wf) => (<Menu.Item key={wf.id}>{wf.name}</Menu.Item>))}
</Menu>
);

const ingestDropdown = (
<Dropdown overlay={ingestMenu} trigger={["click"]}>
<Button icon="import" style={{ width: "100%" }} disabled={!dtIngestionWorkflows.length}>
Ingest <Icon type="down" />
</Button>
</Col>
</Row>
),
</Dropdown>
);

return (
<Row gutter={10}>
<Col span={13}>
{ingestDropdown}
</Col>
<Col span={11}>
<Button
type="danger"
icon="delete"
disabled={ !dt.count || dt.count && dt.count === 0}
onClick={() => handleClearDataType(dt)}
style={{ width: "100%" }}
>
Clear
</Button>
</Col>
</Row>
);
},
] : null),
], [isPrivate, project, dataset, onDatasetIngest]);

const onDataTypeSummaryModalCancel = useCallback(() => setDatatypeSummaryVisible(false), []);

return (
<>
<DataTypeSummaryModal
dataType={selectedDataType}
summary={selectedSummary}
visible={datatypeSummaryVisible}
onCancel={onDataTypeSummaryModalCancel}
/>

<Typography.Title level={4}>
Data Types
</Typography.Title>

<Table
bordered
dataSource={datasetDataTypes}
rowKey="id"
columns={dataTypesColumns}
loading={isFetchingDataset}
/>
</>
);
});
},
] : null),
], [isPrivate, project, dataset, ingestionWorkflows, startIngestionFlow]);

const onDataTypeSummaryModalCancel = useCallback(() => setDatatypeSummaryVisible(false), []);

return (
<>
<DataTypeSummaryModal
dataType={selectedDataType}
summary={selectedSummary}
visible={datatypeSummaryVisible}
onCancel={onDataTypeSummaryModalCancel}
/>

<Typography.Title level={4}>
Data Types
</Typography.Title>

<Table
bordered
size="middle"
pagination={false}
dataSource={datasetDataTypes}
rowKey="id"
columns={dataTypesColumns}
loading={isFetchingDataset}
/>
</>
);
});

DatasetDataTypes.propTypes = {
isPrivate: PropTypes.bool,
project: projectPropTypesShape,
dataset: datasetPropTypesShape,
onDatasetIngest: PropTypes.func,
};

export default DatasetDataTypes;
2 changes: 1 addition & 1 deletion src/components/explorer/IndividualExperiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Button, Descriptions, Icon, Popover, Table, Tooltip, Typography } from

import { experimentPropTypesShape, experimentResultPropTypesShape, individualPropTypesShape } from "../../propTypes";
import { getFileDownloadUrlsFromDrs } from "../../modules/drs/actions";
import { guessFileType } from "../../utils/guessFileType";
import { guessFileType } from "../../utils/files";

import { useDeduplicatedIndividualBiosamples, useIndividualResources } from "./utils";
import { VIEWABLE_FILE_EXTENSIONS } from "../display/FileDisplay";
Expand Down
2 changes: 1 addition & 1 deletion src/components/explorer/IndividualTracks.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { BENTO_PUBLIC_URL, BENTO_URL } from "../../config";
import { individualPropTypesShape } from "../../propTypes";
import { getIgvUrlsFromDrs } from "../../modules/drs/actions";
import { setIgvPosition } from "../../modules/explorer/actions";
import { guessFileType } from "../../utils/guessFileType";
import { guessFileType } from "../../utils/files";
import {useDeduplicatedIndividualBiosamples} from "./utils";

const SQUISHED_CALL_HEIGHT = 10;
Expand Down
47 changes: 0 additions & 47 deletions src/components/manager/DatasetSelectionModal.js

This file was deleted.

Loading

0 comments on commit 4282ac5

Please sign in to comment.