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

[15] feat: implement new workflow system #282

Merged
merged 31 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6c9a77c
feat: add dataset workflow input capability
davidlougheed Aug 17, 2023
ccb61e7
chore: allow dataset tree select to not include project id
davidlougheed Aug 17, 2023
e91415d
feat: add project-dataset input type for workflows
davidlougheed Aug 17, 2023
41d2b68
lint
davidlougheed Aug 23, 2023
b7f2fa7
Merge remote-tracking branch 'origin/master' into feat/dataset-workfl…
davidlougheed Oct 13, 2023
3715fff
feat(manager): support new workflow input types
davidlougheed Oct 13, 2023
208a9f1
chore(manager): remove workflow outputs from list item
davidlougheed Oct 13, 2023
ff135aa
lint
davidlougheed Oct 13, 2023
3284f50
feat(manager): WIP new workflow selection / input fields interface
davidlougheed Oct 13, 2023
207264a
Merge branch 'chore/new-authz' into feat/dataset-workflow-input
davidlougheed Oct 24, 2023
a1cf291
Merge branch 'chore/new-authz' into feat/dataset-workflow-input
davidlougheed Oct 25, 2023
492e745
lint
davidlougheed Oct 25, 2023
1a1826e
lint
davidlougheed Oct 25, 2023
4fdb33d
Merge branch 'chore/new-authz' into feat/dataset-workflow-input
davidlougheed Nov 2, 2023
a930a00
Merge remote-tracking branch 'origin/master' into feat/dataset-workfl…
davidlougheed Nov 13, 2023
a83f9a7
fix: issues with ingestion
davidlougheed Nov 14, 2023
5af1759
chore: implement drop-box ingestion for new workflow system
davidlougheed Nov 14, 2023
95e401e
style: fix styling for workflow tags
davidlougheed Nov 14, 2023
9f51740
chore: support new ingest flow in dataset data type list
davidlougheed Nov 14, 2023
4b54d1e
fix: typo in routed project
davidlougheed Nov 15, 2023
d0e70b2
fix(manager): runsetupinputstable usememo with wrong deps
davidlougheed Nov 15, 2023
c1dc766
style: tweak style for dataset data type table
davidlougheed Nov 16, 2023
4dd1b2d
fix(manager): correctly reset workflow inputs when changing workflow
davidlougheed Nov 16, 2023
395022a
fix(manager): properly incorporate all workflow tags into tag-set
davidlougheed Nov 17, 2023
78bc6c7
fix(manager): add missing deps to useCallbacks in WorkflowFilter
davidlougheed Nov 21, 2023
9a43e01
refact: turn workflow mixin into a hook w/ byID access too
davidlougheed Nov 21, 2023
419d392
chore(manager): document where props come from for getInputComponentA…
davidlougheed Nov 21, 2023
d32da42
refact(manager): factor workflow input tag into component
davidlougheed Nov 21, 2023
0c69b6e
lint
davidlougheed Nov 21, 2023
11f237e
refact(manager): rm submit-workflow action params + make tags opt.
davidlougheed Nov 21, 2023
7aed7c4
fix(manager): make latest ingestion page work again
davidlougheed Nov 21, 2023
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
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