diff --git a/.gitignore b/.gitignore index a975576e0..a85dbc65f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,6 @@ app/public/styles/*.css venv/ # Frontend -.vscode/ .awcache/ .dist/ npm-debug.log diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..466edfad1 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,13 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "eamodio.gitlens" + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [ + + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6267a5031 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "[html]": { + "editor.tabSize": 4 + }, + "[javascript][javascriptreact][typescript][typescriptreact]": { + "editor.detectIndentation": false, + "editor.tabSize": 2 + }, +} \ No newline at end of file diff --git a/src/components/Executions/Tables/nodeExecutionColumns.tsx b/src/components/Executions/Tables/nodeExecutionColumns.tsx index 2b093d864..d1e976443 100644 --- a/src/components/Executions/Tables/nodeExecutionColumns.tsx +++ b/src/components/Executions/Tables/nodeExecutionColumns.tsx @@ -1,4 +1,4 @@ -import { Typography } from '@material-ui/core'; +import { Tooltip, Typography } from '@material-ui/core'; import { formatDateLocalTimezone, formatDateUTC, millisecondsToHMS } from 'common/formatters'; import { timestampToDate } from 'common/utils'; import { useCommonStyles } from 'components/common/styles'; @@ -63,6 +63,7 @@ const ExecutionName: React.FC = ({ execution, sta }; const DisplayId: React.FC = ({ execution }) => { + const commonStyles = useCommonStyles(); const detailsContext = useNodeExecutionContext(); const [displayId, setDisplayId] = React.useState(); @@ -78,7 +79,12 @@ const DisplayId: React.FC = ({ execution }) => { }; }); - return <>{displayId ?? execution.id.nodeId}; + const nodeId = displayId ?? execution.id.nodeId; + return ( + +
{nodeId}
+
+ ); }; const DisplayType: React.FC = ({ execution }) => { diff --git a/src/components/Executions/TaskExecutionDetails/TaskExecutionDetails.tsx b/src/components/Executions/TaskExecutionDetails/TaskExecutionDetails.tsx deleted file mode 100644 index 9468a577a..000000000 --- a/src/components/Executions/TaskExecutionDetails/TaskExecutionDetails.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { LargeLoadingSpinner } from 'components/common/LoadingSpinner'; -import { WaitForQuery } from 'components/common/WaitForQuery'; -import { withRouteParams } from 'components/common/withRouteParams'; -import { DataError } from 'components/Errors/DataError'; -import { useConditionalQuery } from 'components/hooks/useConditionalQuery'; -import { TaskExecution, TaskExecutionIdentifier } from 'models/Execution/types'; -import * as React from 'react'; -import { executionRefreshIntervalMs } from '../constants'; -import { makeTaskExecutionQuery } from '../taskExecutionQueries'; -import { taskExecutionIsTerminal } from '../utils'; -import { TaskExecutionDetailsAppBarContent } from './TaskExecutionDetailsAppBarContent'; -import { TaskExecutionNodes } from './TaskExecutionNodes'; - -export interface TaskExecutionDetailsRouteParams { - domainId: string; - executionName: string; - nodeId: string; - projectId: string; - retryAttempt: string; - taskDomain: string; - taskName: string; - taskProject: string; - taskVersion: string; -} -export type TaskExecutionDetailsProps = TaskExecutionDetailsRouteParams; - -function routeParamsToTaskExecutionId( - params: TaskExecutionDetailsRouteParams -): TaskExecutionIdentifier { - let retryAttempt = Number.parseInt(params.retryAttempt, 10); - if (Number.isNaN(retryAttempt)) { - retryAttempt = 0; - } - - return { - retryAttempt, - nodeExecutionId: { - executionId: { - domain: params.domainId, - name: params.executionName, - project: params.projectId - }, - nodeId: params.nodeId - }, - taskId: { - domain: params.taskDomain, - name: params.taskName, - project: params.taskProject, - version: params.taskVersion - } - }; -} - -export const TaskExecutionDetailsContainer: React.FC = props => { - const taskExecutionId = routeParamsToTaskExecutionId(props); - const taskExecutionQuery = useConditionalQuery( - { - ...makeTaskExecutionQuery(taskExecutionId), - refetchInterval: executionRefreshIntervalMs - }, - execution => !taskExecutionIsTerminal(execution) - ); - - const renderContent = (taskExecution: TaskExecution) => ( - <> - - - - ); - - return ( - - {renderContent} - - ); -}; - -export const TaskExecutionDetails = withRouteParams< - TaskExecutionDetailsRouteParams ->(TaskExecutionDetailsContainer); diff --git a/src/components/Executions/TaskExecutionDetails/TaskExecutionDetailsAppBarContent.tsx b/src/components/Executions/TaskExecutionDetails/TaskExecutionDetailsAppBarContent.tsx deleted file mode 100644 index 42a6c742e..000000000 --- a/src/components/Executions/TaskExecutionDetails/TaskExecutionDetailsAppBarContent.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { Typography } from '@material-ui/core'; -import { makeStyles, Theme } from '@material-ui/core/styles'; -import ArrowBack from '@material-ui/icons/ArrowBack'; -import classnames from 'classnames'; -import { formatDateUTC, protobufDurationToHMS } from 'common/formatters'; -import { timestampToDate } from 'common/utils'; -import { useCommonStyles } from 'components/common/styles'; -import { NavBarContent } from 'components/Navigation/NavBarContent'; -import { smallFontSize } from 'components/Theme/constants'; -import { TaskExecution } from 'models/Execution/types'; -import * as React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import { Routes } from 'routes/routes'; -import { ExecutionStatusBadge } from '../ExecutionStatusBadge'; - -const useStyles = makeStyles((theme: Theme) => { - const actionsMinWidth = theme.spacing(34); - const badgeWidth = theme.spacing(11); - const maxDetailsWidth = `calc(100% - ${actionsMinWidth + badgeWidth}px)`; - return { - actions: { - alignItems: 'center', - display: 'flex', - justifyContent: 'flex-end', - flex: '1 0 auto', - height: '100%', - marginLeft: theme.spacing(2) - }, - backLink: { - color: 'inherit', - marginRight: theme.spacing(1) - }, - container: { - alignItems: 'center', - display: 'flex', - flex: '1 1 auto', - maxWidth: '100%' - }, - detailsContainer: { - alignItems: 'center', - display: 'flex', - flex: '0 1 auto', - justifyContent: 'space-between', - maxWidth: maxDetailsWidth - }, - detailItem: { - flexShrink: 0, - marginLeft: theme.spacing(2) - }, - detailLabel: { - fontSize: smallFontSize, - lineHeight: 1.25 - }, - detailValue: { - fontSize: '0.875rem', - fontWeight: 'bold', - lineHeight: '1.1875rem' - }, - actionButton: { - marginLeft: theme.spacing(2) - }, - title: { - flex: '0 1 auto', - overflow: 'hidden' - }, - version: { - flex: '0 1 auto', - overflow: 'hidden' - } - }; -}); - -interface DetailItem { - className?: string; - label: React.ReactNode; - value: React.ReactNode; -} - -/** Renders information about a given TaskExecution into the NavBar */ -export const TaskExecutionDetailsAppBarContent: React.FC<{ - taskExecution: TaskExecution; -}> = ({ taskExecution }) => { - const commonStyles = useCommonStyles(); - const styles = useStyles(); - - const { - nodeExecutionId: { executionId }, - taskId: { project, domain, name, version }, - retryAttempt - } = taskExecution.id; - const { duration, startedAt, phase } = taskExecution.closure; - const details: DetailItem[] = [ - { - className: styles.title, - label: `${executionId.project}/${executionId.name}`, - value: `${project}/${domain}/${name} (${retryAttempt})` - }, - { label: 'Domain', value: executionId.domain }, - { - className: styles.version, - label: 'Version', - value: version - }, - { - label: 'Time', - value: startedAt ? formatDateUTC(timestampToDate(startedAt)) : '' - }, - { - label: 'Duration', - value: duration ? protobufDurationToHMS(duration) : '' - } - ]; - - return ( - <> - -
- - - - -
- {details.map(({ className, label, value }, idx) => ( -
- - {label} - -
- {value} -
-
- ))} -
-
-
- {/* {modalContent} */} - - ); -}; diff --git a/src/components/Executions/TaskExecutionDetails/TaskExecutionNodes.tsx b/src/components/Executions/TaskExecutionDetails/TaskExecutionNodes.tsx deleted file mode 100644 index 414f182ca..000000000 --- a/src/components/Executions/TaskExecutionDetails/TaskExecutionNodes.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Tab, Tabs } from '@material-ui/core'; -import { makeStyles, Theme } from '@material-ui/core/styles'; -import { WaitForQuery } from 'components/common/WaitForQuery'; -import { DataError } from 'components/Errors/DataError'; -import { useConditionalQuery } from 'components/hooks/useConditionalQuery'; -import { useTabState } from 'components/hooks/useTabState'; -import { every } from 'lodash'; -import { limits } from 'models/AdminEntity/constants'; -import { SortDirection } from 'models/AdminEntity/types'; -import { executionSortFields } from 'models/Execution/constants'; -import { NodeExecution, TaskExecution } from 'models/Execution/types'; -import * as React from 'react'; -import { useQueryClient } from 'react-query'; -import { executionRefreshIntervalMs } from '../constants'; -import { NodeExecutionsRequestConfigContext } from '../contexts'; -import { ExecutionFilters } from '../ExecutionFilters'; -import { useNodeExecutionFiltersState } from '../filters/useExecutionFiltersState'; -import { makeTaskExecutionChildListQuery } from '../nodeExecutionQueries'; -import { NodeExecutionsTable } from '../Tables/NodeExecutionsTable'; -import { nodeExecutionIsTerminal, taskExecutionIsTerminal } from '../utils'; - -const useStyles = makeStyles((theme: Theme) => ({ - filters: { - paddingLeft: theme.spacing(3) - }, - nodesContainer: { - borderTop: `1px solid ${theme.palette.divider}`, - display: 'flex', - flex: '1 1 100%', - flexDirection: 'column' - }, - tabs: { - paddingLeft: theme.spacing(3.5) - } -})); - -interface TaskExecutionNodesProps { - taskExecution: TaskExecution; -} - -const tabIds = { - nodes: 'nodes' -}; - -/** Contains the content for viewing child NodeExecutions for a TaskExecution */ -export const TaskExecutionNodes: React.FC = ({ - taskExecution -}) => { - const styles = useStyles(); - const filterState = useNodeExecutionFiltersState(); - const tabState = useTabState(tabIds, tabIds.nodes); - - const requestConfig = React.useMemo( - () => ({ - filter: filterState.appliedFilters, - limit: limits.NONE, - sort: { - key: executionSortFields.createdAt, - direction: SortDirection.ASCENDING - } - }), - [filterState.appliedFilters] - ); - - const shouldEnableQuery = (executions: NodeExecution[]) => - !every(executions, nodeExecutionIsTerminal) || - !taskExecutionIsTerminal(taskExecution); - - const nodeExecutionsQuery = useConditionalQuery( - { - ...makeTaskExecutionChildListQuery( - useQueryClient(), - taskExecution.id, - requestConfig - ), - refetchInterval: executionRefreshIntervalMs - }, - shouldEnableQuery - ); - - const renderNodeExecutionsTable = (nodeExecutions: NodeExecution[]) => ( - - {/* TODO: legacy code - looks like it's never called - If code still in use NodeExecutionsTable should be wrapped by - NodeExecutionDetailsContextProvider here or in one of the parent components. - */} - - - ); - - return ( - <> - - - -
- {tabState.value === tabIds.nodes && ( - <> -
- -
- - {renderNodeExecutionsTable} - - - )} -
- - ); -}; diff --git a/src/components/Executions/TaskExecutionsList/TaskExecutionsListItem.tsx b/src/components/Executions/TaskExecutionsList/TaskExecutionsListItem.tsx index 24d087913..4f1b3e23d 100644 --- a/src/components/Executions/TaskExecutionsList/TaskExecutionsListItem.tsx +++ b/src/components/Executions/TaskExecutionsList/TaskExecutionsListItem.tsx @@ -1,12 +1,10 @@ +import * as React from 'react'; import { makeStyles, Theme } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import classnames from 'classnames'; import { useCommonStyles } from 'components/common/styles'; import { TaskExecutionPhase } from 'models/Execution/enums'; import { TaskExecution } from 'models/Execution/types'; -import * as React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import { Routes } from 'routes/routes'; import { ExecutionStatusBadge } from '../ExecutionStatusBadge'; import { TaskExecutionDetails } from './TaskExecutionDetails'; import { TaskExecutionError } from './TaskExecutionError'; @@ -14,89 +12,63 @@ import { TaskExecutionLogs } from './TaskExecutionLogs'; import { formatRetryAttempt } from './utils'; const useStyles = makeStyles((theme: Theme) => ({ - detailsLink: { - fontWeight: 'normal' - }, - header: { - marginBottom: theme.spacing(1) - }, - title: { - marginBottom: theme.spacing(1) - }, - showDetailsButton: { - marginTop: theme.spacing(1) - }, - section: { - marginBottom: theme.spacing(2) - } + detailsLink: { + fontWeight: 'normal' + }, + header: { + marginBottom: theme.spacing(1) + }, + title: { + marginBottom: theme.spacing(1) + }, + showDetailsButton: { + marginTop: theme.spacing(1) + }, + section: { + marginBottom: theme.spacing(2) + } })); interface TaskExecutionsListItemProps { - taskExecution: TaskExecution; + taskExecution: TaskExecution; } /** Renders an individual `TaskExecution` record as part of a list */ -export const TaskExecutionsListItem: React.FC = ({ - taskExecution -}) => { - const commonStyles = useCommonStyles(); - const styles = useStyles(); - const { closure, isParent } = taskExecution; - const { error } = closure; - const headerText = formatRetryAttempt(taskExecution.id.retryAttempt); - const taskHasStarted = closure.phase >= TaskExecutionPhase.QUEUED; - return ( -
-
-
-
- - {headerText} - -
- -
- {!!error && ( -
- -
- )} - {taskHasStarted && ( - <> -
- -
-
- -
- - )} - {isParent ? ( - - View Children - - ) : null} -
-
- ); +export const TaskExecutionsListItem: React.FC = ({ taskExecution }) => { + const commonStyles = useCommonStyles(); + const styles = useStyles(); + const { closure } = taskExecution; + const { error } = closure; + const headerText = formatRetryAttempt(taskExecution.id.retryAttempt); + const taskHasStarted = closure.phase >= TaskExecutionPhase.QUEUED; + + return ( +
+
+
+
+ + {headerText} + +
+ +
+ {!!error && ( +
+ +
+ )} + {taskHasStarted && ( + <> +
+ +
+
+ +
+ + )} +
+
+ ); }; diff --git a/src/routes/ApplicationRouter.tsx b/src/routes/ApplicationRouter.tsx index 052e79629..658572ab8 100644 --- a/src/routes/ApplicationRouter.tsx +++ b/src/routes/ApplicationRouter.tsx @@ -1,7 +1,4 @@ -import { - ContentContainer, - ContentContainerProps -} from 'components/common/ContentContainer'; +import { ContentContainer, ContentContainerProps } from 'components/common/ContentContainer'; import { withSideNavigation } from 'components/Navigation/withSideNavigation'; import * as React from 'react'; import { Route, Switch } from 'react-router-dom'; @@ -9,63 +6,40 @@ import { components } from './components'; import { Routes } from './routes'; function withContentContainer

( - WrappedComponent: React.ComponentType

, - contentContainerProps?: ContentContainerProps + WrappedComponent: React.ComponentType

, + contentContainerProps?: ContentContainerProps ) { - return (props: P) => ( - - - - ); + return (props: P) => ( + + + + ); } export const ApplicationRouter: React.FC = () => ( - <> - - - - - - - - - - - + <> + + + + + + + + + + ); diff --git a/src/routes/NavBarRouter.tsx b/src/routes/NavBarRouter.tsx index 6f11aae34..4b820e695 100644 --- a/src/routes/NavBarRouter.tsx +++ b/src/routes/NavBarRouter.tsx @@ -7,17 +7,10 @@ const CustomNavBar = () => ; /** Handles the routing for content displayed in the NavBar */ export const NavBarRouter: React.FC<{}> = () => ( - <> - - - - - - + <> + + + + + ); diff --git a/src/routes/components.ts b/src/routes/components.ts index e2c5d7910..edd721d95 100644 --- a/src/routes/components.ts +++ b/src/routes/components.ts @@ -1,5 +1,4 @@ import { ExecutionDetails } from 'components/Executions/ExecutionDetails/ExecutionDetails'; -import { TaskExecutionDetails } from 'components/Executions/TaskExecutionDetails/TaskExecutionDetails'; import { NotFound } from 'components/NotFound/NotFound'; import { ProjectDetails } from 'components/Project/ProjectDetails'; import { SelectProject } from 'components/SelectProject/SelectProject'; @@ -11,12 +10,11 @@ import { WorkflowVersionDetails } from '../components/Workflow/WorkflowVersionDe * in components which include the Routes dictionary */ export const components = { - executionDetails: ExecutionDetails, - notFound: NotFound, - projectDetails: ProjectDetails, - selectProject: SelectProject, - taskExecutionDetails: TaskExecutionDetails, - taskDetails: TaskDetails, - workflowDetails: WorkflowDetails, - workflowVersionDetails: WorkflowVersionDetails + executionDetails: ExecutionDetails, + notFound: NotFound, + projectDetails: ProjectDetails, + selectProject: SelectProject, + taskDetails: TaskDetails, + workflowDetails: WorkflowDetails, + workflowVersionDetails: WorkflowVersionDetails }; diff --git a/src/routes/routes.ts b/src/routes/routes.ts index 6135cf9e6..de9f804b7 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -1,146 +1,83 @@ import { ensureSlashPrefixed } from 'common/utils'; -import { - TaskExecutionIdentifier, - WorkflowExecutionIdentifier -} from 'models/Execution/types'; -import { - projectBasePath, - projectDomainBasePath, - taskExecutionPath -} from './constants'; +import { TaskExecutionIdentifier, WorkflowExecutionIdentifier } from 'models/Execution/types'; +import { projectBasePath, projectDomainBasePath, taskExecutionPath } from './constants'; import { makeRoute } from './utils'; /** Creates a path relative to a particular project */ export const makeProjectBoundPath = (projectId: string, path = '') => - makeRoute( - `/projects/${projectId}${ - path.length ? ensureSlashPrefixed(path) : path - }` - ); + makeRoute(`/projects/${projectId}${path.length ? ensureSlashPrefixed(path) : path}`); /** Creates a path relative to a particular project and domain. Paths should begin with a slash (/) */ -export const makeProjectDomainBoundPath = ( - projectId: string, - domainId: string, - path = '' -) => makeRoute(`/projects/${projectId}/domains/${domainId}${path}`); +export const makeProjectDomainBoundPath = (projectId: string, domainId: string, path = '') => + makeRoute(`/projects/${projectId}/domains/${domainId}${path}`); export class Routes { - static NotFound = {}; - // Projects - static ProjectDetails = { - makeUrl: (project: string, section?: string) => - makeProjectBoundPath(project, section ? `/${section}` : ''), - path: projectBasePath, - sections: { - executions: { - makeUrl: (project: string, domain?: string) => - makeProjectBoundPath( - project, - `/executions${domain ? `?domain=${domain}` : ''}` - ), - path: `${projectBasePath}/executions` - }, - tasks: { - makeUrl: (project: string, domain?: string) => - makeProjectBoundPath( - project, - `/tasks${domain ? `?domain=${domain}` : ''}` - ), - path: `${projectBasePath}/tasks` - }, - workflows: { - makeUrl: (project: string, domain?: string) => - makeProjectBoundPath( - project, - `/workflows${domain ? `?domain=${domain}` : ''}` - ), - path: `${projectBasePath}/workflows` - } - } - }; - static ProjectExecutions = { - makeUrl: (project: string, domain: string) => - makeProjectDomainBoundPath(project, domain, '/executions'), - path: `${projectDomainBasePath}/executions` - }; - static ProjectTasks = { - makeUrl: (project: string, domain: string) => - makeProjectDomainBoundPath(project, domain, '/tasks'), - path: `${projectDomainBasePath}/tasks` - }; - static ProjectWorkflows = { - makeUrl: (project: string, domain: string) => - makeProjectDomainBoundPath(project, domain, '/workflows'), - path: `${projectDomainBasePath}/workflows` - }; + static NotFound = {}; + // Projects + static ProjectDetails = { + makeUrl: (project: string, section?: string) => makeProjectBoundPath(project, section ? `/${section}` : ''), + path: projectBasePath, + sections: { + executions: { + makeUrl: (project: string, domain?: string) => + makeProjectBoundPath(project, `/executions${domain ? `?domain=${domain}` : ''}`), + path: `${projectBasePath}/executions` + }, + tasks: { + makeUrl: (project: string, domain?: string) => + makeProjectBoundPath(project, `/tasks${domain ? `?domain=${domain}` : ''}`), + path: `${projectBasePath}/tasks` + }, + workflows: { + makeUrl: (project: string, domain?: string) => + makeProjectBoundPath(project, `/workflows${domain ? `?domain=${domain}` : ''}`), + path: `${projectBasePath}/workflows` + } + } + }; + static ProjectExecutions = { + makeUrl: (project: string, domain: string) => makeProjectDomainBoundPath(project, domain, '/executions'), + path: `${projectDomainBasePath}/executions` + }; + static ProjectTasks = { + makeUrl: (project: string, domain: string) => makeProjectDomainBoundPath(project, domain, '/tasks'), + path: `${projectDomainBasePath}/tasks` + }; + static ProjectWorkflows = { + makeUrl: (project: string, domain: string) => makeProjectDomainBoundPath(project, domain, '/workflows'), + path: `${projectDomainBasePath}/workflows` + }; - // Workflows - static WorkflowDetails = { - makeUrl: (project: string, domain: string, workflowName: string) => - makeProjectDomainBoundPath( - project, - domain, - `/workflows/${workflowName}` - ), - path: `${projectDomainBasePath}/workflows/:workflowName` - }; + // Workflows + static WorkflowDetails = { + makeUrl: (project: string, domain: string, workflowName: string) => + makeProjectDomainBoundPath(project, domain, `/workflows/${workflowName}`), + path: `${projectDomainBasePath}/workflows/:workflowName` + }; - // Workflow Version Details - static WorkflowVersionDetails = { - makeUrl: ( - project: string, - domain: string, - workflowName: string, - version: string - ) => - makeProjectDomainBoundPath( - project, - domain, - `/workflows/${workflowName}/version/${version}` - ), - path: `${projectDomainBasePath}/workflows/:workflowName/version/:workflowVersion` - }; + // Workflow Version Details + static WorkflowVersionDetails = { + makeUrl: (project: string, domain: string, workflowName: string, version: string) => + makeProjectDomainBoundPath(project, domain, `/workflows/${workflowName}/version/${version}`), + path: `${projectDomainBasePath}/workflows/:workflowName/version/:workflowVersion` + }; - // Tasks - static TaskDetails = { - makeUrl: (project: string, domain: string, taskName: string) => - makeProjectDomainBoundPath(project, domain, `/tasks/${taskName}`), - path: `${projectDomainBasePath}/tasks/:taskName` - }; + // Tasks + static TaskDetails = { + makeUrl: (project: string, domain: string, taskName: string) => + makeProjectDomainBoundPath(project, domain, `/tasks/${taskName}`), + path: `${projectDomainBasePath}/tasks/:taskName` + }; - // Executions - static ExecutionDetails = { - makeUrl: ({ domain, name, project }: WorkflowExecutionIdentifier) => - makeProjectDomainBoundPath(project, domain, `/executions/${name}`), - path: `${projectDomainBasePath}/executions/:executionId` - }; - static TaskExecutionDetails = { - makeUrl: (taskExecutionId: TaskExecutionIdentifier) => { - const { - nodeExecutionId: { - executionId: { project, domain, name: executionName }, - nodeId - }, - taskId: { - project: taskProject, - domain: taskDomain, - name: taskName, - version: taskVersion - }, - retryAttempt - } = taskExecutionId; - return makeProjectDomainBoundPath( - project, - domain, - `/task_executions/${executionName}/${nodeId}/${taskProject}/${taskDomain}/${taskName}/${taskVersion}/${retryAttempt}` - ); - }, - path: taskExecutionPath - }; + // Executions + static ExecutionDetails = { + makeUrl: ({ domain, name, project }: WorkflowExecutionIdentifier) => + makeProjectDomainBoundPath(project, domain, `/executions/${name}`), + path: `${projectDomainBasePath}/executions/:executionId` + }; - // Landing page - static SelectProject = { - path: makeRoute('/') - }; + // Landing page + static SelectProject = { + path: makeRoute('/') + }; }