From 15aaada3fe4ac73d6f09daf397abe7dfdf0ac7fb Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Sun, 31 Jul 2022 13:36:28 +0200 Subject: [PATCH 1/4] feat: add relative time and context menu in flows --- packages/backend/src/graphql/schema.graphql | 2 + packages/backend/src/models/base.ts | 4 +- packages/backend/src/models/flow.ts | 4 +- packages/types/index.d.ts | 4 ++ .../src/components/FlowContextMenu/index.tsx | 58 +++++++++++++++++ packages/web/src/components/FlowRow/index.tsx | 64 ++++++++++++++++--- .../web/src/components/FlowStep/index.tsx | 2 +- .../web/src/graphql/mutations/delete-flow.ts | 7 ++ packages/web/src/graphql/queries/get-flows.ts | 2 + packages/web/src/locales/en.json | 4 ++ 10 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 packages/web/src/components/FlowContextMenu/index.tsx create mode 100644 packages/web/src/graphql/mutations/delete-flow.ts diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 37b70553..242893ec 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -203,6 +203,8 @@ type Flow { name: String active: Boolean steps: [Step] + createdAt: String + updatedAt: String } type Execution { diff --git a/packages/backend/src/models/base.ts b/packages/backend/src/models/base.ts index 1d982bd6..95c40746 100644 --- a/packages/backend/src/models/base.ts +++ b/packages/backend/src/models/base.ts @@ -31,9 +31,9 @@ class Base extends Model { } async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext): Promise { - await super.$beforeUpdate(opt, queryContext); - this.updatedAt = new Date().toISOString(); + + await super.$beforeUpdate(opt, queryContext); } } diff --git a/packages/backend/src/models/flow.ts b/packages/backend/src/models/flow.ts index 727f2506..01733ded 100644 --- a/packages/backend/src/models/flow.ts +++ b/packages/backend/src/models/flow.ts @@ -44,7 +44,9 @@ class Flow extends Base { }, }); - async $beforeUpdate(opt: ModelOptions): Promise { + async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext): Promise { + await super.$beforeUpdate(opt, queryContext); + if (!this.active) return; const oldFlow = opt.old as Flow; diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index 67b12115..e61a259f 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -26,6 +26,7 @@ export interface IExecutionStep { dataOut: IJSONObject; status: string; createdAt: string; + updatedAt: string; } export interface IExecution { @@ -34,6 +35,7 @@ export interface IExecution { flow: IFlow; testRun: boolean; executionSteps: IExecutionStep[]; + updatedAt: string; createdAt: string; } @@ -61,6 +63,8 @@ export interface IFlow { userId: string; active: boolean; steps: IStep[]; + createdAt: string; + updatedAt: string; } export interface IUser { diff --git a/packages/web/src/components/FlowContextMenu/index.tsx b/packages/web/src/components/FlowContextMenu/index.tsx new file mode 100644 index 00000000..95bea760 --- /dev/null +++ b/packages/web/src/components/FlowContextMenu/index.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import { useMutation } from '@apollo/client'; +import { Link } from 'react-router-dom'; +import Menu from '@mui/material/Menu'; +import type { PopoverProps } from '@mui/material/Popover'; +import MenuItem from '@mui/material/MenuItem'; + +import { DELETE_FLOW } from 'graphql/mutations/delete-flow'; +import * as URLS from 'config/urls'; +import useFormatMessage from 'hooks/useFormatMessage'; + +type ContextMenuProps = { + flowId: string; + onClose: () => void; + anchorEl: PopoverProps['anchorEl']; +}; + +export default function ContextMenu(props: ContextMenuProps): React.ReactElement { + const { flowId, onClose, anchorEl } = props; + const [deleteFlow] = useMutation(DELETE_FLOW); + const formatMessage = useFormatMessage(); + + const onFlowDelete = React.useCallback(async () => { + await deleteFlow({ + variables: { input: { id: flowId } }, + update: (cache) => { + const flowCacheId = cache.identify({ + __typename: 'Flow', + id: flowId, + }); + + cache.evict({ + id: flowCacheId, + }); + } + }); + }, [flowId, deleteFlow]); + + return ( + + + {formatMessage('flow.view')} + + + + {formatMessage('flow.delete')} + + + ); +}; diff --git a/packages/web/src/components/FlowRow/index.tsx b/packages/web/src/components/FlowRow/index.tsx index b220cb90..30d03e6c 100644 --- a/packages/web/src/components/FlowRow/index.tsx +++ b/packages/web/src/components/FlowRow/index.tsx @@ -2,10 +2,15 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import Card from '@mui/material/Card'; import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import Stack from '@mui/material/Stack'; import CardActionArea from '@mui/material/CardActionArea'; -import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; +import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; +import { DateTime } from 'luxon'; import type { IFlow } from '@automatisch/types'; +import FlowContextMenu from 'components/FlowContextMenu'; +import useFormatMessage from 'hooks/useFormatMessage'; import * as URLS from 'config/urls'; import { CardContent, Typography } from './style'; @@ -14,25 +19,68 @@ type FlowRowProps = { } export default function FlowRow(props: FlowRowProps): React.ReactElement { + const formatMessage = useFormatMessage(); + const contextButtonRef = React.useRef(null); + const [anchorEl, setAnchorEl] = React.useState(null); const { flow } = props; + const handleClose = () => { + setAnchorEl(null); + }; + const onContextMenuClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + setAnchorEl(contextButtonRef.current); + } + + const createdAt = DateTime.fromMillis(parseInt(flow.createdAt, 10)); + const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt, 10)); + const isUpdated = updatedAt > createdAt; + const relativeCreatedAt = createdAt.toRelative(); + const relativeUpdatedAt = updatedAt.toRelative(); + return ( - + <> - + - + - {flow.name} + {flow?.name} - + + + {isUpdated && formatMessage('flow.updatedAt', { datetime: relativeUpdatedAt })} + {!isUpdated && formatMessage('flow.createdAt', { datetime: relativeCreatedAt })} + + - theme.palette.primary.main }} /> + + + - + + {anchorEl && } + ); } \ No newline at end of file diff --git a/packages/web/src/components/FlowStep/index.tsx b/packages/web/src/components/FlowStep/index.tsx index 185c22b4..8b2c35cb 100644 --- a/packages/web/src/components/FlowStep/index.tsx +++ b/packages/web/src/components/FlowStep/index.tsx @@ -106,7 +106,7 @@ export default function FlowStep( ); const isTrigger = step.type === 'trigger'; const formatMessage = useFormatMessage(); - const [currentSubstep, setCurrentSubstep] = React.useState(2); + const [currentSubstep, setCurrentSubstep] = React.useState(0); const { data } = useQuery(GET_APPS, { variables: { onlyWithTriggers: isTrigger }, }); diff --git a/packages/web/src/graphql/mutations/delete-flow.ts b/packages/web/src/graphql/mutations/delete-flow.ts new file mode 100644 index 00000000..f708a5b7 --- /dev/null +++ b/packages/web/src/graphql/mutations/delete-flow.ts @@ -0,0 +1,7 @@ +import { gql } from '@apollo/client'; + +export const DELETE_FLOW = gql` + mutation DeleteFlow($input: DeleteFlowInput) { + deleteFlow(input: $input) + } +`; diff --git a/packages/web/src/graphql/queries/get-flows.ts b/packages/web/src/graphql/queries/get-flows.ts index c8d725be..42ded512 100644 --- a/packages/web/src/graphql/queries/get-flows.ts +++ b/packages/web/src/graphql/queries/get-flows.ts @@ -11,6 +11,8 @@ export const GET_FLOWS = gql` node { id name + createdAt + updatedAt } } } diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index 95f334ee..5e75c89f 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -39,6 +39,10 @@ "createFlow.creating": "Creating a flow...", "flow.active": "ON", "flow.inactive": "OFF", + "flow.createdAt": "created {datetime}", + "flow.updatedAt": "updated {datetime}", + "flow.view": "View", + "flow.delete": "Delete", "flowStep.triggerType": "Trigger", "flowStep.actionType": "Action", "flows.create": "Create flow", From 8c59bd664ec4a3a3d44cc3da230cccd49b11f778 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Sun, 31 Jul 2022 16:13:07 +0200 Subject: [PATCH 2/4] feat: add used app icons in FlowRow --- packages/web/src/components/AppIcon/index.tsx | 5 ++-- packages/web/src/components/FlowRow/index.tsx | 27 ++++++++++++------ packages/web/src/components/FlowRow/style.ts | 28 +++++++++++++++++-- packages/web/src/graphql/queries/get-flows.ts | 2 +- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/packages/web/src/components/AppIcon/index.tsx b/packages/web/src/components/AppIcon/index.tsx index 3b940c90..1d07d8f6 100644 --- a/packages/web/src/components/AppIcon/index.tsx +++ b/packages/web/src/components/AppIcon/index.tsx @@ -6,6 +6,7 @@ type AppIconProps = { name?: string; url?: string; color?: string; + variant?: AvatarProps['variant']; }; const inlineImgStyle: React.CSSProperties = { @@ -13,12 +14,12 @@ const inlineImgStyle: React.CSSProperties = { }; export default function AppIcon(props: AppIconProps & AvatarProps): React.ReactElement { - const { name, url, color, sx = {}, ...restProps } = props; + const { name, url, color, sx = {}, variant = "square", ...restProps } = props; return ( - + {["Twitter", "+3", "Github"].map((app) => ( + + ))} + + + <Typography variant="h6" noWrap> {flow?.name} @@ -58,9 +69,9 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement { {isUpdated && formatMessage('flow.updatedAt', { datetime: relativeUpdatedAt })} {!isUpdated && formatMessage('flow.createdAt', { datetime: relativeCreatedAt })} </Typography> - </Stack> + - + - + @@ -83,4 +94,4 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement { />} ); -} \ No newline at end of file +} diff --git a/packages/web/src/components/FlowRow/style.ts b/packages/web/src/components/FlowRow/style.ts index e6260438..e2a7f5be 100644 --- a/packages/web/src/components/FlowRow/style.ts +++ b/packages/web/src/components/FlowRow/style.ts @@ -1,20 +1,42 @@ import { styled } from '@mui/material/styles'; +import MuiStack from '@mui/material/Stack'; +import MuiBox from '@mui/material/Box'; import MuiCardContent from '@mui/material/CardContent'; import MuiTypography from '@mui/material/Typography'; export const CardContent = styled(MuiCardContent)(({ theme }) => ({ display: 'grid', gridTemplateRows: 'auto', - gridTemplateColumns: '1fr auto', - gridColumnGap: theme.spacing(2), + gridTemplateColumns: 'calc(50px * 3 + 8px * 2) minmax(0, auto) min-content', + gridGap: theme.spacing(2), + gridTemplateAreas: ` + "apps title menu" + `, alignItems: 'center', + [theme.breakpoints.down('sm')]: { + gridTemplateAreas: ` + "apps menu" + "title menu" + `, + gridTemplateColumns: 'minmax(0, auto) min-content', + gridTemplateRows: 'auto auto', + } })); +export const Apps = styled(MuiStack)(() => ({ + gridArea: 'apps', +})); +export const Title = styled(MuiStack)(() => ({ + gridArea: 'title', +})); +export const ContextMenu = styled(MuiBox)(() => ({ + gridArea: 'menu', +})); export const Typography = styled(MuiTypography)(() => ({ display: 'inline-block', width: '100%', - maxWidth: '70%', + maxWidth: '85%', })); export const DesktopOnlyBreakline = styled('br')(({ theme }) => ({ diff --git a/packages/web/src/graphql/queries/get-flows.ts b/packages/web/src/graphql/queries/get-flows.ts index 42ded512..d752bebb 100644 --- a/packages/web/src/graphql/queries/get-flows.ts +++ b/packages/web/src/graphql/queries/get-flows.ts @@ -17,4 +17,4 @@ export const GET_FLOWS = gql` } } } -`; \ No newline at end of file +`; From 82bdf9d3b19208102ff9c70178254e721b120a46 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Sun, 31 Jul 2022 22:06:08 +0200 Subject: [PATCH 3/4] feat: add intermediate step count in flow apps --- packages/web/src/components/FlowRow/index.tsx | 24 ++++++++++++------- .../IntermediateStepCount/index.tsx | 20 ++++++++++++++++ .../components/IntermediateStepCount/style.ts | 12 ++++++++++ packages/web/src/graphql/queries/get-flows.ts | 4 ++-- 4 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 packages/web/src/components/IntermediateStepCount/index.tsx create mode 100644 packages/web/src/components/IntermediateStepCount/style.ts diff --git a/packages/web/src/components/FlowRow/index.tsx b/packages/web/src/components/FlowRow/index.tsx index c2acc43c..43856d55 100644 --- a/packages/web/src/components/FlowRow/index.tsx +++ b/packages/web/src/components/FlowRow/index.tsx @@ -7,6 +7,7 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import { DateTime } from 'luxon'; import type { IFlow } from '@automatisch/types'; +import IntermediateStepCount from 'components/IntermediateStepCount'; import AppIcon from 'components/AppIcon'; import FlowContextMenu from 'components/FlowContextMenu'; import useFormatMessage from 'hooks/useFormatMessage'; @@ -45,14 +46,21 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement { - {["Twitter", "+3", "Github"].map((app) => ( - - ))} + + + + + + <Typography variant="body2"> + +{count} + </Typography> + </Container> + ); +} \ No newline at end of file diff --git a/packages/web/src/components/IntermediateStepCount/style.ts b/packages/web/src/components/IntermediateStepCount/style.ts new file mode 100644 index 00000000..e4580f42 --- /dev/null +++ b/packages/web/src/components/IntermediateStepCount/style.ts @@ -0,0 +1,12 @@ +import { styled } from '@mui/material/styles'; + +export const Container = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + minWidth: 50, + height: 50, + border: '1px solid #000', + borderRadius: theme.shape.borderRadius, +})); diff --git a/packages/web/src/graphql/queries/get-flows.ts b/packages/web/src/graphql/queries/get-flows.ts index d752bebb..c18bf02a 100644 --- a/packages/web/src/graphql/queries/get-flows.ts +++ b/packages/web/src/graphql/queries/get-flows.ts @@ -11,8 +11,8 @@ export const GET_FLOWS = gql` node { id name - createdAt - updatedAt + createdAt + updatedAt } } } From 744b31aad68c113c1f12baa23a817e4862d66c5f Mon Sep 17 00:00:00 2001 From: Ali BARIN <ali.barin53@gmail.com> Date: Sat, 6 Aug 2022 23:41:39 +0200 Subject: [PATCH 4/4] feat: add dynamic flow app icons --- packages/backend/src/models/step.ts | 2 + packages/types/index.d.ts | 1 + packages/web/src/components/AppIcon/index.tsx | 12 +++++- .../web/src/components/FlowAppIcons/index.tsx | 37 +++++++++++++++++++ packages/web/src/components/FlowRow/index.tsx | 19 +--------- .../IntermediateStepCount/index.tsx | 2 +- .../components/IntermediateStepCount/style.ts | 2 +- packages/web/src/graphql/queries/get-flows.ts | 3 ++ 8 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 packages/web/src/components/FlowAppIcons/index.tsx diff --git a/packages/backend/src/models/step.ts b/packages/backend/src/models/step.ts index 57ec44e4..a231066e 100644 --- a/packages/backend/src/models/step.ts +++ b/packages/backend/src/models/step.ts @@ -73,6 +73,8 @@ class Step extends Base { }); get iconUrl() { + if (!this.appKey) return null; + return `${appConfig.baseUrl}/apps/${this.appKey}/assets/favicon.svg`; } diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index e61a259f..91130065 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -45,6 +45,7 @@ export interface IStep { flowId: string; key: string; appKey: string; + iconUrl: string; type: 'action' | 'trigger'; connectionId: string; status: string; diff --git a/packages/web/src/components/AppIcon/index.tsx b/packages/web/src/components/AppIcon/index.tsx index 1d07d8f6..2c114bca 100644 --- a/packages/web/src/components/AppIcon/index.tsx +++ b/packages/web/src/components/AppIcon/index.tsx @@ -14,7 +14,16 @@ const inlineImgStyle: React.CSSProperties = { }; export default function AppIcon(props: AppIconProps & AvatarProps): React.ReactElement { - const { name, url, color, sx = {}, variant = "square", ...restProps } = props; + const { + name, + url, + color, + sx = {}, + variant = "square", + ...restProps + } = props; + + const initialLetter = name?.[0]; return ( <Avatar @@ -24,6 +33,7 @@ export default function AppIcon(props: AppIconProps & AvatarProps): React.ReactE imgProps={{ style: inlineImgStyle }} src={url} alt={name} + children={initialLetter} {...restProps} /> ); diff --git a/packages/web/src/components/FlowAppIcons/index.tsx b/packages/web/src/components/FlowAppIcons/index.tsx new file mode 100644 index 00000000..9932b735 --- /dev/null +++ b/packages/web/src/components/FlowAppIcons/index.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import type { IStep } from '@automatisch/types'; + +import AppIcon from 'components/AppIcon'; +import IntermediateStepCount from 'components/IntermediateStepCount'; + +type FlowAppIconsProps = { + steps: Partial<IStep>[]; +} + +export default function FlowAppIcons(props: FlowAppIconsProps) { + const { steps } = props; + const stepsCount = steps.length; + const firstStep = steps[0]; + const lastStep = steps[stepsCount - 1]; + const intermeaditeStepCount = stepsCount - 2; + + + return ( + <> + <AppIcon + name=" " + variant="rounded" + url={firstStep.iconUrl} + imgProps={{ width: 30, height: 30}} + /> + + {intermeaditeStepCount > 0 && <IntermediateStepCount count={intermeaditeStepCount} />} + + <AppIcon + name=" " + variant="rounded" + url={lastStep.iconUrl} + /> + </> + ) +}; diff --git a/packages/web/src/components/FlowRow/index.tsx b/packages/web/src/components/FlowRow/index.tsx index 43856d55..a33bdc19 100644 --- a/packages/web/src/components/FlowRow/index.tsx +++ b/packages/web/src/components/FlowRow/index.tsx @@ -7,8 +7,7 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import { DateTime } from 'luxon'; import type { IFlow } from '@automatisch/types'; -import IntermediateStepCount from 'components/IntermediateStepCount'; -import AppIcon from 'components/AppIcon'; +import FlowAppIcons from 'components/FlowAppIcons'; import FlowContextMenu from 'components/FlowContextMenu'; import useFormatMessage from 'hooks/useFormatMessage'; import * as URLS from 'config/urls'; @@ -46,21 +45,7 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement { <CardActionArea component={Link} to={URLS.FLOW(flow.id)}> <CardContent> <Apps direction="row" gap={1} sx={{gridArea:"apps"}}> - <AppIcon - name="Twitter" - color="lightpink" - variant="rounded" - url="httpss://via.placeholder.com/50" - /> - - <IntermediateStepCount count={99} /> - - <AppIcon - name="Github" - color="lightpink" - variant="rounded" - url="httpss://via.placeholder.com/50" - /> + <FlowAppIcons steps={flow.steps} /> </Apps> <Title diff --git a/packages/web/src/components/IntermediateStepCount/index.tsx b/packages/web/src/components/IntermediateStepCount/index.tsx index 5731bb4d..554d2f78 100644 --- a/packages/web/src/components/IntermediateStepCount/index.tsx +++ b/packages/web/src/components/IntermediateStepCount/index.tsx @@ -12,7 +12,7 @@ export default function IntermediateStepCount(props: IntermediateStepCountProps) return ( <Container> - <Typography variant="body2"> + <Typography variant="subtitle1" sx={{ }}> +{count} </Typography> </Container> diff --git a/packages/web/src/components/IntermediateStepCount/style.ts b/packages/web/src/components/IntermediateStepCount/style.ts index e4580f42..95ac6760 100644 --- a/packages/web/src/components/IntermediateStepCount/style.ts +++ b/packages/web/src/components/IntermediateStepCount/style.ts @@ -7,6 +7,6 @@ export const Container = styled('div')(({ theme }) => ({ alignItems: 'center', minWidth: 50, height: 50, - border: '1px solid #000', + border: `1px solid ${theme.palette.text.disabled}`, borderRadius: theme.shape.borderRadius, })); diff --git a/packages/web/src/graphql/queries/get-flows.ts b/packages/web/src/graphql/queries/get-flows.ts index c18bf02a..56e18dc2 100644 --- a/packages/web/src/graphql/queries/get-flows.ts +++ b/packages/web/src/graphql/queries/get-flows.ts @@ -13,6 +13,9 @@ export const GET_FLOWS = gql` name createdAt updatedAt + steps { + iconUrl + } } } }