mirror of
https://github.com/harness/drone.git
synced 2025-05-04 23:40:24 +08:00
pipeline console WIP
This commit is contained in:
parent
6ca2a6924d
commit
15a4fe04bd
32
web/src/components/Console/Console.module.scss
Normal file
32
web/src/components/Console/Console.module.scss
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: black;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
color: white;
|
||||||
|
font-family: Inconsolata, monospace;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: var(--black);
|
||||||
|
height: var(--log-content-header-height);
|
||||||
|
|
||||||
|
.headerLayout {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
border-bottom: 1px solid var(--grey-800);
|
||||||
|
padding: var(--spacing-medium) 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps {
|
||||||
|
padding: var(--spacing-medium) !important;
|
||||||
|
}
|
10
web/src/components/Console/Console.module.scss.d.ts
vendored
Normal file
10
web/src/components/Console/Console.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// this is an auto-generated file
|
||||||
|
declare const styles: {
|
||||||
|
readonly container: string
|
||||||
|
readonly log: string
|
||||||
|
readonly header: string
|
||||||
|
readonly headerLayout: string
|
||||||
|
readonly steps: string
|
||||||
|
}
|
||||||
|
export default styles
|
48
web/src/components/Console/Console.tsx
Normal file
48
web/src/components/Console/Console.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
import { Container, Layout, Text } from '@harnessio/uicore'
|
||||||
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
|
import type { CODEProps } from 'RouteDefinitions'
|
||||||
|
import type { TypesStage } from 'services/code'
|
||||||
|
import ConsoleStep from 'components/ConsoleStep/ConsoleStep'
|
||||||
|
import css from './Console.module.scss'
|
||||||
|
|
||||||
|
interface ConsoleProps {
|
||||||
|
stage: TypesStage | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const Console: FC<ConsoleProps> = ({ stage }) => {
|
||||||
|
const space = useGetSpaceParam()
|
||||||
|
const { pipeline, execution: executionNum } = useParams<CODEProps>()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={css.container}>
|
||||||
|
<Container className={css.header}>
|
||||||
|
<Layout.Horizontal className={css.headerLayout} spacing="small">
|
||||||
|
<Text font={{ variation: FontVariation.H4 }} color={Color.WHITE} padding={{ left: 'large', right: 'large' }}>
|
||||||
|
{stage?.name}
|
||||||
|
</Text>
|
||||||
|
<Text font={{ variation: FontVariation.BODY }} color={Color.GREY_500}>
|
||||||
|
{/* this needs fixed */}
|
||||||
|
Success in 5 mins
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
<Layout.Vertical className={css.steps} spacing="small">
|
||||||
|
{stage?.steps?.map((step, index) => (
|
||||||
|
<ConsoleStep
|
||||||
|
key={index}
|
||||||
|
step={step}
|
||||||
|
executionNumber={Number(executionNum)}
|
||||||
|
pipelineName={pipeline}
|
||||||
|
spaceName={space}
|
||||||
|
stageNumber={stage.number}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Layout.Vertical>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Console
|
14
web/src/components/ConsoleLogs/ConsoleLogs.module.scss
Normal file
14
web/src/components/ConsoleLogs/ConsoleLogs.module.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.logLayout {
|
||||||
|
margin-left: 2.3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineNumber {
|
||||||
|
width: 1.5rem;
|
||||||
|
color: #999;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
color: white !important;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
8
web/src/components/ConsoleLogs/ConsoleLogs.module.scss.d.ts
vendored
Normal file
8
web/src/components/ConsoleLogs/ConsoleLogs.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// this is an auto-generated file
|
||||||
|
declare const styles: {
|
||||||
|
readonly logLayout: string
|
||||||
|
readonly lineNumber: string
|
||||||
|
readonly log: string
|
||||||
|
}
|
||||||
|
export default styles
|
36
web/src/components/ConsoleLogs/ConsoleLogs.tsx
Normal file
36
web/src/components/ConsoleLogs/ConsoleLogs.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Layout, Text } from '@harnessio/uicore'
|
||||||
|
import React, { FC } from 'react'
|
||||||
|
import css from './ConsoleLogs.module.scss'
|
||||||
|
|
||||||
|
// currently a string - should be an array of strings in future
|
||||||
|
interface ConsoleLogsProps {
|
||||||
|
logs: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface log {
|
||||||
|
pos: number
|
||||||
|
out: string
|
||||||
|
time: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertStringToLogArray = (logs: string): log[] => {
|
||||||
|
const logStrings = logs.split('\n').map(log => {
|
||||||
|
return JSON.parse(log)
|
||||||
|
})
|
||||||
|
|
||||||
|
return logStrings
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConsoleLogs: FC<ConsoleLogsProps> = ({ logs }) => {
|
||||||
|
const logArray = convertStringToLogArray(logs)
|
||||||
|
return logArray.map((log, index) => {
|
||||||
|
return (
|
||||||
|
<Layout.Horizontal key={index} spacing={'medium'} className={css.logLayout}>
|
||||||
|
<Text className={css.lineNumber}>{log.pos}</Text>
|
||||||
|
<Text className={css.log}>{log.out}</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConsoleLogs
|
5
web/src/components/ConsoleStep/ConsoleStep.module.scss
Normal file
5
web/src/components/ConsoleStep/ConsoleStep.module.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.stepLayout {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
6
web/src/components/ConsoleStep/ConsoleStep.module.scss.d.ts
vendored
Normal file
6
web/src/components/ConsoleStep/ConsoleStep.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// this is an auto-generated file
|
||||||
|
declare const styles: {
|
||||||
|
readonly stepLayout: string
|
||||||
|
}
|
||||||
|
export default styles
|
64
web/src/components/ConsoleStep/ConsoleStep.tsx
Normal file
64
web/src/components/ConsoleStep/ConsoleStep.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { Icon } from '@harnessio/icons'
|
||||||
|
import { FlexExpander, Layout } from '@harnessio/uicore'
|
||||||
|
import React, { FC, useEffect } from 'react'
|
||||||
|
import { useGet } from 'restful-react'
|
||||||
|
import { Text } from '@harnessio/uicore'
|
||||||
|
import type { TypesStep } from 'services/code'
|
||||||
|
import { timeDistance } from 'utils/Utils'
|
||||||
|
import ConsoleLogs from 'components/ConsoleLogs/ConsoleLogs'
|
||||||
|
import css from './ConsoleStep.module.scss'
|
||||||
|
|
||||||
|
interface ConsoleStepProps {
|
||||||
|
step: TypesStep | undefined
|
||||||
|
stageNumber: number | undefined
|
||||||
|
spaceName: string
|
||||||
|
pipelineName: string | undefined
|
||||||
|
executionNumber: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConsoleStep: FC<ConsoleStepProps> = ({ step, stageNumber, spaceName, pipelineName, executionNumber }) => {
|
||||||
|
const [isOpened, setIsOpened] = React.useState(false)
|
||||||
|
|
||||||
|
const { data, error, loading, refetch } = useGet<string>({
|
||||||
|
path: `/api/v1/pipelines/${spaceName}/${pipelineName}/+/executions/${executionNumber}/logs/${String(
|
||||||
|
stageNumber
|
||||||
|
)}/${String(step?.number)}`,
|
||||||
|
lazy: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// this refetches any open steps when the stage number changes - really it shouldnt refetch until reopened...
|
||||||
|
useEffect(() => {
|
||||||
|
setIsOpened(false)
|
||||||
|
refetch()
|
||||||
|
}, [stageNumber, refetch])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout.Horizontal
|
||||||
|
className={css.stepLayout}
|
||||||
|
spacing="medium"
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpened(!isOpened)
|
||||||
|
if (!data && !loading) refetch()
|
||||||
|
}}>
|
||||||
|
<Icon name={isOpened ? 'chevron-down' : 'chevron-right'} />
|
||||||
|
<Icon name={step?.status === 'Success' ? 'success-tick' : 'circle'} />
|
||||||
|
<Text>{step?.name}</Text>
|
||||||
|
<FlexExpander />
|
||||||
|
{step?.started && step?.stopped && <div>{timeDistance(step?.stopped, step?.started)}</div>}
|
||||||
|
</Layout.Horizontal>
|
||||||
|
|
||||||
|
{isOpened ? (
|
||||||
|
loading ? (
|
||||||
|
<div>Loading...</div>
|
||||||
|
) : error ? (
|
||||||
|
<div>Error: {error}</div>
|
||||||
|
) : data ? (
|
||||||
|
<ConsoleLogs logs={data} />
|
||||||
|
) : null
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConsoleStep
|
@ -0,0 +1,39 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
--stage-title-height: 54px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.menuItem {
|
||||||
|
margin: 0.5rem 0 0.5rem 1rem !important;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: var(--stage-title-height);
|
||||||
|
padding: 0 var(--spacing-medium) 0 var(--spacing-medium);
|
||||||
|
border-radius: 10px 0 0 10px !important;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: var(--primary-1);
|
||||||
|
|
||||||
|
.uid {
|
||||||
|
color: var(--primary-7) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uid {
|
||||||
|
color: var(--grey-700) !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
web/src/components/ExecutionStageList/ExecutionStageList.module.scss.d.ts
vendored
Normal file
11
web/src/components/ExecutionStageList/ExecutionStageList.module.scss.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// this is an auto-generated file
|
||||||
|
declare const styles: {
|
||||||
|
readonly container: string
|
||||||
|
readonly menu: string
|
||||||
|
readonly menuItem: string
|
||||||
|
readonly layout: string
|
||||||
|
readonly selected: string
|
||||||
|
readonly uid: string
|
||||||
|
}
|
||||||
|
export default styles
|
56
web/src/components/ExecutionStageList/ExecutionStageList.tsx
Normal file
56
web/src/components/ExecutionStageList/ExecutionStageList.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import React, { FC } from 'react'
|
||||||
|
import { Container, Layout, Text } from '@harnessio/uicore'
|
||||||
|
import { Icon } from '@harnessio/icons'
|
||||||
|
import cx from 'classnames'
|
||||||
|
import type { TypesStage } from 'services/code'
|
||||||
|
import css from './ExecutionStageList.module.scss'
|
||||||
|
|
||||||
|
interface ExecutionStageListProps {
|
||||||
|
stages: TypesStage[]
|
||||||
|
selectedStage: number | null
|
||||||
|
setSelectedStage: (selectedStep: number | null) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExecutionStageProps {
|
||||||
|
stage: TypesStage
|
||||||
|
isSelected?: boolean
|
||||||
|
selectedStage: number | null
|
||||||
|
setSelectedStage: (selectedStage: number | null) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExecutionStage: FC<ExecutionStageProps> = ({ stage, isSelected = false, setSelectedStage = () => {} }) => {
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
className={css.menuItem}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedStage(stage.number || null)
|
||||||
|
}}>
|
||||||
|
<Layout.Horizontal spacing="small" className={cx(css.layout, { [css.selected]: isSelected })}>
|
||||||
|
<Icon name="success-tick" size={16} />
|
||||||
|
<Text className={css.uid} lineClamp={1}>
|
||||||
|
{stage.name}
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExecutionStageList: FC<ExecutionStageListProps> = ({ stages, setSelectedStage, selectedStage }) => {
|
||||||
|
return (
|
||||||
|
<Container className={css.menu}>
|
||||||
|
{stages.map((stage, index) => {
|
||||||
|
return (
|
||||||
|
<ExecutionStage
|
||||||
|
key={index}
|
||||||
|
stage={stage}
|
||||||
|
isSelected={selectedStage === stage.number}
|
||||||
|
selectedStage={selectedStage}
|
||||||
|
setSelectedStage={setSelectedStage}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExecutionStageList
|
@ -1,4 +1,59 @@
|
|||||||
.main {
|
.main {
|
||||||
min-height: var(--page-height);
|
min-height: var(--page-height);
|
||||||
background-color: var(--primary-bg) !important;
|
background-color: var(--primary-bg) !important;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.Resizer {
|
||||||
|
background-color: var(--grey-300);
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Resizer:hover {
|
||||||
|
transition: all 2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Resizer.horizontal {
|
||||||
|
margin: -5px 0;
|
||||||
|
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||||
|
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||||
|
cursor: row-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Resizer.horizontal:hover {
|
||||||
|
border-top: 5px solid rgba(0, 0, 0, 0.5);
|
||||||
|
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Resizer.vertical {
|
||||||
|
width: 11px;
|
||||||
|
margin: 0 -5px;
|
||||||
|
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||||
|
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Resizer.vertical:hover {
|
||||||
|
border-left: 5px solid rgba(0, 0, 0, 0.5);
|
||||||
|
border-right: 5px solid rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Resizer.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Resizer.disabled:hover {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
height: calc(100vh - var(--page-header-height));
|
||||||
|
}
|
||||||
|
|
||||||
|
.withError {
|
||||||
|
display: grid;
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,7 @@
|
|||||||
// this is an auto-generated file
|
// this is an auto-generated file
|
||||||
declare const styles: {
|
declare const styles: {
|
||||||
readonly main: string
|
readonly main: string
|
||||||
|
readonly container: string
|
||||||
|
readonly withError: string
|
||||||
}
|
}
|
||||||
export default styles
|
export default styles
|
||||||
|
@ -1,29 +1,59 @@
|
|||||||
import React from 'react'
|
import { Container, PageHeader, PageBody } from '@harnessio/uicore'
|
||||||
import { Container, PageHeader } from '@harnessio/uicore'
|
import React, { useState } from 'react'
|
||||||
|
import cx from 'classnames'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { useGet } from 'restful-react'
|
import { useGet } from 'restful-react'
|
||||||
|
import SplitPane from 'react-split-pane'
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
import type { CODEProps } from 'RouteDefinitions'
|
import type { CODEProps } from 'RouteDefinitions'
|
||||||
import type { TypesExecution } from 'services/code'
|
import type { TypesExecution } from 'services/code'
|
||||||
|
import ExecutionStageList from 'components/ExecutionStageList/ExecutionStageList'
|
||||||
|
import Console from 'components/Console/Console'
|
||||||
|
import { getErrorMessage, voidFn } from 'utils/Utils'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
|
import noExecutionImage from '../RepositoriesListing/no-repo.svg'
|
||||||
import css from './Execution.module.scss'
|
import css from './Execution.module.scss'
|
||||||
|
|
||||||
const Execution = () => {
|
const Execution = () => {
|
||||||
const space = useGetSpaceParam()
|
const space = useGetSpaceParam()
|
||||||
const { pipeline, execution: executionNum } = useParams<CODEProps>()
|
const { pipeline, execution: executionNum } = useParams<CODEProps>()
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: execution
|
data: execution,
|
||||||
// error,
|
error,
|
||||||
// loading,
|
loading,
|
||||||
// refetch
|
refetch
|
||||||
// response
|
|
||||||
} = useGet<TypesExecution>({
|
} = useGet<TypesExecution>({
|
||||||
path: `/api/v1/pipelines/${space}/${pipeline}/+/executions/${executionNum}`
|
path: `/api/v1/pipelines/${space}/${pipeline}/+/executions/${executionNum}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [selectedStage, setSelectedStage] = useState<number | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={css.main}>
|
<Container className={css.main}>
|
||||||
<PageHeader title={`EXECUTION STATUS = ${execution?.status}`} />
|
<PageHeader title={execution?.title} />
|
||||||
|
<PageBody
|
||||||
|
className={cx({ [css.withError]: !!error })}
|
||||||
|
error={error ? getErrorMessage(error) : null}
|
||||||
|
retryOnError={voidFn(refetch)}
|
||||||
|
noData={{
|
||||||
|
when: () => !execution,
|
||||||
|
image: noExecutionImage,
|
||||||
|
message: getString('executions.noData')
|
||||||
|
// button: NewExecutionButton
|
||||||
|
}}>
|
||||||
|
<LoadingSpinner visible={loading} />
|
||||||
|
<SplitPane split="vertical" size={300} minSize={200} maxSize={400}>
|
||||||
|
<ExecutionStageList
|
||||||
|
stages={execution?.stages || []}
|
||||||
|
setSelectedStage={setSelectedStage}
|
||||||
|
selectedStage={selectedStage}
|
||||||
|
/>
|
||||||
|
{selectedStage && <Console stage={execution?.stages?.[selectedStage - 1]} />}
|
||||||
|
</SplitPane>
|
||||||
|
</PageBody>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ const ExecutionList = () => {
|
|||||||
const columns: Column<TypesExecution>[] = useMemo(
|
const columns: Column<TypesExecution>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: getString('repos.name'),
|
Header: getString('executions.name'),
|
||||||
width: 'calc(100% - 180px)',
|
width: 'calc(100% - 180px)',
|
||||||
Cell: ({ row }: CellProps<TypesExecution>) => {
|
Cell: ({ row }: CellProps<TypesExecution>) => {
|
||||||
const record = row.original
|
const record = row.original
|
||||||
@ -127,7 +127,7 @@ const ExecutionList = () => {
|
|||||||
routes.toCODEExecution({
|
routes.toCODEExecution({
|
||||||
space,
|
space,
|
||||||
pipeline: pipeline as string,
|
pipeline: pipeline as string,
|
||||||
execution: String(executionInfo.id)
|
execution: String(executionInfo.number)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user