diff --git a/PipelineData.jsx b/PipelineData.jsx new file mode 100644 index 00000000..9a0b009d --- /dev/null +++ b/PipelineData.jsx @@ -0,0 +1,201 @@ + PipelineData = { + name: "fastsurfer_dev", + authors: "gideonpinto123@gmail.com", + description: "test", + category: "mri", + locked: false, + plugin_tree: JSON.stringify([ + { + plugin_name: "pl-simpledsapp", + plugin_version: "2.0.2", + previous_index: null, + }, + { + plugin_name: "pl-pfdicom_tagextract", + plugin_version: "3.1.2", + previous_index: 0, + plugin_parameter_defaults: [ + { + name: "outputFileType", + default: "txt,scv,json,html", + }, + { + name: "outputFileStem", + default: "Post-Sub", + }, + { + name: "imageFile", + default: "'m:%_nospc|-_ProtocolName.jpg'", + }, + { + name: "imageScale", + default: "3:none", + }, + ], + }, + { + plugin_name: "pl-pfdicom_tagsub", + plugin_version: "3.2.3", + previous_index: 0, + plugin_parameter_defaults: [ + { + name: "extension", + default: ".dcm", + }, + { + name: "splitToken", + default: "++", + }, + { + name: "splitKeyValue", + default: ",", + }, + { + name: "tagInfo", + default: + "'PatientName,%_name|patientID_PatientName ++ PatientID,%_md5|7_PatientID ++ AccessionNumber,%_md5|8_AccessionNumber ++ PatientBirthDate,%_strmsk|******01_PatientBirthDate ++ re:.*hysician,%_md5|4_#tag ++ re:.*stitution,#tag ++ re:.*ddress,#tag'", + }, + ], + }, + { + plugin_name: "pl-pfdicom_tagextract", + plugin_version: "3.1.2", + previous_index: 2, + plugin_parameter_default: [ + { + name: "outputFileType", + default: "txt,scv,json,html", + }, + { + name: "outputFileStem", + default: "Post-Sub", + }, + { + name: "imageFile", + default: "'m:%_nospc|-_ProtocolName.jpg'", + }, + { + name: "imageScale", + default: "3:none", + }, + { + name: "extension", + default: ".dcm", + }, + ], + }, + { + plugin_name: "pl-fshack", + plugin_version: "1.2.0", + previous_index: 2, + plugin_parameter_default: [ + { + name: "exec", + default: "recon-all", + }, + { + name: "args", + default: "'ARGS:-autorecon1'", + }, + { + name: "outputFile", + default: "recon-of-SAG-anon-dcm", + }, + { + name: "inputFile", + default: ".dcm", + }, + ], + }, + { + plugin_name: "pl-fastsurfer_inference", + plugin_version: "1.0.15", + previous_index: 4, + plugin_parameter_default: [ + { + name: "subjectDir", + default: "recon-of-SAG-anon-dcm", + }, + { + name: "subject", + default: "mri", + }, + { + name: "copyInputFiles", + default: "mgz", + }, + { + name: "iname", + default: "brainmask.mgz", + }, + ], + }, + { + plugin_name: "pl-multipass", + plugin_version: "1.2.12", + previous_index: 5, + plugin_parameter_default: [ + { + name: "splitExpr", + default: "++", + }, + { + name: "commonArgs", + default: + "'--printElapsedTime --verbosity 5 --saveImages --skipAllLabels --outputFileStem sample --outputFileType png'", + }, + { + name: "specificArgs", + default: + "'--inputFile mri/brainmask.mgz --wholeVolume brainVolume ++ --inputFile mri/aparc.DKTatlas+aseg.deep.mgz --wholeVolume segVolume --lookupTable __fs__'", + }, + { + name: "exec", + default: "pfdo_mgz2image", + }, + ], + }, + { + plugin_name: "pl-pfdorun", + plugin_version: "2.2.6", + previous_index: 6, + plugin_parameter_default: [ + { + name: "dirFilter", + default: "label-brainVolume", + }, + { + name: "fileFilter", + default: "png", + }, + { + name: "exec", + default: + "'composite -dissolve 90 -gravity Center %inputWorkingDir/%inputWorkingFile %inputWorkingDir/../../aparc.DKTatlas+aseg.deep.mgz/label-segVolume/%inputWorkingFile -alpha Set %outputWorkingDir/%inputWorkingFile'", + }, + { + name: "verbose", + default: "5", + }, + ], + }, + { + plugin_name: "pl-mgz2lut_report", + plugin_version: "1.3.1", + previous_index: 5, + plugin_parameter_default: [ + { + name: "file_name", + default: "mri/aparc.DKTatlas+aseg.deep.mgz", + }, + { + name: "report_types", + default: "txt,csv,json,html,pdf", + }, + ], + }, + ]), + }; + + return data; +}; diff --git a/package.json b/package.json index 696ac962..fdda2cf2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,10 @@ "@patternfly/react-core": "^4.75.2", "@patternfly/react-icons": "^4.7.16", "@patternfly/react-table": "^4.27.7", + "@types/jest": "^27.4.0", + "@types/node": "^17.0.8", + "@types/react": "^17.0.38", + "@types/react-dom": "^17.0.11", "classnames": "^2.2.6", "core-js": "^2.5.7", "cross-env": "^5.2.0", @@ -42,7 +46,9 @@ "prop-types": "^15.6.2", "react": "^17.0.2", "react-bootstrap": "^0.32.1", + "react-d3-tree": "^3.2.1", "react-dom": "^17.0.2", + "react-icons": "^4.3.1", "react-router-dom": "^5.2.0", "react-scripts": "3.4.3", "react-test-renderer": "^16.4.1", diff --git a/src/components/Dashboard/components/DashCollaborator/Collaborators.jsx b/src/components/Dashboard/components/DashCollaborator/Collaborators.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/components/Dashboard/components/DashCollaborator/DashCollaboratorView.jsx b/src/components/Dashboard/components/DashCollaborator/DashCollaboratorView.jsx index 70e602c5..0d2fd382 100644 --- a/src/components/Dashboard/components/DashCollaborator/DashCollaboratorView.jsx +++ b/src/components/Dashboard/components/DashCollaborator/DashCollaboratorView.jsx @@ -9,7 +9,7 @@ import { GridItem, CardFooter, Button, - } from '@patternfly/react-core'; +} from '@patternfly/react-core'; import Client from '@fnndsc/chrisstoreapi'; import { PlusCircleIcon } from '@patternfly/react-icons'; import ChrisStore from '../../../../store/ChrisStore'; @@ -22,12 +22,10 @@ class DashCollaboratorView extends Component { this.state = { collaborators: [], errors: [], - }; const storeURL = process.env.REACT_APP_STORE_URL; const auth = { token: props.store.get('authToken') }; this.client = new Client(storeURL, auth); - } async componentDidMount() { @@ -36,11 +34,11 @@ class DashCollaboratorView extends Component { try { const pluginMeta = await this.fetchPluginMeta(pluginName); const collaboratorList = await this.fetchPluginCollaborators(pluginMeta); - - + + this.setState({ collaborators:[...collaboratorList] - + }); } catch (error) { this.setState((prev) => ({ @@ -55,86 +53,86 @@ class DashCollaboratorView extends Component { errors: [...prev.errors, error] })); } - // eslint-disable-next-line react/destructuring-assignment + // eslint-disable-next-line react/destructuring-assignment -/** - * Fetch all versions of a plugin. - * @param {PluginMeta} pluginMeta - * @returns {Promise} Collaborators of the plugin - */ + /** + * Fetch all versions of a plugin. + * @param {PluginMeta} pluginMeta + * @returns {Promise} Collaborators of the plugin + */ // eslint-disable-next-line class-methods-use-this - async fetchPluginMeta(pluginName) { + async fetchPluginMeta(pluginName) { const metas = await this.client.getPluginMetas({ name_exact: pluginName, limit: 1 }); return metas.getItems().shift(); } -// eslint-disable-next-line class-methods-use-this + // eslint-disable-next-line class-methods-use-this async fetchPluginCollaborators(pluginMeta) { const collabitems = (await pluginMeta.getCollaborators()).getItems(); - const collablist= await collabitems.map((collaborator, index) => collabitems[index].data) - const collaboratorlist=await collablist.map((collaborator, index) => collabitems[index]) + const collablist = await collabitems.map((collaborator, index) => collabitems[index].data) + const collaboratorlist = await collablist.map((collaborator, index) => collabitems[index]) return Array.from(collaboratorlist.values()) - ; + ; } render() { - const {collaborators ,errors} = this.state; + const { collaborators, errors } = this.state; return ( - <> - { - errors.map((error, index) => ( - { + <> + { + errors.map((error, index) => ( + { errors.splice(index) this.setState({ errors }) }} /> )) } - - + + - - -

Collaborators

-
- {/* FIXME disabled button bc it does nothing */} - -

Use this area, to add and manage collaborators to help you - with this plugin.

+ + +

Collaborators

+
+ +

Use this area, to add and manage collaborators to help you + with this plugin.

-
+
- -
- + + + - + + -
+ -
+
-
+ ); @@ -143,7 +141,6 @@ class DashCollaboratorView extends Component { DashCollaboratorView.propTypes = { // eslint-disable-next-line react/no-unused-prop-types collaborators: PropTypes.arrayOf(PropTypes.object), - }; DashCollaboratorView.defaultProps = { @@ -151,5 +148,4 @@ DashCollaboratorView.defaultProps = { }; - export default ChrisStore.withStore(DashCollaboratorView); diff --git a/src/components/Dashboard/components/DashPluginCardView/DashPluginCardView.jsx b/src/components/Dashboard/components/DashPluginCardView/DashPluginCardView.jsx index 63e77911..67fdb82c 100644 --- a/src/components/Dashboard/components/DashPluginCardView/DashPluginCardView.jsx +++ b/src/components/Dashboard/components/DashPluginCardView/DashPluginCardView.jsx @@ -157,8 +157,8 @@ class DashPluginCardView extends Component { Edit, Delete, Manage Collaborators} /> - - + + ]} /> diff --git a/src/components/Dashboard/components/DashTeamView/DashTeamView.jsx b/src/components/Dashboard/components/DashTeamView/DashTeamView.jsx index 4a355118..100c3c25 100644 --- a/src/components/Dashboard/components/DashTeamView/DashTeamView.jsx +++ b/src/components/Dashboard/components/DashTeamView/DashTeamView.jsx @@ -54,10 +54,21 @@ class DashTeamView extends Component { sortBy: {}, }; +<<<<<<< HEAD + this.state.rows = props.collaborators.map((collaborator, index) => { + const row = []; + const { columns } = this.state; +<<<<<<< HEAD + row.push(...columns.map(({ property }) => collaborator.data[property])); +======= + row.push(...columns.map(({property }) => collaborator.data[property])); +>>>>>>> owner +======= this.state.rows = props.collaborators.map((collaborator,index) => { const row = []; const { columns } = this.state; row.push(...columns.map(({property }) => collaborator.data[property])); +>>>>>>> addb093594cbc04c5c51e19cbc135b1fb8c397b7 return row; }); @@ -81,7 +92,11 @@ class DashTeamView extends Component { render() { const { collaborators } = this.props; const { rows, columns, sortBy } = this.state; +<<<<<<< HEAD + +======= const showEmptyState = isEmpty(collaborators); +>>>>>>> addb093594cbc04c5c51e19cbc135b1fb8c397b7 return ( @@ -89,9 +104,7 @@ class DashTeamView extends Component { Teammates - {showEmptyState ? ( - - ) : ( + <>
- )} +
- {!showEmptyState && ( - - - - + + + + + - - )} + +
@@ -156,4 +169,4 @@ DashTeamView.defaultProps = { plugins: [], }; -export default DashTeamView; +export default DashTeamView; \ No newline at end of file diff --git a/src/components/Navbar/Navbar.jsx b/src/components/Navbar/Navbar.jsx index 2a69450e..bd4e6903 100644 --- a/src/components/Navbar/Navbar.jsx +++ b/src/components/Navbar/Navbar.jsx @@ -19,10 +19,15 @@ const navLinks = [ label: 'Plugins', to: '/plugins', }, + { + label: 'Pipelines', + to: '/pipelines', + }, { label: 'Submit your Plugin', to: '/quickstart', }, + { label: 'Dashboard', to: '/dashboard', diff --git a/src/components/Pipelines/DisplayPage.jsx b/src/components/Pipelines/DisplayPage.jsx new file mode 100644 index 00000000..5128202c --- /dev/null +++ b/src/components/Pipelines/DisplayPage.jsx @@ -0,0 +1,377 @@ +/* eslint-disable no-nested-ternary */ +import React, { useRef } from "react"; +import { + Pagination, + Card, + CardTitle, + CardHeader, + CardHeaderMain, + CardBody, + Grid, + GridItem, + Drawer, + DrawerPanelContent, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerActions, + DrawerCloseButton, + Title, + Divider, + Button, + Alert, + TextInput, +} from "@patternfly/react-core"; +import { ImTree } from "react-icons/im"; +import { GrCloudComputer } from "react-icons/gr"; +import { AiOutlineUpload } from "react-icons/ai"; +import { FaCode } from "react-icons/fa"; +import Client from '@fnndsc/chrisstoreapi'; +import PluginTree from "./PluginTree"; +import Downloadjson from "./Downloadjson"; + + + + + + + +const DisplayPage = ({ + resources, + selectedResource, + pageState, + onPerPageSelect, + onSetPage, + setSelectedResource, + title, + showPipelineButton, + fetch, + handlePipelineSearch, + search, + handleDelete +}) => { + const { perPage, page, itemCount } = pageState; + const fileOpen = useRef(null); + const [fileName, setFileName] = React.useState(""); + const [error, setError] = React.useState(null); + // const [warningMessage, setWarningMessage] = React.useState(""); + // const [isSucessful, setSucessful] = React.useState(false); + const [deleteError, setDeleteError] = React.useState(""); + const [isExpanded, setIsExpanded] = React.useState(false); + const [pluginPipings, setPluginPipings] = React.useState([]); + + + + + + + + const iconStyle = { + fill: + title === "Plugins" + ? "#0066CC" + : title === "Pipelines" + ? "#1F0066" + : title === "Compute Environments " + ? "red" + : "", + height: "1.5em", + width: "1.25em", + marginRight: "0.5em", + marginTop: "0.25em", + }; + const showOpenFile = () => { + if (fileOpen.current) { + fileOpen.current.click(); + } + }; + + + + const readFile = (file) => { + const reader = new FileReader(); + + reader.onloadend = async () => { + try { + if (reader.result) { + const result = JSON.parse(reader.result); + result.plugin_tree = JSON.stringify(result.plugin_tree); + setFileName(result.name); + try { + const storeURL = process.env.REACT_APP_STORE_URL; + const token = window.sessionStorage.getItem('AUTH_TOKEN'); + const client = new Client(storeURL, { token }); + + + await client.createPipeline(result); + + fetch && fetch(); + + // setfileURls() + + + + + } catch (error) { + + + + + + + + setError(error); + + + + + + + + } + + + + + + + } + } catch (error) { + setError(error) + setFileName(""); + + } + }; + if (file) { + reader.readAsText(file); + } + }; + + + + + const handleUpload = (event) => { + const file = event.target.files && event.target.files[0]; + setError(""); + readFile(file); + }; + + + + + const drawerContent = ( + +
+ + {title} + +
+ + <> + +
+ {error && + + + + } + + {fileName} + + +
+ + + + { + + handlePipelineSearch && handlePipelineSearch(value); + } + + } + /> +
+
+ + {resources && + resources.length > 0 && + resources.map((resource) => { + return ( + + { + setSelectedResource(resource); + setIsExpanded(true); + }} + + onKeyDown={async (event) => { + if ([13, 32].includes(event.keyCode)) { + setSelectedResource(resource); + + // Check if the resource is a pipeline by either the title prop or is showPipelineButton is true. + // if it is a pipeline resource, set pluginPipings here. + + if (title === 'Pipelines') { + const Pipings = await resource.getPluginPipings(); + + setPluginPipings(pluginPipings) + } + + setIsExpanded(true); + + } + }} + className="pluginList" + key={resource.data.id} + > + + + {title === "Pipelines" ? ( + + ) : title === "Compute Environments" ? ( + + ) : ( + + )} + + + +

{resource.data.name}

+

{resource.data.authors}

+
+ + +

+ {resource.data.description} +

+
+
+
+ ); + })} +
+ ); + + const panelContent = ( + + + + { + setIsExpanded(false); + }} + /> + + {selectedResource && ( + <> + {selectedResource.data.name} +

+ {selectedResource.data.authors} +

+ + )} + +

{selectedResource.data.description}

+ + {/* Download */} + + + + + + + + + + + + + )} +
+
+ ); + + return ( + <> + + + + {drawerContent} + + + + ); +}; + +export default DisplayPage; + + + + + + + diff --git a/src/components/Pipelines/Downloadjson.jsx b/src/components/Pipelines/Downloadjson.jsx new file mode 100644 index 00000000..ad588c2a --- /dev/null +++ b/src/components/Pipelines/Downloadjson.jsx @@ -0,0 +1,74 @@ +import React, { useState, useEffect } from 'react' + +const Downloadjson = ({ selectedResource }) => { + const [fileDownloadUrl, setfileDownloadUrl] =useState(null); + const [fileName, setFileName] = useState(""); + + const Jsonfile = async() => { + const res = (await selectedResource.getPluginPipings()).data; + const pipelinedata = { ...selectedResource.data, plugin_tree: [...res] }; + let filename="" + filename=pipelinedata.name + filename=filename.replaceAll('.', ''); + setFileName(filename); + const pipelineJson = JSON.stringify(pipelinedata, null, 2); + const blob = new Blob([pipelineJson],{ + type:"application/json" + }); + const fileDownloadUrl = URL.createObjectURL(blob); + + setfileDownloadUrl(fileDownloadUrl); + // Step 5 + // () => { + // this.dofileDownload.click(); // Step 6 + // URL.revokeObjectURL(fileDownloadUrl); // Step 7 + // setState({ fileDownloadUrl: "" }) + // }) + + + + + + + } + + useEffect(() => { + Jsonfile(); + // }) + // Runs once, after mounting + + }, [fileName, fileDownloadUrl]); + + + return ( + Download + ) +} + +export default Downloadjson; + + + + + + + + + + + + + + + + +// const [fileName, setFileName] = React.useState(""); +// const [fileURls, setfileURls] = React.useState(map1); +// const [pipelineDownloadUrl, setpipelineDownloadUrl] = React.useState(null); const downloadJsonFile = () => { +// console.log(fileURls); +// } + +// const downloadJsonFile = () => { +// console.log(fileURls); +// } + diff --git a/src/components/Pipelines/PipelineCatalog.jsx b/src/components/Pipelines/PipelineCatalog.jsx new file mode 100644 index 00000000..d54e8b95 --- /dev/null +++ b/src/components/Pipelines/PipelineCatalog.jsx @@ -0,0 +1,153 @@ +import React, {useState,useEffect } from "react"; +import Client from '@fnndsc/chrisstoreapi'; +import DisplayPage from './DisplayPage'; +import ChrisStore from '../../store/ChrisStore'; + + +const PipelineCatalog = (props) => { + const [pipelines, setPipelines] = useState([]); + const [fetch, setFetch] = useState(false); + const [filteredId, setFilteredId] = React.useState(); + const [pageState, setPageState] = useState({ + page: 1, + perPage: 5, + search: "", + itemCount: 0, + }); + + const { page, perPage, search } = pageState; + const [selectedPipeline, setSelectedPipeline] = useState(); + const storeURL = process.env.REACT_APP_STORE_URL; + const auth = { token: props.store.get("authToken") }; + + + const onSetPage = (_event, page) => { + setPageState({ + ...pageState, + page, + }); + }; + const onPerPageSelect = (_event, perPage) => { + setPageState({ + ...pageState, + perPage, + }); + }; + + const handleFilterChange = (value) => { + setPageState({ + ...pageState, + search: value, + }); + }; + useEffect(() => { + async function fetchPipelines( + perPage, + page, + search + ) { + const offset = perPage * (page - 1); + const params = { + limit: perPage, + offset: offset, + name: search, + }; + const client = new Client(storeURL,auth); + const pipelinesList = await client.getPipelines(params); + let pipelines; + pipelines=pipelinesList.getItems(); + if (filteredId && pipelines) { + pipelines = pipelines.filter( + (pipeline) => pipeline.data.id !== filteredId + ); + } + if (pipelines) { + setPipelines(pipelines); + setPageState((pageState) => { + return { + ...pageState, + itemCount: pipelinesList.totalCount, + }; + }); + } + } + + fetchPipelines(perPage, page, search); + }, [perPage, page, search,fetch, filteredId]); + const handleFetch = (id) => { + id && setFilteredId(id); + setFetch(!fetch); + }; + const handleDelete = async(selectedResource) => { + + // const client = new Client(storeURL,auth); + // const pipeline = await client.getPipeline(selectedResource.data.id); + // console.log(pipeline) + // const deletedPipeline = pipeline.delete( ); + // console.log(deletedPipeline) + // return deletedPipeline + const client = new Client(storeURL, auth); + const offset = perPage * (page - 1); + const params = { + limit: perPage, + offset: offset, + name: search, + }; + + const pipelinesList = await client.getPipelines(params); + // eslint-disable-next-line prefer-const + const response = await client.getPipeline(selectedResource.data.id); + + await response.delete(); + let pipelines; + pipelines=pipelinesList.getItems(); + setPipelines(pipelines.filter((pipeline) => pipeline.data.id!==selectedResource.data.id)) + setFetch(!fetch); + + + + + + + + + + }; + + const handleSearch = (search) => { + setPageState({ + ...pageState, + search, + }); + }; + + return ( + <> + { + setSelectedPipeline(pipeline); + + + + + }} + + title="Pipelines" + fetch={handleFetch} + handlePipelineSearch={handleSearch} + search={pageState.search} + handleDelete={handleDelete} + /> + + + ); +}; + +export default ChrisStore.withStore(PipelineCatalog); diff --git a/src/components/Pipelines/PipelineTree.jsx b/src/components/Pipelines/PipelineTree.jsx new file mode 100644 index 00000000..7acb2d44 --- /dev/null +++ b/src/components/Pipelines/PipelineTree.jsx @@ -0,0 +1,41 @@ + +const getPluginTree = (items) => { + + const tree = []; + const mappedArr = {}; + + items.forEach((item) => { + const id = item.id; + if (!mappedArr.hasOwnProperty(id)) { + + mappedArr[id] = { + id, + name:item.plugin_name, + plugin_id: item.plugin_id, + pipeline_id: item.pipeline_id, + previous_id: item.previous_id && item.previous_id, + children: [], + }; + } + }); + + for (const id in mappedArr) { + let mappedElem; + if (mappedArr.hasOwnProperty(id)) { + mappedElem = mappedArr[id]; + if (mappedElem.previous_id) { + const parentId = mappedElem.previous_id; + if (parentId && mappedArr[parentId] && mappedArr[parentId].children) { + mappedArr[parentId].children.push(mappedElem); + } + } else tree.push(mappedElem); + } + } + return tree; +}; +export default getPluginTree; + + + + + diff --git a/src/components/Pipelines/PluginTree.jsx b/src/components/Pipelines/PluginTree.jsx new file mode 100644 index 00000000..13ea78c1 --- /dev/null +++ b/src/components/Pipelines/PluginTree.jsx @@ -0,0 +1,62 @@ +import React,{useState, useEffect} from 'react'; +import Tree from 'react-d3-tree'; +import getPluginTree from './PipelineTree'; +// This is a simplified example of an org chart with a depth of 2. +// Note how deeper levels are defined recursively via the `children` property. + + +const PluginTree = ({selectedResource }) => { + const [treeData, setTreeData] = useState({ + + name: "", + children:[], + + }); + const [error, setError] = React.useState(null); + + + const treedata = async() => { + console.log(selectedResource); + try { + const res = await selectedResource.getPluginPipings(); + + const tree = getPluginTree(res.data); + + + + + setTreeData({ + name:tree[0].name, + children:tree[0].children, + }); + + return treeData; + + + } catch (error) { + setError(error); + } + } + + + useEffect(() => { + treedata(); + // Runs once, after mounting + + }, []); + + + + + + + return ( + // `` will fill width/height of its container; in this case `#treeWrapper`. +
+ +
+ ); +}; + +export default PluginTree; + diff --git a/src/components/Router/Router.jsx b/src/components/Router/Router.jsx index ec9eb437..d24d55c8 100644 --- a/src/components/Router/Router.jsx +++ b/src/components/Router/Router.jsx @@ -12,6 +12,7 @@ import CreatePlugin from '../CreatePlugin/CreatePlugin'; import NotFound from '../NotFound/NotFound'; import Dashboard from '../Dashboard/Dashboard'; import DashCollaboratorView from '../Dashboard/components/DashCollaborator/DashCollaboratorView'; +import PipelineCatalog from '../Pipelines/PipelineCatalog'; import ProtectedRoute from './ProtectedRoute'; const Router = () => ( @@ -20,6 +21,7 @@ const Router = () => ( + diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 00000000..6431bc5f --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +///