feat: add single execution page
This commit is contained in:
@@ -19,7 +19,8 @@ const getExecutionSteps = async (
|
|||||||
|
|
||||||
const executionSteps = execution
|
const executionSteps = execution
|
||||||
.$relatedQuery('executionSteps')
|
.$relatedQuery('executionSteps')
|
||||||
.orderBy('created_at', 'desc');
|
.withGraphFetched('step')
|
||||||
|
.orderBy('created_at', 'asc');
|
||||||
|
|
||||||
return paginate(executionSteps, params.limit, params.offset);
|
return paginate(executionSteps, params.limit, params.offset);
|
||||||
};
|
};
|
||||||
|
@@ -163,9 +163,12 @@ type ExecutionStep {
|
|||||||
id: String
|
id: String
|
||||||
executionId: String
|
executionId: String
|
||||||
stepId: String
|
stepId: String
|
||||||
|
step: Step
|
||||||
status: String
|
status: String
|
||||||
dataIn: JSONObject
|
dataIn: JSONObject
|
||||||
dataOut: JSONObject
|
dataOut: JSONObject
|
||||||
|
createdAt: String
|
||||||
|
updatedAt: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Field {
|
type Field {
|
||||||
|
4
packages/types/index.d.ts
vendored
4
packages/types/index.d.ts
vendored
@@ -20,10 +20,12 @@ export interface IConnection {
|
|||||||
export interface IExecutionStep {
|
export interface IExecutionStep {
|
||||||
id: string;
|
id: string;
|
||||||
executionId: string;
|
executionId: string;
|
||||||
stepId: string;
|
stepId: IStep["id"];
|
||||||
|
step: IStep;
|
||||||
dataIn: IJSONObject;
|
dataIn: IJSONObject;
|
||||||
dataOut: IJSONObject;
|
dataOut: IJSONObject;
|
||||||
status: string;
|
status: string;
|
||||||
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExecution {
|
export interface IExecution {
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "> TODO: description",
|
"description": "> TODO: description",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@alenaksu/json-viewer": "^1.0.0",
|
||||||
"@apollo/client": "^3.4.15",
|
"@apollo/client": "^3.4.15",
|
||||||
"@automatisch/types": "0.1.0",
|
"@automatisch/types": "0.1.0",
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
|
@@ -11,15 +11,15 @@ import ErrorIcon from '@mui/icons-material/Error';
|
|||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from 'notistack';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import type { IConnection } from '@automatisch/types';
|
||||||
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
|
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
|
||||||
import { TEST_CONNECTION } from 'graphql/queries/test-connection';
|
import { TEST_CONNECTION } from 'graphql/queries/test-connection';
|
||||||
import ConnectionContextMenu from 'components/AppConnectionContextMenu';
|
import ConnectionContextMenu from 'components/AppConnectionContextMenu';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import type { Connection } from 'types/connection';
|
|
||||||
import { CardContent, Typography } from './style';
|
import { CardContent, Typography } from './style';
|
||||||
|
|
||||||
type AppConnectionRowProps = {
|
type AppConnectionRowProps = {
|
||||||
connection: Connection;
|
connection: IConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
const countTranslation = (value: React.ReactNode) => (
|
const countTranslation = (value: React.ReactNode) => (
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useQuery } from '@apollo/client';
|
import { useQuery } from '@apollo/client';
|
||||||
|
|
||||||
|
import type { IConnection } from '@automatisch/types';
|
||||||
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
|
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
|
||||||
import AppConnectionRow from 'components/AppConnectionRow';
|
import AppConnectionRow from 'components/AppConnectionRow';
|
||||||
import NoResultFound from 'components/NoResultFound';
|
import NoResultFound from 'components/NoResultFound';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import type { Connection } from 'types/connection';
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
|
|
||||||
type AppConnectionsProps = {
|
type AppConnectionsProps = {
|
||||||
@@ -16,7 +16,7 @@ export default function AppConnections(props: AppConnectionsProps): React.ReactE
|
|||||||
const { appKey } = props;
|
const { appKey } = props;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { data } = useQuery(GET_APP_CONNECTIONS, { variables: { key: appKey } });
|
const { data } = useQuery(GET_APP_CONNECTIONS, { variables: { key: appKey } });
|
||||||
const appConnections: Connection[] = data?.getApp?.connections || [];
|
const appConnections: IConnection[] = data?.getApp?.connections || [];
|
||||||
|
|
||||||
const hasConnections = appConnections?.length;
|
const hasConnections = appConnections?.length;
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ export default function AppConnections(props: AppConnectionsProps): React.ReactE
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{appConnections.map((appConnection: Connection) => (
|
{appConnections.map((appConnection: IConnection) => (
|
||||||
<AppConnectionRow key={appConnection.id} connection={appConnection} />
|
<AppConnectionRow key={appConnection.id} connection={appConnection} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@@ -2,6 +2,7 @@ import * as React from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
import CardActionArea from '@mui/material/CardActionArea';
|
import CardActionArea from '@mui/material/CardActionArea';
|
||||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
@@ -21,23 +22,23 @@ export default function ExecutionRow(props: ExecutionRowProps): React.ReactEleme
|
|||||||
const { flow } = execution;
|
const { flow } = execution;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={URLS.FLOW(flow.id.toString())}>
|
<Link to={URLS.EXECUTION(execution.id)}>
|
||||||
<Card sx={{ mb: 1 }}>
|
<Card sx={{ mb: 1 }}>
|
||||||
<CardActionArea>
|
<CardActionArea>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Box
|
<Stack
|
||||||
display="flex"
|
justifyContent="center"
|
||||||
flex={1}
|
alignItems="flex-start"
|
||||||
flexDirection="column"
|
spacing={1}
|
||||||
>
|
>
|
||||||
<Typography variant="h6" noWrap>
|
<Typography variant="h6" noWrap>
|
||||||
{flow.name}
|
{flow.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="subtitle1" noWrap>
|
<Typography variant="caption" noWrap>
|
||||||
{getHumanlyDate(parseInt(execution.createdAt, 10))}
|
{getHumanlyDate(parseInt(execution.createdAt, 10))}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Stack>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<ArrowForwardIosIcon sx={{ color: (theme) => theme.palette.primary.main }} />
|
<ArrowForwardIosIcon sx={{ color: (theme) => theme.palette.primary.main }} />
|
||||||
|
95
packages/web/src/components/ExecutionStep/index.tsx
Normal file
95
packages/web/src/components/ExecutionStep/index.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import ErrorIcon from '@mui/icons-material/Error';
|
||||||
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||||
|
import Tabs from '@mui/material/Tabs';
|
||||||
|
import Tab from '@mui/material/Tab';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import type { IApp, IJSONObject, IExecutionStep, IStep } from '@automatisch/types';
|
||||||
|
|
||||||
|
import TabPanel from 'components/TabPanel';
|
||||||
|
import JSONViewer from 'components/JSONViewer';
|
||||||
|
import AppIcon from 'components/AppIcon';
|
||||||
|
import { GET_APPS } from 'graphql/queries/get-apps';
|
||||||
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import { AppIconWrapper, AppIconStatusIconWrapper, Content, Header, Wrapper } from './style';
|
||||||
|
|
||||||
|
type ExecutionStepProps = {
|
||||||
|
collapsed?: boolean;
|
||||||
|
step: IStep;
|
||||||
|
index?: number;
|
||||||
|
executionStep: IExecutionStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validIcon = <CheckCircleIcon color="success" />;
|
||||||
|
const errorIcon = <ErrorIcon color="error" />;
|
||||||
|
|
||||||
|
export default function ExecutionStep(props: ExecutionStepProps): React.ReactElement | null {
|
||||||
|
const { executionStep, index, } = props;
|
||||||
|
const [activeTabIndex, setActiveTabIndex] = React.useState(0);
|
||||||
|
const step: IStep = executionStep.step;
|
||||||
|
const isTrigger = step.type === 'trigger';
|
||||||
|
const formatMessage = useFormatMessage();
|
||||||
|
const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }});
|
||||||
|
const apps: IApp[] = data?.getApps;
|
||||||
|
const app = apps?.find((currentApp: IApp) => currentApp.key === step.appKey);
|
||||||
|
|
||||||
|
if (!apps) return null;
|
||||||
|
|
||||||
|
const validationStatusIcon = executionStep.status === 'success' ? validIcon : errorIcon;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper elevation={1}>
|
||||||
|
<Header>
|
||||||
|
<Stack direction="row" alignItems="center" gap={2}>
|
||||||
|
<AppIconWrapper>
|
||||||
|
<AppIcon url={app?.iconUrl} name={app?.name} />
|
||||||
|
|
||||||
|
<AppIconStatusIconWrapper>
|
||||||
|
{validationStatusIcon}
|
||||||
|
</AppIconStatusIconWrapper>
|
||||||
|
</AppIconWrapper>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Typography variant="caption">
|
||||||
|
{
|
||||||
|
isTrigger ?
|
||||||
|
formatMessage('flowStep.triggerType') :
|
||||||
|
formatMessage('flowStep.actionType')
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body2">
|
||||||
|
{step.position}. {app?.name}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</Header>
|
||||||
|
|
||||||
|
<Content sx={{ px: 2 }}>
|
||||||
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||||
|
<Tabs value={activeTabIndex} onChange={(event, tabIndex) => setActiveTabIndex(tabIndex)}>
|
||||||
|
<Tab label="Data in" />
|
||||||
|
<Tab label="Data out" />
|
||||||
|
<Tab label="Execution step" />
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TabPanel value={activeTabIndex} index={0}>
|
||||||
|
<JSONViewer data={executionStep.dataIn} />
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel value={activeTabIndex} index={1}>
|
||||||
|
<JSONViewer data={executionStep.dataOut} />
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel value={activeTabIndex} index={2}>
|
||||||
|
<JSONViewer data={(executionStep as unknown) as IJSONObject} />
|
||||||
|
</TabPanel>
|
||||||
|
</Content>
|
||||||
|
|
||||||
|
</Wrapper>
|
||||||
|
)
|
||||||
|
};
|
42
packages/web/src/components/ExecutionStep/style.ts
Normal file
42
packages/web/src/components/ExecutionStep/style.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { styled, alpha } from '@mui/material/styles';
|
||||||
|
import Card from '@mui/material/Card';
|
||||||
|
|
||||||
|
export const AppIconWrapper = styled('div')`
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AppIconStatusIconWrapper = styled('span')`
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(50%, -50%);
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
// to make it distinguishable over an app icon
|
||||||
|
background: white;
|
||||||
|
border-radius: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Wrapper = styled(Card)`
|
||||||
|
width: 100%;
|
||||||
|
overflow: unset;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type HeaderProps = {
|
||||||
|
collapsed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Header = styled('div', { shouldForwardProp: prop => prop !== 'collapsed' })<HeaderProps>`
|
||||||
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
|
cursor: ${({ collapsed }) => collapsed ? 'pointer' : 'unset'};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Content = styled('div')`
|
||||||
|
border: 1px solid ${({ theme }) => alpha(theme.palette.divider, .8)};
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
padding: ${({ theme }) => theme.spacing(2, 0)};
|
||||||
|
`;
|
@@ -17,7 +17,7 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement {
|
|||||||
const { flow } = props;
|
const { flow } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={URLS.FLOW(flow.id.toString())}>
|
<Link to={URLS.FLOW(flow.id)}>
|
||||||
<Card sx={{ mb: 1 }}>
|
<Card sx={{ mb: 1 }}>
|
||||||
<CardActionArea>
|
<CardActionArea>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
@@ -144,7 +144,7 @@ export default function FlowStep(
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
{index}. {app?.name}
|
{step.position}. {app?.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
31
packages/web/src/components/JSONViewer/index.tsx
Normal file
31
packages/web/src/components/JSONViewer/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import '@alenaksu/json-viewer';
|
||||||
|
|
||||||
|
import type { IJSONObject } from '@automatisch/types';
|
||||||
|
import { jsonViewerStyles } from './style';
|
||||||
|
|
||||||
|
type JSONViewerProps = {
|
||||||
|
data: IJSONObject;
|
||||||
|
}
|
||||||
|
function JSONViewer(props: JSONViewerProps) {
|
||||||
|
const { data } = props;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const viewerRef = React.useRef<any>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (viewerRef.current){
|
||||||
|
viewerRef.current.data = data;
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{jsonViewerStyles}
|
||||||
|
|
||||||
|
<json-viewer ref={viewerRef} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default JSONViewer;
|
29
packages/web/src/components/JSONViewer/style.tsx
Normal file
29
packages/web/src/components/JSONViewer/style.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import GlobalStyles from '@mui/material/GlobalStyles';
|
||||||
|
|
||||||
|
export const jsonViewerStyles = (<GlobalStyles styles={(theme) => ({
|
||||||
|
'json-viewer': {
|
||||||
|
'--background-color': 'transparent',
|
||||||
|
'--font-family': 'monaco, Consolas, Lucida Console, monospace',
|
||||||
|
'--font-size': '1rem',
|
||||||
|
'--indent-size': '1.5em',
|
||||||
|
'--indentguide-size': '1px',
|
||||||
|
'--indentguide-style': 'solid',
|
||||||
|
'--indentguide-color': theme.palette.text.primary,
|
||||||
|
'--indentguide-color-active': '#666',
|
||||||
|
'--indentguide': 'var(--indentguide-size) var(--indentguide-style) var(--indentguide-color)',
|
||||||
|
'--indentguide-active': 'var(--indentguide-size) var(--indentguide-style) var(--indentguide-color-active)',
|
||||||
|
|
||||||
|
/* Types colors */
|
||||||
|
'--string-color': theme.palette.text.secondary,
|
||||||
|
'--number-color': theme.palette.text.primary,
|
||||||
|
'--boolean-color': theme.palette.text.primary,
|
||||||
|
'--null-color': theme.palette.text.primary,
|
||||||
|
'--property-color': theme.palette.text.primary,
|
||||||
|
|
||||||
|
/* Collapsed node preview */
|
||||||
|
'--preview-color': theme.palette.text.primary,
|
||||||
|
|
||||||
|
/* Search highlight color */
|
||||||
|
'--highlight-color': '#6fb3d2',
|
||||||
|
}
|
||||||
|
})} />)
|
@@ -4,6 +4,7 @@ import Collapse from '@mui/material/Collapse';
|
|||||||
import ListItem from '@mui/material/ListItem';
|
import ListItem from '@mui/material/ListItem';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
|
|
||||||
|
import JSONViewer from 'components/JSONViewer';
|
||||||
import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
|
import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
|
||||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||||
import type { IStep, ISubstep } from '@automatisch/types';
|
import type { IStep, ISubstep } from '@automatisch/types';
|
||||||
@@ -58,9 +59,7 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
|
|||||||
<ListItem sx={{ pt: 2, pb: 3, flexDirection: 'column', alignItems: 'flex-start' }}>
|
<ListItem sx={{ pt: 2, pb: 3, flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||||
|
|
||||||
{response && (
|
{response && (
|
||||||
<pre style={{ whiteSpace: 'pre-wrap', }}>
|
<JSONViewer data={response} />
|
||||||
{JSON.stringify(response, null, 2)}
|
|
||||||
</pre>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
export const CONNECTIONS = '/connections';
|
export const CONNECTIONS = '/connections';
|
||||||
export const EXPLORE = '/explore';
|
export const EXPLORE = '/explore';
|
||||||
export const EXECUTIONS = '/executions';
|
export const EXECUTIONS = '/executions';
|
||||||
|
export const EXECUTION_PATTERN = '/executions/:executionId';
|
||||||
|
export const EXECUTION = (executionId: string): string => `/executions/${executionId}`;
|
||||||
|
|
||||||
export const LOGIN = '/login';
|
export const LOGIN = '/login';
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { InMemoryCache } from '@apollo/client';
|
import { InMemoryCache } from '@apollo/client';
|
||||||
|
import offsetLimitPagination from './pagination';
|
||||||
|
|
||||||
const cache = new InMemoryCache({
|
const cache = new InMemoryCache({
|
||||||
typePolicies: {
|
typePolicies: {
|
||||||
@@ -30,6 +31,11 @@ const cache = new InMemoryCache({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Query: {
|
||||||
|
fields: {
|
||||||
|
getExecutionSteps: offsetLimitPagination(['executionId', 'limit']),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
30
packages/web/src/graphql/queries/get-execution-steps.ts
Normal file
30
packages/web/src/graphql/queries/get-execution-steps.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const GET_EXECUTION_STEPS = gql`
|
||||||
|
query GetExecutionSteps($executionId: String!, $limit: Int!, $offset: Int!) {
|
||||||
|
getExecutionSteps(executionId: $executionId, limit: $limit, offset: $offset) {
|
||||||
|
pageInfo {
|
||||||
|
currentPage
|
||||||
|
totalPages
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
executionId
|
||||||
|
status
|
||||||
|
dataIn
|
||||||
|
dataOut
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
step {
|
||||||
|
id
|
||||||
|
appKey
|
||||||
|
type
|
||||||
|
status
|
||||||
|
position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@@ -12,6 +12,7 @@ export const GET_FLOW = gql`
|
|||||||
key
|
key
|
||||||
appKey
|
appKey
|
||||||
status
|
status
|
||||||
|
position
|
||||||
connection {
|
connection {
|
||||||
id
|
id
|
||||||
verified
|
verified
|
||||||
|
52
packages/web/src/pages/Execution/index.tsx
Normal file
52
packages/web/src/pages/Execution/index.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Grid from '@mui/material/Grid';
|
||||||
|
import type { IExecutionStep } from '@automatisch/types';
|
||||||
|
|
||||||
|
import ExecutionStep from 'components/ExecutionStep';
|
||||||
|
import Container from 'components/Container';
|
||||||
|
import { GET_EXECUTION_STEPS } from 'graphql/queries/get-execution-steps';
|
||||||
|
|
||||||
|
type ExecutionParams = {
|
||||||
|
executionId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EXECUTION_PER_PAGE = 5;
|
||||||
|
|
||||||
|
const getLimitAndOffset = (page: number) => ({
|
||||||
|
limit: EXECUTION_PER_PAGE,
|
||||||
|
offset: (page - 1) * EXECUTION_PER_PAGE,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function Execution(): React.ReactElement {
|
||||||
|
const { executionId } = useParams() as ExecutionParams;
|
||||||
|
const { data, fetchMore } = useQuery(GET_EXECUTION_STEPS, { variables: { executionId, ...getLimitAndOffset(1) } });
|
||||||
|
|
||||||
|
const { edges, pageInfo } = data?.getExecutionSteps || {};
|
||||||
|
const executionSteps: IExecutionStep[] = edges?.map((edge: { node: IExecutionStep }) => edge.node);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (pageInfo?.currentPage < pageInfo?.totalPages) {
|
||||||
|
fetchMore({
|
||||||
|
variables: {
|
||||||
|
executionId,
|
||||||
|
...getLimitAndOffset(pageInfo.currentPage + 1),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [executionId, fetchMore, pageInfo]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ py: 3 }}>
|
||||||
|
<Container>
|
||||||
|
<Grid container item sx={{ mb: [2, 5] }} columnSpacing={1.5} rowGap={3}>
|
||||||
|
{executionSteps?.map((executionStep) => (
|
||||||
|
<ExecutionStep key={executionStep.id} executionStep={executionStep} step={executionStep.step} />
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@@ -5,24 +5,22 @@ import Grid from '@mui/material/Grid';
|
|||||||
|
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
|
|
||||||
type ApplicationParams = {
|
type FlowParams = {
|
||||||
flowId: string;
|
flowId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Flow(): React.ReactElement {
|
export default function Flow(): React.ReactElement {
|
||||||
const { flowId } = useParams() as ApplicationParams;
|
const { flowId } = useParams() as FlowParams;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box sx={{ py: 3 }}>
|
||||||
<Box sx={{ py: 3 }}>
|
<Container>
|
||||||
<Container>
|
<Grid container>
|
||||||
<Grid container>
|
<Grid item xs>
|
||||||
<Grid item xs>
|
{flowId}
|
||||||
{flowId}
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Grid>
|
||||||
</Box>
|
</Container>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
7
packages/web/src/react-app-env.d.ts
vendored
7
packages/web/src/react-app-env.d.ts
vendored
@@ -1 +1,8 @@
|
|||||||
/// <reference types="react-scripts" />
|
/// <reference types="react-scripts" />
|
||||||
|
|
||||||
|
declare namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
"json-viewer": any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -3,8 +3,9 @@ import Layout from 'components/Layout';
|
|||||||
import PublicLayout from 'components/PublicLayout';
|
import PublicLayout from 'components/PublicLayout';
|
||||||
import Applications from 'pages/Applications';
|
import Applications from 'pages/Applications';
|
||||||
import Application from 'pages/Application';
|
import Application from 'pages/Application';
|
||||||
import Flows from 'pages/Flows';
|
|
||||||
import Executions from 'pages/Executions';
|
import Executions from 'pages/Executions';
|
||||||
|
import Execution from 'pages/Execution';
|
||||||
|
import Flows from 'pages/Flows';
|
||||||
import Flow from 'pages/Flow';
|
import Flow from 'pages/Flow';
|
||||||
import Explore from 'pages/Explore';
|
import Explore from 'pages/Explore';
|
||||||
import Login from 'pages/Login';
|
import Login from 'pages/Login';
|
||||||
@@ -15,9 +16,11 @@ export default (
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path={URLS.EXECUTIONS} element={<Layout><Executions /></Layout>} />
|
<Route path={URLS.EXECUTIONS} element={<Layout><Executions /></Layout>} />
|
||||||
|
|
||||||
|
<Route path={URLS.EXECUTION_PATTERN} element={<Layout><Execution /></Layout>} />
|
||||||
|
|
||||||
<Route path={URLS.FLOWS} element={<Layout><Flows /></Layout>} />
|
<Route path={URLS.FLOWS} element={<Layout><Flows /></Layout>} />
|
||||||
|
|
||||||
<Route path={`${URLS.FLOW_PATTERN}/*`} element={<Layout><Flow /></Layout>} />
|
<Route path={URLS.FLOW_PATTERN} element={<Layout><Flow /></Layout>} />
|
||||||
|
|
||||||
<Route path={`${URLS.APPS}/*`} element={<Layout><Applications /></Layout>} />
|
<Route path={`${URLS.APPS}/*`} element={<Layout><Applications /></Layout>} />
|
||||||
|
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
import type { IConnection } from '@automatisch/types';
|
|
||||||
|
|
||||||
export type Connection = IConnection;
|
|
36
yarn.lock
36
yarn.lock
@@ -2,6 +2,13 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@alenaksu/json-viewer@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@alenaksu/json-viewer/-/json-viewer-1.0.0.tgz#4451d80f581f4eb4e0b4f030441c56ffb5420869"
|
||||||
|
integrity sha512-eG4vPLGrjAkx3qyM5ub89POG/ySbXu46tz8ANzdFw9JnZaTrKXBwRxTo6zgC+6T5InXU5wpLvecPe/t+1vwlqQ==
|
||||||
|
dependencies:
|
||||||
|
lit "^2.2.0"
|
||||||
|
|
||||||
"@algolia/autocomplete-core@1.5.0":
|
"@algolia/autocomplete-core@1.5.0":
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.0.tgz#6c91c9de7748e9c103846828a58dfe92bd4d6689"
|
resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.0.tgz#6c91c9de7748e9c103846828a58dfe92bd4d6689"
|
||||||
@@ -3112,6 +3119,11 @@
|
|||||||
npmlog "^4.1.2"
|
npmlog "^4.1.2"
|
||||||
write-file-atomic "^3.0.3"
|
write-file-atomic "^3.0.3"
|
||||||
|
|
||||||
|
"@lit/reactive-element@^1.3.0":
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.3.1.tgz#3021ad0fa30a75a41212c5e7f1f169c5762ef8bb"
|
||||||
|
integrity sha512-nOJARIr3pReqK3hfFCSW2Zg/kFcFsSAlIE7z4a0C9D2dPrgD/YSn3ZP2ET/rxKB65SXyG7jJbkynBRm+tGlacw==
|
||||||
|
|
||||||
"@mapbox/node-pre-gyp@^1.0.0":
|
"@mapbox/node-pre-gyp@^1.0.0":
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz#32abc8a5c624bc4e46c43d84dfb8b26d33a96f58"
|
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz#32abc8a5c624bc4e46c43d84dfb8b26d33a96f58"
|
||||||
@@ -11991,6 +12003,30 @@ lines-and-columns@^1.1.6:
|
|||||||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
||||||
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
||||||
|
|
||||||
|
lit-element@^3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.2.0.tgz#9c981c55dfd9a8f124dc863edb62cc529d434db7"
|
||||||
|
integrity sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==
|
||||||
|
dependencies:
|
||||||
|
"@lit/reactive-element" "^1.3.0"
|
||||||
|
lit-html "^2.2.0"
|
||||||
|
|
||||||
|
lit-html@^2.2.0:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.2.1.tgz#762f112a8b54eaf0bbae3f516de935a25dcc12d1"
|
||||||
|
integrity sha512-AiJ/Rs0awjICs2FioTnHSh+Np5dhYSkyRczKy3wKjp8qjLhr1Ov+GiHrUQNdX8ou1LMuznpIME990AZsa/tR8g==
|
||||||
|
dependencies:
|
||||||
|
"@types/trusted-types" "^2.0.2"
|
||||||
|
|
||||||
|
lit@^2.2.0:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lit/-/lit-2.2.1.tgz#4b679e1d8cb6c7977b64921b1ea3eca7850ca1dd"
|
||||||
|
integrity sha512-dSe++R50JqrvNGXmI9OE13de1z5U/Y3J2dTm/9GC86vedI8ILoR8ZGnxfThFpvQ9m0lR0qRnIR4IiKj/jDCfYw==
|
||||||
|
dependencies:
|
||||||
|
"@lit/reactive-element" "^1.3.0"
|
||||||
|
lit-element "^3.2.0"
|
||||||
|
lit-html "^2.2.0"
|
||||||
|
|
||||||
load-json-file@^4.0.0:
|
load-json-file@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
|
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
|
||||||
|
Reference in New Issue
Block a user