diff --git a/src/app/Workflows/Pipelines/Pipeline/Pipeline.tsx b/src/app/Workflows/Pipelines/Pipeline/Pipeline.tsx index bd6eabfee..0dff90b37 100644 --- a/src/app/Workflows/Pipelines/Pipeline/Pipeline.tsx +++ b/src/app/Workflows/Pipelines/Pipeline/Pipeline.tsx @@ -105,71 +105,6 @@ const Pipeline: React.FC = ({ groupId, pipelineId }) => { {pipeline && tasks && (
-
- - - - - - - - - - - - - - - - - -
iduuidlast runprevious runruns
{pipeline.id}{pipeline.uuid} - {pipeline.current_run && ( - - view - - )} - - {pipeline.last_run && ( - - view - - )} - - - view - -
- - {tasks.length ? ( -
- {tasks.map((task) => { - return ( -
- -
- ); - })} -
- ) : ( - No tasks - )} -
-
)} diff --git a/src/app/Workflows/Pipelines/Pipeline/_Layout/Layout.tsx b/src/app/Workflows/Pipelines/Pipeline/_Layout/Layout.tsx index bc429bab8..c046c332b 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_Layout/Layout.tsx +++ b/src/app/Workflows/Pipelines/Pipeline/_Layout/Layout.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Router } from '../_Router'; const Layout: React.FC = () => { - return ; + return }; export default Layout; diff --git a/src/app/Workflows/Pipelines/Pipeline/_Router/Router.tsx b/src/app/Workflows/Pipelines/Pipeline/_Router/Router.tsx index 94217ac82..4ae6718e6 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_Router/Router.tsx +++ b/src/app/Workflows/Pipelines/Pipeline/_Router/Router.tsx @@ -1,21 +1,43 @@ import React from 'react'; -import { Route, RouteComponentProps, Switch } from 'react-router-dom'; - +import { Redirect, Route, RouteComponentProps, Switch, useLocation, useRouteMatch } from 'react-router-dom'; import { default as Pipeline } from '../Pipeline'; +import { default as PipelineRunsLayout } from '../../PipelineRuns'; +import { default as TaskLayout } from '../../Task'; +import { Menu } from 'app/Workflows/Pipelines/Pipeline/_components'; const Router: React.FC = () => { + const location = useLocation() + const { path } = useRouteMatch() return ( + + + + + + + + + ) => } + tab: string; + }>) => ( + <> + + + + )} /> ); diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.old.tsx b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.old.tsx deleted file mode 100644 index 9870bee2f..000000000 --- a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.old.tsx +++ /dev/null @@ -1,359 +0,0 @@ -import { Workflows } from '@tapis/tapis-typescript'; -import { Workflows as Hooks } from '@tapis/tapisui-hooks'; -import { Icon } from '@tapis/tapisui-common'; -import { useExtension } from 'extensions'; -import styles from './DagView.module.scss'; -import { Button } from 'reactstrap'; -import Tooltip from '@mui/material/Tooltip'; -import { useCallback, useEffect, useReducer } from 'react'; -import { useQueryClient } from 'react-query'; -import { TaskEditor } from '../../../_components'; -import { - MenuList, - MenuItem, - ListItemText, - ListItemIcon, - Divider, - Paper, -} from '@mui/material'; -import { - Delete, - Edit, - Hub, - Input, - Output, - Visibility, -} from '@mui/icons-material'; -import { useLocation } from 'react-router-dom'; - -type NodeTaskActionProps = { - onClickEdit?: () => void; - onClickDelete?: () => void; - task: Workflows.Task; -}; - -const NodeTaskActions: React.FC = ({ - onClickEdit, - onClickDelete, - task, -}) => { - const location = useLocation(); - return ( - - - - - - - View - - - - - - Edit - - - - - - - Dependencies - - - - - - Input - - - - - - Output - - - - - - - Delete - - - - ); -}; - -type DagViewProps = { - tasks: Array; - pipelineId: string; - groupId: string; -}; - -type State = { - tasks: Array; - selectedTask?: Workflows.Task | undefined; - editTask?: Workflows.Task | undefined; -}; - -enum EnumActionType { - ADD_TASK, - DELETE_TASK, - SELECT_TASK, - EDIT_TASK, -} - -type Action = { - type: EnumActionType; - payload: Workflows.Task; -}; - -const reducer = (state: State, action: Action) => { - switch (action.type) { - case EnumActionType.ADD_TASK: - return { - ...state, - tasks: [...state.tasks, action.payload], - }; - - case EnumActionType.DELETE_TASK: - return { - ...state, - tasks: state.tasks.filter((task) => task.id !== action.payload.id), - }; - case EnumActionType.SELECT_TASK: - let selectedTask: Workflows.Task | undefined = action.payload; - if (state.selectedTask && selectedTask.id === state.selectedTask.id) { - selectedTask = undefined; - } - return { - ...state, - selectedTask, - }; - case EnumActionType.EDIT_TASK: - let editTask: Workflows.Task | undefined = action.payload; - if (state.editTask && editTask.id === state.editTask.id) { - editTask = undefined; - } - return { - ...state, - editTask, - }; - default: - return state; - } -}; - -const initialReducerState = { - tasks: [], - selectedTask: undefined, -}; - -const DagView: React.FC = ({ groupId, pipelineId, tasks }) => { - // const [state, dispatch] = useReducer(reducer, initialReducerState); - // useEffect(() => { - // const taskIdsInState = state.tasks.map((t) => t.id); - // tasks.map((task) => { - // if (!taskIdsInState.includes(task.id)) { - // dispatch({ type: EnumActionType.ADD_TASK, payload: task }); - // } - // }); - // }, [tasks]); - - const { extension } = useExtension(); - const queryClient = useQueryClient(); - - const sidebarTasks = - extension?.serviceCustomizations?.workflows?.dagTasks || []; - - // const { - // create, - // isLoading: createTaskIsLoading, - // isError: createTaskIsError, - // error: createTaskError, - // reset: createTaskReset, - // } = Hooks.Tasks.useCreate(); - - // const onSuccessCreateTask = useCallback(() => { - // queryClient.invalidateQueries(Hooks.Tasks.queryKeys.list); - // createTaskReset(); - // }, [queryClient, createTaskReset]); - - // const { - // removeAsync, - // isLoading: deleteTaskIsLoading, - // isError: deleteTaskIsError, - // error: deleteTaskError, - // isSuccess: deleteTaskIsSuccess, - // reset: deleteTaskReset, - // } = Hooks.Tasks.useDelete(); - - // const onSuccessDeleteTask = useCallback(() => { - // queryClient.invalidateQueries(Hooks.Tasks.queryKeys.list); - // deleteTaskReset(); - // }, [queryClient, deleteTaskReset]); - - // const handleEditTask = (task: Workflows.Task) => { - // // Unselect a task if selected - // if (state.selectedTask) { - // dispatch({ - // type: EnumActionType.SELECT_TASK, - // payload: state.selectedTask, - // }); - // } - // dispatch({ type: EnumActionType.EDIT_TASK, payload: task }); - // }; - - // const handleCreateTask = (task: Workflows.Task) => { - // // Unselect a task if selected - // if (state.selectedTask) { - // dispatch({ - // type: EnumActionType.SELECT_TASK, - // payload: state.selectedTask, - // }); - // } - - // const taskIdsInState = state.tasks.map((t) => t.id); - // let i = 1; - // let taskId = task.id; - // while (taskIdsInState.includes(task.id)) { - // i++; - // if (!taskIdsInState.includes(taskId + `${i}`)) { - // break; - // } - // } - // taskId = i == 1 ? taskId : taskId + `${i}`; - // task = { ...task, id: taskId }; - // dispatch({ type: EnumActionType.ADD_TASK, payload: task }); - // switch (task.type) { - // case Workflows.EnumTaskType.Function: - // create( - // { - // groupId, - // pipelineId, - // reqTask: { - // ...task, - // id: task.id!, - // runtime: task.runtime!, - // installer: task.installer!, - // code: task.code! || undefined, - // }, - // }, - // { - // onSuccess: onSuccessCreateTask, - // onError: () => { - // dispatch({ type: EnumActionType.DELETE_TASK, payload: task }); - // }, - // } - // ); - // } - // }; - - // const handleDeleteTask = (task: Workflows.Task) => { - // dispatch({ type: EnumActionType.DELETE_TASK, payload: task }); - // removeAsync( - // { groupId, pipelineId, taskId: task.id! }, - // { - // onSuccess: onSuccessDeleteTask, - // onError: () => { - // dispatch({ type: EnumActionType.ADD_TASK, payload: task }); - // }, - // } - // ); - // }; - - // const handleSelectTask = (task: Workflows.Task) => { - // dispatch({ type: EnumActionType.SELECT_TASK, payload: task }); - // }; - - return ( - <> - //
- // {state.editTask ? ( - // { - // handleEditTask(state.editTask!); - // }} - // /> - // ) : ( - //
- //
- // {sidebarTasks.map((task) => { - // return ( - // - //
- //
- // {task.id} - //
- // - //
- //
- // ); - // })} - //
- //
- //
- // {createTaskIsError && 'ERROR:' + createTaskError?.message} - // {deleteTaskIsError && 'ERROR:' + deleteTaskError?.message} - // {state.tasks.map((task) => { - // let classNames = `${styles['dag-task']}`; - // let isActive = - // state.selectedTask && state.selectedTask.id == task.id; - // if (isActive) { - // classNames += ' ' + styles['dag-task-active']; - // } - // return ( - // <> - // - //
{ - // handleSelectTask(task); - // }} - // > - // {task.id} - //
- //
- // {isActive && ( - // { - // handleDeleteTask(task); - // }} - // onClickEdit={() => { - // handleEditTask(task); - // }} - // /> - // )} - // - // ); - // })} - //
- //
- //
- // )} - //
- ); -}; - -export default DagView; diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.tsx b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.tsx index e428ff2e4..9116b4150 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.tsx +++ b/src/app/Workflows/Pipelines/Pipeline/_components/DagView/DagView.tsx @@ -4,20 +4,11 @@ import { Workflows } from '@tapis/tapis-typescript'; import { Workflows as Hooks } from '@tapis/tapisui-hooks'; import { useExtension } from 'extensions'; import styles from './DagView.module.scss'; -import { Button } from 'reactstrap'; -import Tooltip from '@mui/material/Tooltip'; -import { useQueryClient } from 'react-query'; -import { TaskEditor } from '../../../_components'; import { - MenuList, - MenuItem, ListItemText, ListItemIcon, Divider, - Paper, Chip, - SpeedDial, - SpeedDialAction, Drawer, Box, List, @@ -131,6 +122,7 @@ const DagViewDrawer: React.FC = ({ { + console.log({task}) handleCreateDagTask(task as Workflows.Task); }} > diff --git a/src/app/Workflows/Pipelines/Pipeline/_components/Menu/Menu.tsx b/src/app/Workflows/Pipelines/Pipeline/_components/Menu/Menu.tsx index 71830ab62..dc9dea855 100644 --- a/src/app/Workflows/Pipelines/Pipeline/_components/Menu/Menu.tsx +++ b/src/app/Workflows/Pipelines/Pipeline/_components/Menu/Menu.tsx @@ -1,40 +1,30 @@ import React from 'react'; -import { useHistory } from 'react-router-dom'; -import Stack from '@mui/material/Stack'; -import { Button } from '@mui/material'; +import { useHistory, useRouteMatch } from 'react-router-dom'; +import { Box, Tab } from '@mui/material'; +import { TabList, TabContext } from '@mui/lab'; -const Menu: React.FC = () => { - const history = useHistory(); +const Menu: React.FC<{tab: string}> = ({tab}) => { + const { url } = useRouteMatch() + const history = useHistory() + const handleChangeTab = (_: React.SyntheticEvent, value: string) => { + const parts = url.split("/") + parts.pop() + history.push(`${parts.join("/")}/${value}`) +} return ( - - - - - + + + + + + + + + + + + + ); }; diff --git a/src/app/Workflows/Pipelines/PipelineRuns/PipelineRuns.tsx b/src/app/Workflows/Pipelines/PipelineRuns/PipelineRuns.tsx index c499e887f..992668e53 100644 --- a/src/app/Workflows/Pipelines/PipelineRuns/PipelineRuns.tsx +++ b/src/app/Workflows/Pipelines/PipelineRuns/PipelineRuns.tsx @@ -99,13 +99,13 @@ const PipelineRuns: React.FC = ({ groupId, pipelineId }) => { )} - {showModal && groupId && pipelineId && ( + {/* {showModal && groupId && pipelineId && ( - )} + )} */} ); }; diff --git a/src/app/Workflows/Pipelines/_Router/Router.tsx b/src/app/Workflows/Pipelines/_Router/Router.tsx index 6e6f32116..60a3e6c47 100644 --- a/src/app/Workflows/Pipelines/_Router/Router.tsx +++ b/src/app/Workflows/Pipelines/_Router/Router.tsx @@ -1,33 +1,22 @@ import React from 'react'; import { Route, useRouteMatch, Switch } from 'react-router-dom'; - -import { default as Pipelines } from '../Pipelines'; -import { default as Pipeline } from '../Pipeline'; -import { default as PipelineRuns } from '../PipelineRuns'; -import { default as Task } from '../Task'; +import { default as PipelinesLayout } from '../Pipelines'; +import { default as PipelineLayout } from '../Pipeline'; const Router: React.FC = () => { const { path } = useRouteMatch(); return ( - + - - - - - + - - - - - - - + + + ); diff --git a/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/FunctionTaskEditor.module.scss b/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/FunctionTaskEditor.module.scss deleted file mode 100644 index fbecf0d98..000000000 --- a/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/FunctionTaskEditor.module.scss +++ /dev/null @@ -1,63 +0,0 @@ -.container { - display: flex; - flex-direction: row; -} - -.stack { - padding-bottom: 8px; -} - -.close { - padding: 2px; - border: 2px solid #cccccc; - border-radius: 3px; -} - -.close:hover { - cursor: pointer; - border-color: #333333; - transition: 0.2s; -} - -.body-with-sidebar { - width: 70%; -} - -.body-wo-sidebar { - width: 100%; -} - -.code-container { - display: flex; - flex-direction: column; - border: 1px solid #cccccc; - min-height: 320px; - max-height: 100%; -} - -.code-runtime { - font-size: 14px; -} - -.code-container-header { - padding: 4px; -} - -.code-container-header-item { - cursor: pointer; -} - -.code { -} - -.form-group { - padding: 16px; - display: grid; -} - -.form { - width: 100%; - padding: 16px; - display: grid; - gap: 16px; -} diff --git a/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/FunctionTaskEditor.tsx b/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/FunctionTaskEditor.tsx deleted file mode 100644 index 53b3604e9..000000000 --- a/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/FunctionTaskEditor.tsx +++ /dev/null @@ -1,731 +0,0 @@ -import React, { useState, useMemo } from 'react'; -import { Workflows } from '@tapis/tapis-typescript'; -import { Workflows as Hooks } from '@tapis/tapisui-hooks'; -import CodeMirror from '@uiw/react-codemirror'; -import { python } from '@codemirror/lang-python'; -import { vscodeDark } from '@uiw/codemirror-theme-vscode'; -import { decode, encode } from 'base-64'; -import styles from './FunctionTaskEditor.module.scss'; -import { - Delete, - ArrowBack, - Update, - Info, - Hub, - CompareArrows, - GitHub, - Tune, - Memory, - Fullscreen, - FullscreenExit, -} from '@mui/icons-material'; -import { LoadingButton as Button, TabContext, TabList } from '@mui/lab'; -import { - // Button, - Box, - TextField, - Checkbox, - FormGroup, - FormControlLabel, - FormControl, - InputLabel, - Select, - MenuItem, - FormHelperText, - Tab, - Chip, - Stack, - Snackbar, - Alert, - AlertTitle, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, -} from '@mui/material'; - -type SidebarProps = { - title: string; - toggle: () => void; -}; - -const Sidebar: React.FC> = ({ - children, - title, - toggle, -}) => { - return ( -
-
-

{title}

- -
- {children} -
- ); -}; - -type FunctionTaskEditorProps = { - groupId: string; - pipelineId: string; - task: Workflows.FunctionTask; - tasks: Array; - defaultTab?: string; -}; - -const FunctionTaskEditor: React.FC = ({ - groupId, - pipelineId, - task, - tasks, - defaultTab = 'code', -}) => { - const initialTaskData = JSON.parse(JSON.stringify(task)); - console.log({ initialTaskData }); - const [modal, setModal] = useState(undefined); - const [tab, setTab] = useState(defaultTab); - const [patchData, setPatchData] = - useState>(initialTaskData); - const { patch, isLoading, isSuccess, isError, error, reset } = - Hooks.Tasks.usePatch(); - - const patchTask = ( - task: Workflows.FunctionTask, - data: Partial - ) => { - setPatchData({ - ...task, - ...data, - }); - }; - - console.log({ patchData }); - - const packageConverter = ( - packages: Array | string, - reverse = false - ) => { - if (!reverse) { - let packageString = ''; - (packages as Array).map((name) => { - packageString += name + '\n'; - }); - - return packageString; - } - - const splitPackages = (packages as string) - .replace(/^\s+|\s+$/g, '') - .replace(/\s/g, ' ') - .split(' '); - return splitPackages; - }; - - const handlePatch = () => { - patch({ - groupId, - pipelineId, - taskId: task.id!, - task: patchData as Workflows.Task, - }); - }; - - const handleChangeTab = (_: React.SyntheticEvent, tab: string) => { - setTab(tab); - }; - - const dependentTasks = useMemo(() => { - return tasks.filter((t) => { - for (let dep of t.depends_on!) { - if (dep.id === task.id) { - return true; - } - } - return false; - }); - }, [task, tasks, patchData]); - - const handleUpdateDep = (taskId: string, action: 'add' | 'remove') => { - if (action === 'add') { - // TODO handle for can_fail and can_skip - patchTask(task, { - depends_on: [ - ...patchData.depends_on!, - { id: taskId, can_fail: false, can_skip: false }, - ], - }); - return; - } - - if (action === 'remove') { - patchTask(task, { - depends_on: [ - ...patchData.depends_on!.filter((dep) => dep.id !== taskId), - ], - }); - return; - } - }; - - return ( -
- - - - - - - - - - - - - - - - {isError && error && ( - { - reset(); - }} - > - Error - {error.message} - - )} - {isSuccess && ( - - { - reset(); - }} - > - Task {task.id} updated successfully - - - )} - - - - -
- {tab === 'general' && ( - { - setTab('code'); - }} - > - - - - { - patchTask(task, { description: e.target.value }); - }} - /> - - - )} - {tab === 'deps' && ( - { - setTab('code'); - }} - > - - {tasks.map((dep) => { - if (dep.id === task.id) { - return; - } - return ( - t.id === dep.id) - .length > 0 - } - style={{ padding: 0 }} - onChange={(e) => { - if (e.target.checked) { - handleUpdateDep(e.target.value, 'add'); - return; - } - handleUpdateDep(e.target.value, 'remove'); - }} - value={dep.id} - /> - } - label={dep.id} - /> - ); - })} - - - )} - {tab === 'io' && ( - { - setTab('code'); - }} - > - {Object.entries(task.input || {}).map((k, v) => { - return `${k}:${v}`; - })} - - )} - {tab === 'execprofile' && ( - { - setTab('code'); - }} - > -
- - - Task invocation mode - - - - - Excute tasks asynchronously or serially - - - - Retry policy - - - - - Controls how soon to retry a task once it enters a failed state - - - - Flavor - - - - - How many cpus/gpus, and how much memory and disk available to - this task - - - - Maximum number of times this task will execute after failing - once - - - - Max time in seconds this task is permitted to run - -
-
- )} - {tab === 'runtime' && ( - { - setTab('code'); - }} - > -
- - - Runtime environment - - - - - The runtime envrionment in which the function code will be - executed - - { - patchTask(task, { - packages: packageConverter( - e.target.value, - true - ) as Array, - }); - }} - /> - - Each package must be on a seperate line. May be just the package - name or the pacakge name followed by the version. Ex. - tapipy==^1.6.0 - - - - Installer - - - - - Choose which installer to use to install the packages above - -
-
- )} - {tab === 'git' && ( - { - setTab('code'); - }} - > - Test - - )} -
-
- - { - setModal('runtime'); - }} - /> - -
- { - patchTask(task, { code: encode(value) }); - }} - style={{ - fontSize: 12, - backgroundColor: '#1E1E1E', - height: '100%', - fontFamily: - 'ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace', - }} - /> -
-
- {}} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - Delete task? - - {dependentTasks.length > 0 && ( - - This task is required by {dependentTasks.length} other task - {dependentTasks.length > 1 ? 's' : ''} in this pipeline: [{' '} - {dependentTasks.map((d) => `${d.id} `)}]. -
- Running this workflow after this task is deleted will result in an - immediate failure. -
- )} - - Deleting a task is an irrevocable action. Are you sure you want to - continue? - -
- - - - -
- {}} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - Update Runtime - -
- - - Runtime environment - - - - - The runtime envrionment in which the function code will be - executed - -
-
- - - - -
-
- ); -}; - -export default FunctionTaskEditor; diff --git a/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/index.ts b/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/index.ts deleted file mode 100644 index 16592a408..000000000 --- a/src/app/Workflows/Pipelines/_components/TaskEditor/FunctionTaskEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as FunctionTaskEditor } from './FunctionTaskEditor'; diff --git a/src/app/Workflows/Pipelines/_components/TaskEditor/Tabs/GitTab/GitTab.module.scss b/src/app/Workflows/Pipelines/_components/TaskEditor/Tabs/GitTab/GitTab.module.scss new file mode 100644 index 000000000..d0ae65f2c --- /dev/null +++ b/src/app/Workflows/Pipelines/_components/TaskEditor/Tabs/GitTab/GitTab.module.scss @@ -0,0 +1,7 @@ +.form { + width: 100%; + padding: 16px; + display: grid; + gap: 16px; + } + \ No newline at end of file diff --git a/src/app/Workflows/Pipelines/_components/TaskEditor/Tabs/GitTab/GitTab.tsx b/src/app/Workflows/Pipelines/_components/TaskEditor/Tabs/GitTab/GitTab.tsx index c64323915..7da92ee90 100644 --- a/src/app/Workflows/Pipelines/_components/TaskEditor/Tabs/GitTab/GitTab.tsx +++ b/src/app/Workflows/Pipelines/_components/TaskEditor/Tabs/GitTab/GitTab.tsx @@ -1,6 +1,12 @@ import { usePatchTask } from 'app/Workflows/_hooks'; import { Workflows } from '@tapis/tapis-typescript'; import { Sidebar } from '../../../Sidebar'; +import { useEffect, useRef, useState } from 'react'; +import styles from "./GitTab.module.scss" +import { FormControl, FormHelperText, InputLabel, Input, InputAdornment, Button, Select, MenuItem, Alert, AlertTitle, Box, Stepper, Step, StepLabel } from '@mui/material'; +import { Search } from '@mui/icons-material'; +import { listRepos, listBranches } from 'app/apis/Github'; + const GitTab: React.FC<{ toggle: () => void }> = ({ toggle }) => { const { @@ -11,12 +17,178 @@ const GitTab: React.FC<{ toggle: () => void }> = ({ toggle }) => { isLoading, isError, isSuccess, - error, + // error, reset, } = usePatchTask(); + + const [username, setUsername] = useState(undefined) + const [repos, setRepos] = useState>([]) + const [repo, setRepo] = useState(undefined) + const [branches, setBranches] = useState | undefined>(undefined) + const [branch, setBranch] = useState(undefined) + const [error, setError] = useState(undefined) + const searchRef = useRef(null) + + useEffect(() => { + if (username) { + listRepos({username}, { + onSuccess: (response) => {setRepos(response.result!)}, + onError: (response) => {setError(response.error), setUsername(undefined)} + }) + } + }, [username]) + + useEffect(() => { + if (username && repo) { + listBranches({username, repo}, { + onSuccess: (response) => {setBranches(response.result!)}, + onError: (response) => {setError(response.error), setBranches(undefined)} + }) + } + }, [repo, branches]) + return ( - Coming soon + {error && ( + {setError(undefined)}} + > + Error + {error.message} + + )} +
+ <> + + + Repository owner + + + + + } + /> + + + The owner of the git repository you want to clone + + + { + repos.length > 0 && ( + <> + + + Repository + + + + + The git repository to clone into the task's execution directory + + + ) + } + { + repo && branches && ( + <> + + + Branch + + + + + The branch of the repoistory to clone + + + ) + } + { + branch && ( + <> + + + Clone directory + + + + + The repository will be cloned into this directory inside of the task's exection directory + + + ) + } + +
+
+ { + task.git_repositories!.map((repo) => { + return ( +
+

{repo.url}

+

{repo.branch}

+

{repo.directory}

+
+ ) + }) + } +
); }; diff --git a/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.module.scss b/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.module.scss index 544c93dd7..660aa0396 100644 --- a/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.module.scss +++ b/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.module.scss @@ -32,8 +32,8 @@ } .container { - width: 100%; - padding: 16px; + display: flex; + flex-direction: row; } .body-with-sidebar { diff --git a/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.old.tsx b/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.old.tsx deleted file mode 100644 index 35c076362..000000000 --- a/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.old.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useCallback } from 'react'; -import { Workflows } from '@tapis/tapis-typescript'; -import styles from './TaskEditor.module.scss'; -import { Close, ArrowBack } from '@mui/icons-material'; -import { FunctionTaskEditor } from './FunctionTaskEditor'; - -type TaskEditorProps = { - groupId: string; - pipelineId: string; - task: Workflows.Task; - tasks: Array; - toggle?: () => void; -}; - -const TaskEditor: React.FC = ({ - task, - tasks, - groupId, - pipelineId, - toggle, -}) => { - const renderTaskEditor = useCallback( - (task: Workflows.Task) => { - switch (task.type) { - case Workflows.EnumTaskType.Function: - return ( - - ); - case Workflows.EnumTaskType.Template: - return `${task.type} task editor unsupported`; - case Workflows.EnumTaskType.Request: - return `${task.type} task editor unsupported`; - case Workflows.EnumTaskType.ImageBuild: - return `${task.type} task editor unsupported`; - case Workflows.EnumTaskType.Application: - return `${task.type} task editor unsupported`; - case Workflows.EnumTaskType.TapisJob: - return `${task.type} task editor unsupported`; - case Workflows.EnumTaskType.TapisActor: - return `${task.type} task editor unsupported`; - } - }, - [task] - ); - - return ( -
- {toggle && ( -
- -

Task Editor

- -
- )} -
{renderTaskEditor(task)}
-
- ); -}; - -export default TaskEditor; diff --git a/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.tsx b/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.tsx index ca545e897..e2a1e9c92 100644 --- a/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.tsx +++ b/src/app/Workflows/Pipelines/_components/TaskEditor/TaskEditor.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { Workflows } from '@tapis/tapis-typescript'; -import styles from './FunctionTaskEditor/FunctionTaskEditor.module.scss'; +import styles from './TaskEditor.module.scss'; import { Delete, Update } from '@mui/icons-material'; import { LoadingButton as Button, TabContext, TabList } from '@mui/lab'; import { diff --git a/src/app/Workflows/_components/Menu/Menu.tsx b/src/app/Workflows/_components/Menu/Menu.tsx index 3fdd83847..d61647c7e 100644 --- a/src/app/Workflows/_components/Menu/Menu.tsx +++ b/src/app/Workflows/_components/Menu/Menu.tsx @@ -1,49 +1,72 @@ -import React from 'react'; +import React, {useState} from 'react'; import { useHistory } from 'react-router-dom'; import Stack from '@mui/material/Stack'; -import { Button } from '@mui/material'; +import { + ListItemText, + Drawer, + Box, + List, + ListItem, + ListItemButton, + ListItemIcon +} from '@mui/material'; +import { Menu as MenuIcon, Groups, SpaceDashboard, AccountTree, Backup } from '@mui/icons-material'; const Menu: React.FC = () => { - const history = useHistory(); + const history = useHistory() + const [open, setOpen] = useState(false) + const toggle = () => {setOpen(!open)} + return ( - - - - - - +
+ {setOpen(!open)}} style={{cursor: "pointer"}}/> + + + + + {history.push("/workflows")}}> + + + + + + + + {history.push("/workflows/groups")}}> + + + + + + + + {history.push("/workflows/pipelines")}}> + + + + + + + + {history.push("/workflows/archives")}}> + + + + + + + + + +
); }; diff --git a/src/app/apis/Github/index.ts b/src/app/apis/Github/index.ts new file mode 100644 index 000000000..c9bb7203b --- /dev/null +++ b/src/app/apis/Github/index.ts @@ -0,0 +1,2 @@ +export { default as listRepos } from "./listRepos" +export { default as listBranches } from "./listBranches" \ No newline at end of file diff --git a/src/app/apis/Github/listBranches.ts b/src/app/apis/Github/listBranches.ts new file mode 100644 index 000000000..7bb5d51a6 --- /dev/null +++ b/src/app/apis/Github/listBranches.ts @@ -0,0 +1,11 @@ +import { type API } from "app/apis" +import { decoder, request } from "../utils" + +type ListBranchesResponse = Array<{[key: string | number]: any}> +type ListBranchesRequest = {username: string, repo: string} + +const listBranches: API = ({username, repo}, callbacks) => { + decoder(() => request(`https://api.github.com/repos/${username}/${repo}/branches`), callbacks) +} + +export default listBranches \ No newline at end of file diff --git a/src/app/apis/Github/listRepos.ts b/src/app/apis/Github/listRepos.ts new file mode 100644 index 000000000..36d71eaf2 --- /dev/null +++ b/src/app/apis/Github/listRepos.ts @@ -0,0 +1,11 @@ +import { type API } from "app/apis" +import { decoder, request } from "../utils" + +type ListReposResponse = Array<{[key: string | number]: any}> +type ListReposRequest = {username: string} + +const listRepos: API = ({username}, callbacks) => { + decoder(() => request(`https://api.github.com/users/${username}/repos`), callbacks) +} + +export default listRepos \ No newline at end of file diff --git a/src/app/apis/index.ts b/src/app/apis/index.ts new file mode 100644 index 000000000..3508d61a8 --- /dev/null +++ b/src/app/apis/index.ts @@ -0,0 +1,9 @@ +export * as Github from "./Github" + +export type APIResponse = { result: Resp | undefined, error: Error | undefined} + +export type Callbacks = { + onSuccess?: (response: APIResponse) => void + onError?: (response: APIResponse) => void +} | undefined +export type API = (params: Req, callbacks?: Callbacks) => void \ No newline at end of file diff --git a/src/app/apis/utils/decoder.ts b/src/app/apis/utils/decoder.ts new file mode 100644 index 000000000..9c552e837 --- /dev/null +++ b/src/app/apis/utils/decoder.ts @@ -0,0 +1,20 @@ +import { request } from "app/apis/utils" +import { type Callbacks } from "app/apis" + +export const decoder = async ( + fn: () => ReturnType, + callbacks: Callbacks +) => { + fn() + .then(res => res.json()) + .then((res: T) => { + const success = res.status === undefined ? true : false + if (success && callbacks?.onSuccess) { + callbacks.onSuccess({result: res, error: undefined}) + } + + if (!success && callbacks?.onError) { + callbacks.onError({result: undefined, error: new Error(res.message)}) + } + }) +} \ No newline at end of file diff --git a/src/app/apis/utils/index.ts b/src/app/apis/utils/index.ts new file mode 100644 index 000000000..1a7911b86 --- /dev/null +++ b/src/app/apis/utils/index.ts @@ -0,0 +1,2 @@ +export { decoder } from "./decoder" +export { request } from "./request" \ No newline at end of file diff --git a/src/app/apis/utils/request.ts b/src/app/apis/utils/request.ts new file mode 100644 index 000000000..e5e8abe44 --- /dev/null +++ b/src/app/apis/utils/request.ts @@ -0,0 +1,3 @@ +export const request = (requestUrl: string) => { + return fetch(requestUrl) +} \ No newline at end of file