feat: introduce propTypes

This commit is contained in:
kasia.oczkowska
2024-02-29 09:53:07 +00:00
committed by Ali BARIN
parent bfc7d5d0dd
commit 7afdf43872
57 changed files with 1119 additions and 735 deletions

View File

@@ -1,3 +1,4 @@
node_modules
build
source
.eslintrc.js

View File

@@ -4,4 +4,7 @@ module.exports = {
'plugin:@tanstack/eslint-plugin-query/recommended',
'prettier',
],
rules: {
'react/prop-types': 'warn',
},
};

View File

@@ -19,7 +19,6 @@
"@testing-library/user-event": "^12.1.10",
"clipboard-copy": "^4.0.1",
"compare-versions": "^4.1.3",
"eslint-plugin-react": "^7.33.2",
"graphql": "^15.6.0",
"lodash": "^4.17.21",
"luxon": "^2.3.1",
@@ -47,7 +46,7 @@
"build:watch": "yarn nodemon --exec react-scripts build --watch 'src/**/*.ts' --watch 'public/**/*' --ext ts,html",
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "eslint .",
"lint": "eslint src --ext .js,.jsx",
"prepack": "yarn build"
},
"files": [

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import PropTypes from 'prop-types';
import MenuItem from '@mui/material/MenuItem';
import Menu from '@mui/material/Menu';
import { Link } from 'react-router-dom';
@@ -51,4 +52,12 @@ function AccountDropdownMenu(props) {
</Menu>
);
}
AccountDropdownMenu.propTypes = {
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
anchorEl: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
id: PropTypes.string.isRequired,
};
export default AccountDropdownMenu;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import LoadingButton from '@mui/lab/LoadingButton';
import Alert from '@mui/material/Alert';
import Dialog from '@mui/material/Dialog';
@@ -6,6 +7,8 @@ import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import * as React from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { AppPropType } from 'propTypes/propTypes';
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
import InputCreator from 'components/InputCreator';
import * as URLS from 'config/urls';
@@ -13,7 +16,8 @@ import useAuthenticateApp from 'hooks/useAuthenticateApp.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import { generateExternalLink } from 'helpers/translationValues';
import { Form } from './style';
export default function AddAppConnection(props) {
function AddAppConnection(props) {
const { application, connectionId, onClose } = props;
const { name, authDocUrl, key, auth } = application;
const navigate = useNavigate();
@@ -137,3 +141,11 @@ export default function AddAppConnection(props) {
</Dialog>
);
}
AddAppConnection.propTypes = {
onClose: PropTypes.func.isRequired,
application: AppPropType.isRequired,
connectionId: PropTypes.string,
};
export default AddAppConnection;

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useLazyQuery } from '@apollo/client';
import { Link } from 'react-router-dom';
import debounce from 'lodash/debounce';
@@ -23,13 +24,14 @@ import * as URLS from 'config/urls';
import AppIcon from 'components/AppIcon';
import { GET_APPS } from 'graphql/queries/get-apps';
import useFormatMessage from 'hooks/useFormatMessage';
function createConnectionOrFlow(appKey, supportsConnections = false) {
if (!supportsConnections) {
return URLS.CREATE_FLOW_WITH_APP(appKey);
}
return URLS.APP_ADD_CONNECTION(appKey);
}
export default function AddNewAppConnection(props) {
function AddNewAppConnection(props) {
const { onClose } = props;
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('sm'));
@@ -136,3 +138,9 @@ export default function AddNewAppConnection(props) {
</Dialog>
);
}
AddNewAppConnection.propTypes = {
onClose: PropTypes.func.isRequired,
};
export default AddNewAppConnection;

View File

@@ -1,4 +1,5 @@
import React from 'react';
import * as React from 'react';
import PropTypes from 'prop-types';
import LoadingButton from '@mui/lab/LoadingButton';
import Alert from '@mui/material/Alert';
import Dialog from '@mui/material/Dialog';
@@ -6,12 +7,16 @@ import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import CircularProgress from '@mui/material/CircularProgress';
import { ApolloError } from '@apollo/client';
import { FieldPropType } from 'propTypes/propTypes';
import useFormatMessage from 'hooks/useFormatMessage';
import InputCreator from 'components/InputCreator';
import Switch from 'components/Switch';
import TextField from 'components/TextField';
import { Form } from './style';
export default function AdminApplicationAuthClientDialog(props) {
function AdminApplicationAuthClientDialog(props) {
const {
error,
onClose,
@@ -80,3 +85,17 @@ export default function AdminApplicationAuthClientDialog(props) {
</Dialog>
);
}
AdminApplicationAuthClientDialog.propTypes = {
error: PropTypes.instanceOf(ApolloError),
onClose: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
loading: PropTypes.bool.isRequired,
submitHandler: PropTypes.func.isRequired,
authFields: PropTypes.arrayOf(FieldPropType),
submitting: PropTypes.bool.isRequired,
defaultValues: PropTypes.object.isRequired,
disabled: PropTypes.bool,
};
export default AdminApplicationAuthClientDialog;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import CircularProgress from '@mui/material/CircularProgress';
import Stack from '@mui/material/Stack';
@@ -11,6 +12,7 @@ import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useAppAuthClients from 'hooks/useAppAuthClients.ee';
import NoResultFound from 'components/NoResultFound';
function AdminApplicationAuthClients(props) {
const { appKey } = props;
const formatMessage = useFormatMessage();
@@ -72,4 +74,9 @@ function AdminApplicationAuthClients(props) {
</div>
);
}
AdminApplicationAuthClients.propTypes = {
appKey: PropTypes.string.isRequired,
};
export default AdminApplicationAuthClients;

View File

@@ -1,11 +1,15 @@
import PropTypes from 'prop-types';
import React, { useCallback, useMemo } from 'react';
import { useMutation } from '@apollo/client';
import { AppPropType } from 'propTypes/propTypes';
import { CREATE_APP_CONFIG } from 'graphql/mutations/create-app-config';
import { CREATE_APP_AUTH_CLIENT } from 'graphql/mutations/create-app-auth-client';
import useAppConfig from 'hooks/useAppConfig.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
export default function AdminApplicationCreateAuthClient(props) {
function AdminApplicationCreateAuthClient(props) {
const { appKey, application, onClose } = props;
const { auth } = application;
const formatMessage = useFormatMessage();
@@ -87,3 +91,11 @@ export default function AdminApplicationCreateAuthClient(props) {
/>
);
}
AdminApplicationCreateAuthClient.propTypes = {
appKey: PropTypes.string.isRequired,
application: AppPropType.isRequired,
onClose: PropTypes.func.isRequired,
};
export default AdminApplicationCreateAuthClient;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import { useMemo } from 'react';
import useAppConfig from 'hooks/useAppConfig.ee';
import useFormatMessage from 'hooks/useFormatMessage';
@@ -11,6 +12,7 @@ import { UPDATE_APP_CONFIG } from 'graphql/mutations/update-app-config';
import Form from 'components/Form';
import { Switch } from './style';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
function AdminApplicationSettings(props) {
const { appConfig, loading } = useAppConfig(props.appKey);
const [createAppConfig, { loading: loadingCreateAppConfig }] = useMutation(
@@ -109,4 +111,9 @@ function AdminApplicationSettings(props) {
></Form>
);
}
AdminApplicationSettings.propTypes = {
appKey: PropTypes.string.isRequired,
};
export default AdminApplicationSettings;

View File

@@ -1,13 +1,15 @@
import PropTypes from 'prop-types';
import React, { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { useMutation } from '@apollo/client';
import { AppPropType } from 'propTypes/propTypes';
import { UPDATE_APP_AUTH_CLIENT } from 'graphql/mutations/update-app-auth-client';
import useFormatMessage from 'hooks/useFormatMessage';
import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog';
import useAdminAppAuthClient from 'hooks/useAdminAppAuthClient.ee';
export default function AdminApplicationUpdateAuthClient(props) {
function AdminApplicationUpdateAuthClient(props) {
const { application, onClose } = props;
const { auth } = application;
const formatMessage = useFormatMessage();
@@ -87,3 +89,10 @@ export default function AdminApplicationUpdateAuthClient(props) {
/>
);
}
AdminApplicationUpdateAuthClient.propTypes = {
application: AppPropType.isRequired,
onClose: PropTypes.func.isRequired,
};
export default AdminApplicationUpdateAuthClient;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
import GroupIcon from '@mui/icons-material/Group';
import GroupsIcon from '@mui/icons-material/Groups';
@@ -14,6 +15,7 @@ import Drawer from 'components/Drawer';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useCurrentUserAbility from 'hooks/useCurrentUserAbility';
function createDrawerLinks({
canReadRole,
canReadUser,
@@ -65,7 +67,8 @@ function createDrawerLinks({
].filter(Boolean);
return items;
}
export default function SettingsLayout({ children }) {
function SettingsLayout({ children }) {
const theme = useTheme();
const formatMessage = useFormatMessage();
const currentUserAbility = useCurrentUserAbility();
@@ -83,7 +86,6 @@ export default function SettingsLayout({ children }) {
currentUserAbility.can('create', 'SamlAuthProvider'),
canUpdateApp: currentUserAbility.can('update', 'App'),
});
const a = 123;
const drawerBottomLinks = [
{
Icon: ArrowBackIosNewIcon,
@@ -118,3 +120,9 @@ export default function SettingsLayout({ children }) {
</>
);
}
SettingsLayout.propTypes = {
children: PropTypes.node.isRequired,
};
export default SettingsLayout;

View File

@@ -3,6 +3,7 @@ import * as React from 'react';
import { mutateAndGetClient } from 'graphql/client';
import useAuthentication from 'hooks/useAuthentication';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
const ApolloProvider = (props) => {
const enqueueSnackbar = useEnqueueSnackbar();
const authentication = useAuthentication();
@@ -25,4 +26,5 @@ const ApolloProvider = (props) => {
}, [onError, authentication]);
return <BaseApolloProvider client={client} {...props} />;
};
export default ApolloProvider;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import List from '@mui/material/List';
@@ -7,7 +8,8 @@ import ListItemText from '@mui/material/ListItemText';
import * as React from 'react';
import useAppAuthClients from 'hooks/useAppAuthClients.ee';
import useFormatMessage from 'hooks/useFormatMessage';
export default function AppAuthClientsDialog(props) {
function AppAuthClientsDialog(props) {
const { appKey, onClientClick, onClose } = props;
const { appAuthClients } = useAppAuthClients({ appKey, active: true });
const formatMessage = useFormatMessage();
@@ -37,3 +39,11 @@ export default function AppAuthClientsDialog(props) {
</Dialog>
);
}
AppAuthClientsDialog.propTypes = {
appKey: PropTypes.string.isRequired,
onClientClick: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};
export default AppAuthClientsDialog;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import MenuIcon from '@mui/icons-material/Menu';
import MenuOpenIcon from '@mui/icons-material/MenuOpen';
@@ -13,8 +14,10 @@ import Logo from 'components/Logo/index';
import TrialStatusBadge from 'components/TrialStatusBadge/index.ee';
import * as URLS from 'config/urls';
import { Link } from './style';
const accountMenuId = 'account-menu';
export default function AppBar(props) {
function AppBar(props) {
const { drawerOpen, onDrawerOpen, onDrawerClose, maxWidth = false } = props;
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
@@ -73,3 +76,15 @@ export default function AppBar(props) {
</MuiAppBar>
);
}
AppBar.propTypes = {
drawerOpen: PropTypes.bool.isRequired,
onDrawerOpen: PropTypes.func.isRequired,
onDrawerClose: PropTypes.func.isRequired,
maxWidth: PropTypes.oneOfType([
PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl', false]),
PropTypes.string,
]),
};
export default AppBar;

View File

@@ -1,10 +1,14 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { Link } from 'react-router-dom';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
export default function ContextMenu(props) {
import { ConnectionPropType } from 'propTypes/propTypes';
function ContextMenu(props) {
const {
appKey,
connection,
@@ -61,3 +65,17 @@ export default function ContextMenu(props) {
</Menu>
);
}
ContextMenu.propTypes = {
appKey: PropTypes.string.isRequired,
connection: ConnectionPropType.isRequired,
onClose: PropTypes.func.isRequired,
onMenuItemClick: PropTypes.func.isRequired,
anchorEl: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
]),
disableReconnection: PropTypes.bool.isRequired,
};
export default ContextMenu;

View File

@@ -14,7 +14,9 @@ import ConnectionContextMenu from 'components/AppConnectionContextMenu';
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
import { TEST_CONNECTION } from 'graphql/queries/test-connection';
import useFormatMessage from 'hooks/useFormatMessage';
import { ConnectionPropType } from 'propTypes/propTypes';
import { CardContent, Typography } from './style';
const countTranslation = (value) => (
<>
<Typography variant="body1">{value}</Typography>
@@ -166,4 +168,9 @@ function AppConnectionRow(props) {
</>
);
}
AppConnectionRow.propTypes = {
connection: ConnectionPropType.isRequired,
};
export default AppConnectionRow;

View File

@@ -1,11 +1,13 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useQuery } from '@apollo/client';
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
import AppConnectionRow from 'components/AppConnectionRow';
import NoResultFound from 'components/NoResultFound';
import useFormatMessage from 'hooks/useFormatMessage';
import * as URLS from 'config/urls';
export default function AppConnections(props) {
function AppConnections(props) {
const { appKey } = props;
const formatMessage = useFormatMessage();
const { data } = useQuery(GET_APP_CONNECTIONS, {
@@ -22,6 +24,7 @@ export default function AppConnections(props) {
/>
);
}
return (
<>
{appConnections.map((appConnection) => (
@@ -30,3 +33,9 @@ export default function AppConnections(props) {
</>
);
}
AppConnections.propTypes = {
appKey: PropTypes.string.isRequired,
};
export default AppConnections;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import { useQuery } from '@apollo/client';
import { Link, useSearchParams } from 'react-router-dom';
import { GET_FLOWS } from 'graphql/queries/get-flows';
@@ -12,7 +13,7 @@ const getLimitAndOffset = (page) => ({
limit: FLOW_PER_PAGE,
offset: (page - 1) * FLOW_PER_PAGE,
});
export default function AppFlows(props) {
function AppFlows(props) {
const { appKey } = props;
const formatMessage = useFormatMessage();
const [searchParams, setSearchParams] = useSearchParams();
@@ -62,3 +63,9 @@ export default function AppFlows(props) {
</>
);
}
AppFlows.propTypes = {
appKey: PropTypes.string.isRequired,
};
export default AppFlows;

View File

@@ -1,9 +1,10 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Avatar from '@mui/material/Avatar';
const inlineImgStyle = {
objectFit: 'contain',
};
export default function AppIcon(props) {
function AppIcon(props) {
const { name, url, color, sx = {}, variant = 'square', ...restProps } = props;
const initialLetter = name?.[0];
return (
@@ -14,8 +15,22 @@ export default function AppIcon(props) {
imgProps={{ style: inlineImgStyle }}
src={url}
alt={name}
children={initialLetter}
{...restProps}
/>
>
{initialLetter}
</Avatar>
);
}
AppIcon.propTypes = {
name: PropTypes.string,
url: PropTypes.string,
color: PropTypes.string,
variant: PropTypes.oneOfType([
PropTypes.oneOf(['circular', 'rounded', 'square']),
PropTypes.string,
]),
sx: PropTypes.object,
};
export default AppIcon;

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import Card from '@mui/material/Card';
import Box from '@mui/material/Box';
@@ -6,13 +7,16 @@ import CardActionArea from '@mui/material/CardActionArea';
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import useFormatMessage from 'hooks/useFormatMessage';
import AppIcon from 'components/AppIcon';
import { AppPropType } from 'propTypes/propTypes';
import { CardContent, Typography } from './style';
const countTranslation = (value) => (
<>
<Typography variant="body1">{value}</Typography>
<br />
</>
);
function AppRow(props) {
const formatMessage = useFormatMessage();
const { name, primaryColor, iconUrl, connectionCount, flowCount } =
@@ -65,4 +69,10 @@ function AppRow(props) {
</Link>
);
}
AppRow.propTypes = {
application: AppPropType.isRequired,
url: PropTypes.string.isRequired,
};
export default AppRow;

View File

@@ -1,7 +1,18 @@
import PropTypes from 'prop-types';
import { Can as OriginalCan } from '@casl/react';
import * as React from 'react';
import useCurrentUserAbility from 'hooks/useCurrentUserAbility';
export default function Can(props) {
function Can(props) {
const currentUserAbility = useCurrentUserAbility();
return <OriginalCan ability={currentUserAbility} {...props} />;
}
Can.propTypes = {
I: PropTypes.string.isRequired,
a: PropTypes.string,
an: PropTypes.string,
passThrough: PropTypes.bool,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
};
export default Can;

View File

@@ -3,6 +3,7 @@ import { useLocation } from 'react-router-dom';
import Alert from '@mui/material/Alert';
import Typography from '@mui/material/Typography';
import useFormatMessage from 'hooks/useFormatMessage';
export default function CheckoutCompletedAlert() {
const formatMessage = useFormatMessage();
const location = useLocation();

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
@@ -11,6 +12,8 @@ import useFormatMessage from 'hooks/useFormatMessage';
import useApps from 'hooks/useApps';
import { EditorContext } from 'contexts/Editor';
import FlowSubstepTitle from 'components/FlowSubstepTitle';
import { StepPropType, SubstepPropType } from 'propTypes/propTypes';
const optionGenerator = (app) => ({
label: app.name,
value: app.key,
@@ -222,4 +225,15 @@ function ChooseAppAndEventSubstep(props) {
</React.Fragment>
);
}
ChooseAppAndEventSubstep.propTypes = {
substep: SubstepPropType.isRequired,
expanded: PropTypes.bool,
onExpand: PropTypes.func.isRequired,
onCollapse: PropTypes.func.isRequired,
step: StepPropType.isRequired,
onSubmit: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};
export default ChooseAppAndEventSubstep;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import { useLazyQuery, useQuery } from '@apollo/client';
import Autocomplete from '@mui/material/Autocomplete';
import Button from '@mui/material/Button';
@@ -14,14 +15,23 @@ import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
import { TEST_CONNECTION } from 'graphql/queries/test-connection';
import useAuthenticateApp from 'hooks/useAuthenticateApp.ee';
import useFormatMessage from 'hooks/useFormatMessage';
import {
AppPropType,
StepPropType,
SubstepPropType,
} from 'propTypes/propTypes';
const ADD_CONNECTION_VALUE = 'ADD_CONNECTION';
const ADD_SHARED_CONNECTION_VALUE = 'ADD_SHARED_CONNECTION';
const optionGenerator = (connection) => ({
label: connection?.formattedData?.screenName ?? 'Unnamed',
value: connection?.id,
});
const getOption = (options, connectionId) =>
options.find((connection) => connection.value === connectionId) || null;
function ChooseConnectionSubstep(props) {
const {
substep,
@@ -237,4 +247,16 @@ function ChooseConnectionSubstep(props) {
</React.Fragment>
);
}
ChooseConnectionSubstep.propTypes = {
application: AppPropType.isRequired,
substep: SubstepPropType.isRequired,
expanded: PropTypes.bool,
onExpand: PropTypes.func.isRequired,
onCollapse: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
step: StepPropType.isRequired,
};
export default ChooseConnectionSubstep;

View File

@@ -1,7 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from './style';
const BG_IMAGE_FALLBACK =
'linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(135deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(135deg, transparent 75%, #ccc 75%) /*! @noflip */';
const ColorButton = (props) => {
const {
bgColor,
@@ -24,4 +27,12 @@ const ColorButton = (props) => {
/>
);
};
ColorButton.propTypes = {
bgColor: PropTypes.string.isRequired,
className: PropTypes.string,
disablePopover: PropTypes.bool.isRequired,
isBgColorValid: PropTypes.bool.isRequired,
};
export default ColorButton;

View File

@@ -1,8 +1,10 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { Controller, useFormContext } from 'react-hook-form';
import { MuiColorInput } from 'mui-color-input';
import ColorButton from './ColorButton';
export default function ColorInput(props) {
function ColorInput(props) {
const { control } = useFormContext();
const {
required,
@@ -32,3 +34,12 @@ export default function ColorInput(props) {
/>
);
}
ColorInput.propTypes = {
shouldUnregister: PropTypes.bool,
name: PropTypes.string.isRequired,
disabled: PropTypes.bool,
required: PropTypes.bool,
};
export default ColorInput;

View File

@@ -1,9 +1,11 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import Button from '@mui/material/Button';
import { IconButton } from './style';
export default function ConditionalIconButton(props) {
function ConditionalIconButton(props) {
const { icon, ...buttonProps } = props;
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
@@ -24,3 +26,9 @@ export default function ConditionalIconButton(props) {
}
return <Button {...buttonProps} />;
}
ConditionalIconButton.propTypes = {
icon: PropTypes.node.isRequired,
};
export default ConditionalIconButton;

View File

@@ -1,18 +1,20 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
export default function ConfirmationDialog(props) {
function ConfirmationDialog(props) {
const {
onClose,
onConfirm,
title,
description,
cancelButtonChildren,
confirmButtionChildren,
confirmButtonChildren,
open = true,
} = props;
const dataTest = props['data-test'];
@@ -32,16 +34,29 @@ export default function ConfirmationDialog(props) {
</Button>
)}
{confirmButtionChildren && onConfirm && (
{confirmButtonChildren && onConfirm && (
<Button
onClick={onConfirm}
color="error"
data-test="confirmation-confirm-button"
>
{confirmButtionChildren}
{confirmButtonChildren}
</Button>
)}
</DialogActions>
</Dialog>
);
}
ConfirmationDialog.propTypes = {
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
title: PropTypes.node.isRequired,
description: PropTypes.node.isRequired,
cancelButtonChildren: PropTypes.node.isRequired,
confirmButtonChildren: PropTypes.node.isRequired,
open: PropTypes.bool,
'data-test': PropTypes.string,
};
export default ConfirmationDialog;

View File

@@ -1,8 +1,10 @@
import * as React from 'react';
import MuiContainer from '@mui/material/Container';
export default function Container(props) {
return <MuiContainer {...props} />;
}
Container.defaultProps = {
maxWidth: 'lg',
};

View File

@@ -1,10 +1,13 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import FormHelperText from '@mui/material/FormHelperText';
import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
import Typography from '@mui/material/Typography';
const getOption = (options, value) =>
options.find((option) => option.value === value) || null;
// Enables filtering by value in autocomplete dropdown
const filterOptions = createFilterOptions({
stringify: ({ label, value }) => `
@@ -12,6 +15,7 @@ const filterOptions = createFilterOptions({
${value}
`,
});
function ControlledAutocomplete(props) {
const { control, watch, setValue, resetField } = useFormContext();
const {
@@ -113,4 +117,18 @@ function ControlledAutocomplete(props) {
/>
);
}
ControlledAutocomplete.propTypes = {
shouldUnregister: PropTypes.bool,
name: PropTypes.string.isRequired,
required: PropTypes.bool,
showOptionValue: PropTypes.bool,
description: PropTypes.string,
dependsOn: PropTypes.arrayOf(PropTypes.string),
defaultValue: PropTypes.any,
onBlur: PropTypes.func,
onChange: PropTypes.func,
options: PropTypes.array,
};
export default ControlledAutocomplete;

View File

@@ -1,7 +1,9 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { Controller, useFormContext } from 'react-hook-form';
import Checkbox from '@mui/material/Checkbox';
export default function ControlledCheckbox(props) {
function ControlledCheckbox(props) {
const { control } = useFormContext();
const {
required,
@@ -52,3 +54,15 @@ export default function ControlledCheckbox(props) {
/>
);
}
ControlledCheckbox.propTypes = {
name: PropTypes.string.isRequired,
defaultValue: PropTypes.bool,
dataTest: PropTypes.string,
required: PropTypes.bool,
disabled: PropTypes.bool,
onBlur: PropTypes.func,
onChange: PropTypes.func,
};
export default ControlledCheckbox;

View File

@@ -1,5 +1,7 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { Controller as RHFController, useFormContext } from 'react-hook-form';
function Controller(props) {
const { control } = useFormContext();
const {
@@ -20,4 +22,13 @@ function Controller(props) {
/>
);
}
Controller.propTypes = {
defaultValue: PropTypes.string,
name: PropTypes.string.isRequired,
required: PropTypes.bool,
shouldUnregister: PropTypes.bool,
children: PropTypes.element.isRequired,
};
export default Controller;

View File

@@ -1,11 +1,14 @@
import PropTypes from 'prop-types';
import Paper from '@mui/material/Paper';
import Popper from '@mui/material/Popper';
import Tab from '@mui/material/Tab';
import * as React from 'react';
import Suggestions from 'components/PowerInput/Suggestions';
import TabPanel from 'components/TabPanel';
import { FieldDropdownOptionPropType } from 'propTypes/propTypes';
import Options from './Options';
import { Tabs } from './style';
const CustomOptions = (props) => {
const {
open,
@@ -69,4 +72,23 @@ const CustomOptions = (props) => {
</Popper>
);
};
CustomOptions.propTypes = {
open: PropTypes.bool.isRequired,
anchorEl: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired,
data: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
output: PropTypes.arrayOf(PropTypes.object).isRequired,
}),
).isRequired,
options: PropTypes.arrayOf(FieldDropdownOptionPropType).isRequired,
onSuggestionClick: PropTypes.func.isRequired,
onOptionClick: PropTypes.func.isRequired,
onTabChange: PropTypes.func.isRequired,
label: PropTypes.string,
initialTabIndex: PropTypes.oneOf([0, 1]),
};
export default CustomOptions;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import throttle from 'lodash/throttle';
@@ -7,12 +8,16 @@ import { Typography } from '@mui/material';
import SearchInput from 'components/SearchInput';
import useFormatMessage from 'hooks/useFormatMessage';
import { SearchInputWrapper } from './style';
import { FieldDropdownOptionPropType } from 'propTypes/propTypes';
const SHORT_LIST_LENGTH = 4;
const LIST_ITEM_HEIGHT = 64;
const computeListHeight = (currentLength) => {
const numberOfRenderedItems = Math.min(SHORT_LIST_LENGTH, currentLength);
return LIST_ITEM_HEIGHT * numberOfRenderedItems;
};
const renderItemFactory =
({ onOptionClick }) =>
(props) => {
@@ -44,6 +49,7 @@ const renderItemFactory =
</ListItemButton>
);
};
const Options = (props) => {
const formatMessage = useFormatMessage();
const { data, onOptionClick } = props;
@@ -108,4 +114,10 @@ const Options = (props) => {
</>
);
};
Options.propTypes = {
data: PropTypes.arrayOf(FieldDropdownOptionPropType).isRequired,
onOptionClick: PropTypes.func.isRequired,
};
export default Options;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { useController, useFormContext } from 'react-hook-form';
import { IconButton } from '@mui/material';
@@ -28,6 +29,7 @@ import {
import CustomOptions from './CustomOptions';
import { processStepWithExecutions } from 'components/PowerInput/data';
import { StepExecutionsContext } from 'contexts/StepExecutions';
function ControlledCustomAutocomplete(props) {
const {
defaultValue = '',
@@ -257,4 +259,23 @@ function ControlledCustomAutocomplete(props) {
</Slate>
);
}
ControlledCustomAutocomplete.propTypes = {
options: PropTypes.array,
loading: PropTypes.bool.isRequired,
showOptionValue: PropTypes.bool,
dependsOn: PropTypes.arrayOf(PropTypes.string),
defaultValue: PropTypes.string,
name: PropTypes.string.isRequired,
label: PropTypes.string,
type: PropTypes.string,
required: PropTypes.bool,
readOnly: PropTypes.bool,
description: PropTypes.string,
docUrl: PropTypes.string,
clickToCopy: PropTypes.bool,
disabled: PropTypes.bool,
shouldUnregister: PropTypes.bool,
};
export default ControlledCustomAutocomplete;

View File

@@ -1,5 +1,6 @@
import useConfig from 'hooks/useConfig';
import { LogoImage } from './style.ee';
const CustomLogo = () => {
const { config, loading } = useConfig(['logo.svgData']);
if (loading || !config?.['logo.svgData']) return null;
@@ -11,4 +12,5 @@ const CustomLogo = () => {
/>
);
};
export default CustomLogo;

View File

@@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
import MationLogo from 'components/MationLogo';
import useAutomatischInfo from 'hooks/useAutomatischInfo';
export default function DefaultLogo() {
const DefaultLogo = () => {
const { data: automatischInfo, isPending } = useAutomatischInfo();
const isMation = automatischInfo?.data.isMation;
@@ -16,4 +16,6 @@ export default function DefaultLogo() {
<FormattedMessage id="brandText" />
</Typography>
);
}
};
export default DefaultLogo;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from '@apollo/client';
@@ -8,7 +9,8 @@ import { DELETE_CURRENT_USER } from 'graphql/mutations/delete-current-user.ee';
import useAuthentication from 'hooks/useAuthentication';
import useFormatMessage from 'hooks/useFormatMessage';
import useCurrentUser from 'hooks/useCurrentUser';
export default function DeleteAccountDialog(props) {
function DeleteAccountDialog(props) {
const [deleteCurrentUser] = useMutation(DELETE_CURRENT_USER);
const formatMessage = useFormatMessage();
const currentUser = useCurrentUser();
@@ -27,7 +29,13 @@ export default function DeleteAccountDialog(props) {
onClose={props.onClose}
onConfirm={handleConfirm}
cancelButtonChildren={formatMessage('deleteAccountDialog.cancel')}
confirmButtionChildren={formatMessage('deleteAccountDialog.confirm')}
confirmButtonChildren={formatMessage('deleteAccountDialog.confirm')}
/>
);
}
DeleteAccountDialog.propTypes = {
onClose: PropTypes.func.isRequired,
};
export default DeleteAccountDialog;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import { useMutation } from '@apollo/client';
import DeleteIcon from '@mui/icons-material/Delete';
import IconButton from '@mui/material/IconButton';
@@ -7,7 +8,8 @@ import Can from 'components/Can';
import ConfirmationDialog from 'components/ConfirmationDialog';
import { DELETE_ROLE } from 'graphql/mutations/delete-role.ee';
import useFormatMessage from 'hooks/useFormatMessage';
export default function DeleteRoleButton(props) {
function DeleteRoleButton(props) {
const { disabled, roleId } = props;
const [showConfirmation, setShowConfirmation] = React.useState(false);
const [deleteRole] = useMutation(DELETE_ROLE, {
@@ -52,9 +54,16 @@ export default function DeleteRoleButton(props) {
onClose={() => setShowConfirmation(false)}
onConfirm={handleConfirm}
cancelButtonChildren={formatMessage('deleteRoleButton.cancel')}
confirmButtionChildren={formatMessage('deleteRoleButton.confirm')}
confirmButtonChildren={formatMessage('deleteRoleButton.confirm')}
data-test="delete-role-modal"
/>
</>
);
}
DeleteRoleButton.propTypes = {
disabled: PropTypes.bool,
roleId: PropTypes.string.isRequired,
};
export default DeleteRoleButton;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import { useMutation } from '@apollo/client';
import DeleteIcon from '@mui/icons-material/Delete';
import IconButton from '@mui/material/IconButton';
@@ -6,7 +7,8 @@ import * as React from 'react';
import ConfirmationDialog from 'components/ConfirmationDialog';
import { DELETE_USER } from 'graphql/mutations/delete-user.ee';
import useFormatMessage from 'hooks/useFormatMessage';
export default function DeleteUserButton(props) {
function DeleteUserButton(props) {
const { userId } = props;
const [showConfirmation, setShowConfirmation] = React.useState(false);
const [deleteUser] = useMutation(DELETE_USER, {
@@ -46,9 +48,15 @@ export default function DeleteUserButton(props) {
onClose={() => setShowConfirmation(false)}
onConfirm={handleConfirm}
cancelButtonChildren={formatMessage('deleteUserButton.cancel')}
confirmButtionChildren={formatMessage('deleteUserButton.confirm')}
confirmButtonChildren={formatMessage('deleteUserButton.confirm')}
data-test="delete-user-modal"
/>
</>
);
}
DeleteUserButton.propTypes = {
userId: PropTypes.string.isRequired,
};
export default DeleteUserButton;

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useTheme } from '@mui/material/styles';
import Toolbar from '@mui/material/Toolbar';
import List from '@mui/material/List';
@@ -8,10 +9,12 @@ import Badge from '@mui/material/Badge';
import ListItemLink from 'components/ListItemLink';
import useFormatMessage from 'hooks/useFormatMessage';
import { Drawer as BaseDrawer } from './style';
const iOS =
typeof navigator !== 'undefined' &&
/iPad|iPhone|iPod/.test(navigator.userAgent);
export default function Drawer(props) {
function Drawer(props) {
const { links = [], bottomLinks = [], ...drawerProps } = props;
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'));
@@ -70,3 +73,20 @@ export default function Drawer(props) {
</BaseDrawer>
);
}
const DrawerLinkPropTypes = PropTypes.shape({
Icon: PropTypes.elementType.isRequired,
primary: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
target: PropTypes.oneOf(['_blank']),
badgeContent: PropTypes.node,
dataTest: PropTypes.string,
});
Drawer.propTypes = {
links: PropTypes.arrayOf(DrawerLinkPropTypes).isRequired,
bottomLinks: PropTypes.arrayOf(DrawerLinkPropTypes),
onClose: PropTypes.func.isRequired,
};
export default Drawer;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { v4 as uuidv4 } from 'uuid';
import { useFormContext, useWatch } from 'react-hook-form';
@@ -9,6 +10,8 @@ import RemoveIcon from '@mui/icons-material/Remove';
import AddIcon from '@mui/icons-material/Add';
import InputCreator from 'components/InputCreator';
import { EditorContext } from 'contexts/Editor';
import { FieldsPropType } from 'propTypes/propTypes';
function DynamicField(props) {
const { label, description, fields, name, defaultValue, stepId } = props;
const { control, setValue, getValues } = useFormContext();
@@ -107,4 +110,23 @@ function DynamicField(props) {
</React.Fragment>
);
}
DynamicField.propTypes = {
onChange: PropTypes.func,
onBlur: PropTypes.func,
defaultValue: PropTypes.arrayOf(PropTypes.object),
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
type: PropTypes.string,
required: PropTypes.bool,
readOnly: PropTypes.bool,
description: PropTypes.string,
docUrl: PropTypes.string,
clickToCopy: PropTypes.bool,
disabled: PropTypes.bool,
fields: FieldsPropType.isRequired,
shouldUnregister: PropTypes.bool,
stepId: PropTypes.string,
};
export default DynamicField;

View File

@@ -1,8 +1,11 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import Typography from '@mui/material/Typography';
import EditIcon from '@mui/icons-material/Edit';
import { Box, TextField } from './style';
const noop = () => null;
function EditableTypography(props) {
const { children, onConfirm = noop, sx, ...typographyProps } = props;
const [editing, setEditing] = React.useState(false);
@@ -55,4 +58,11 @@ function EditableTypography(props) {
</Box>
);
}
EditableTypography.propTypes = {
children: PropTypes.string.isRequired,
onConfirm: PropTypes.func,
sx: PropTypes.object,
};
export default EditableTypography;

View File

@@ -7,6 +7,8 @@ import { GET_FLOW } from 'graphql/queries/get-flow';
import { CREATE_STEP } from 'graphql/mutations/create-step';
import { UPDATE_STEP } from 'graphql/mutations/update-step';
import FlowStep from 'components/FlowStep';
import { FlowPropType } from 'propTypes/propTypes';
function updateHandlerFactory(flowId, previousStepId) {
return function createStepUpdateHandler(cache, mutationResult) {
const { data } = mutationResult;
@@ -28,7 +30,8 @@ function updateHandlerFactory(flowId, previousStepId) {
});
};
}
export default function Editor(props) {
function Editor(props) {
const [updateStep] = useMutation(UPDATE_STEP);
const [createStep, { loading: creationInProgress }] = useMutation(
CREATE_STEP,
@@ -118,3 +121,9 @@ export default function Editor(props) {
</Box>
);
}
Editor.propTypes = {
flow: FlowPropType.isRequired,
};
export default Editor;

View File

@@ -17,6 +17,7 @@ import { UPDATE_FLOW_STATUS } from 'graphql/mutations/update-flow-status';
import { UPDATE_FLOW } from 'graphql/mutations/update-flow';
import { GET_FLOW } from 'graphql/queries/get-flow';
import * as URLS from 'config/urls';
export default function EditorLayout() {
const { flowId } = useParams();
const formatMessage = useFormatMessage();

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { DateTime } from 'luxon';
import Stack from '@mui/material/Stack';
@@ -5,6 +6,8 @@ import Box from '@mui/material/Box';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import useFormatMessage from 'hooks/useFormatMessage';
import { ExecutionPropType } from 'propTypes/propTypes';
function ExecutionName(props) {
return (
<Typography variant="h3" gutterBottom>
@@ -12,6 +15,11 @@ function ExecutionName(props) {
</Typography>
);
}
ExecutionName.propTypes = {
name: PropTypes.string.isRequired,
};
function ExecutionId(props) {
const formatMessage = useFormatMessage();
const id = (
@@ -27,6 +35,11 @@ function ExecutionId(props) {
</Box>
);
}
ExecutionId.propTypes = {
id: PropTypes.string.isRequired,
};
function ExecutionDate(props) {
const createdAt = DateTime.fromMillis(parseInt(props.createdAt, 10));
const relativeCreatedAt = createdAt.toRelative();
@@ -40,7 +53,13 @@ function ExecutionDate(props) {
</Tooltip>
);
}
export default function ExecutionHeader(props) {
ExecutionDate.propTypes = {
createdAt: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)])
.isRequired,
};
function ExecutionHeader(props) {
const { execution } = props;
if (!execution) return <React.Fragment />;
return (
@@ -59,3 +78,9 @@ export default function ExecutionHeader(props) {
</Stack>
);
}
ExecutionHeader.propTypes = {
execution: ExecutionPropType,
};
export default ExecutionHeader;

View File

@@ -9,9 +9,12 @@ import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import FlowAppIcons from 'components/FlowAppIcons';
import { Apps, CardContent, ArrowContainer, Title, Typography } from './style';
export default function ExecutionRow(props) {
import { ExecutionPropType } from 'propTypes/propTypes';
function ExecutionRow(props) {
const formatMessage = useFormatMessage();
const { execution } = props;
const { flow } = execution;
const createdAt = DateTime.fromMillis(parseInt(execution.createdAt, 10));
const relativeCreatedAt = createdAt.toRelative();
@@ -66,3 +69,9 @@ export default function ExecutionRow(props) {
</Link>
);
}
ExecutionRow.propTypes = {
execution: ExecutionPropType.isRequired,
};
export default ExecutionRow;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { DateTime } from 'luxon';
import Stack from '@mui/material/Stack';
@@ -21,6 +22,8 @@ import {
Metadata,
Wrapper,
} from './style';
import { ExecutionStepPropType, StepPropType } from 'propTypes/propTypes';
function ExecutionStepId(props) {
const formatMessage = useFormatMessage();
const id = (
@@ -36,6 +39,11 @@ function ExecutionStepId(props) {
</Box>
);
}
ExecutionStepId.propTypes = {
id: PropTypes.string.isRequired,
};
function ExecutionStepDate(props) {
const formatMessage = useFormatMessage();
const createdAt = DateTime.fromMillis(parseInt(props.createdAt, 10));
@@ -52,9 +60,15 @@ function ExecutionStepDate(props) {
</Tooltip>
);
}
ExecutionStepDate.propTypes = {
createdAt: PropTypes.string.isRequired,
};
const validIcon = <CheckCircleIcon color="success" />;
const errorIcon = <ErrorIcon color="error" />;
export default function ExecutionStep(props) {
function ExecutionStep(props) {
const { executionStep } = props;
const [activeTabIndex, setActiveTabIndex] = React.useState(0);
const step = executionStep.step;
@@ -136,3 +150,12 @@ export default function ExecutionStep(props) {
</Wrapper>
);
}
ExecutionStep.propTypes = {
collapsed: PropTypes.bool,
step: StepPropType.isRequired,
index: PropTypes.number,
executionStep: ExecutionStepPropType.isRequired,
};
export default ExecutionStep;

View File

@@ -1,7 +1,9 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import AppIcon from 'components/AppIcon';
import IntermediateStepCount from 'components/IntermediateStepCount';
export default function FlowAppIcons(props) {
function FlowAppIcons(props) {
const { steps } = props;
const stepsCount = steps.length;
const firstStep = steps[0];
@@ -31,3 +33,13 @@ export default function FlowAppIcons(props) {
</>
);
}
FlowAppIcons.propTypes = {
steps: PropTypes.arrayOf(
PropTypes.shape({
iconUrl: PropTypes.string,
}),
).isRequired,
};
export default FlowAppIcons;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import { useMutation } from '@apollo/client';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
@@ -9,7 +10,8 @@ import * as URLS from 'config/urls';
import { DELETE_FLOW } from 'graphql/mutations/delete-flow';
import { DUPLICATE_FLOW } from 'graphql/mutations/duplicate-flow';
import useFormatMessage from 'hooks/useFormatMessage';
export default function ContextMenu(props) {
function ContextMenu(props) {
const { flowId, onClose, anchorEl } = props;
const enqueueSnackbar = useEnqueueSnackbar();
const [deleteFlow] = useMutation(DELETE_FLOW);
@@ -80,3 +82,14 @@ export default function ContextMenu(props) {
</Menu>
);
}
ContextMenu.propTypes = {
flowId: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
anchorEl: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
]).isRequired,
};
export default ContextMenu;

View File

@@ -11,6 +11,8 @@ import FlowContextMenu from 'components/FlowContextMenu';
import useFormatMessage from 'hooks/useFormatMessage';
import * as URLS from 'config/urls';
import { Apps, CardContent, ContextMenu, Title, Typography } from './style';
import { FlowPropType } from 'propTypes/propTypes';
function getFlowStatusTranslationKey(status) {
if (status === 'published') {
return 'flow.published';
@@ -19,6 +21,7 @@ function getFlowStatusTranslationKey(status) {
}
return 'flow.draft';
}
function getFlowStatusColor(status) {
if (status === 'published') {
return 'success';
@@ -27,7 +30,8 @@ function getFlowStatusColor(status) {
}
return 'info';
}
export default function FlowRow(props) {
function FlowRow(props) {
const formatMessage = useFormatMessage();
const contextButtonRef = React.useRef(null);
const [anchorEl, setAnchorEl] = React.useState(null);
@@ -113,3 +117,9 @@ export default function FlowRow(props) {
</>
);
}
FlowRow.propTypes = {
flow: FlowPropType.isRequired,
};
export default FlowRow;

View File

@@ -1,3 +1,4 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { useLazyQuery } from '@apollo/client';
import Stack from '@mui/material/Stack';
@@ -33,8 +34,11 @@ import {
Wrapper,
} from './style';
import isEmpty from 'helpers/isEmpty';
import { StepPropType } from 'propTypes/propTypes';
const validIcon = <CheckCircleIcon color="success" />;
const errorIcon = <ErrorIcon color="error" />;
function generateValidationSchema(substeps) {
const fieldValidations = substeps?.reduce(
(allValidations, { arguments: args }) => {
@@ -91,7 +95,8 @@ function generateValidationSchema(substeps) {
});
return yupResolver(validationSchema);
}
export default function FlowStep(props) {
function FlowStep(props) {
const { collapsed, onChange, onContinue } = props;
const editorContext = React.useContext(EditorContext);
const contextButtonRef = React.useRef(null);
@@ -309,3 +314,15 @@ export default function FlowStep(props) {
</Wrapper>
);
}
FlowStep.propTypes = {
collapsed: PropTypes.bool,
step: StepPropType.isRequired,
index: PropTypes.number,
onOpen: PropTypes.func,
onClose: PropTypes.func,
onChange: PropTypes.func.isRequired,
onContinue: PropTypes.func,
};
export default FlowStep;

View File

@@ -1,9 +1,11 @@
import PropTypes from 'prop-types';
import * as React from 'react';
import { useMutation } from '@apollo/client';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import { DELETE_STEP } from 'graphql/mutations/delete-step';
import useFormatMessage from 'hooks/useFormatMessage';
function FlowStepContextMenu(props) {
const { stepId, onClose, anchorEl, deletable } = props;
const [deleteStep] = useMutation(DELETE_STEP, {
@@ -32,4 +34,12 @@ function FlowStepContextMenu(props) {
</Menu>
);
}
FlowStepContextMenu.propTypes = {
stepId: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
anchorEl: PropTypes.element.isRequired,
deletable: PropTypes.bool.isRequired,
};
export default FlowStepContextMenu;

View File

@@ -91,8 +91,8 @@ function TestSubstep(props) {
severity="error"
sx={{ mb: 2, fontWeight: 500, width: '100%' }}
>
{serializeErrors(error.graphQLErrors).map((error) => (
<div>{error.message}</div>
{serializeErrors(error.graphQLErrors).map((error, i) => (
<div key={i}>{error.message}</div>
))}
</Alert>
)}

View File

@@ -0,0 +1,479 @@
import PropTypes from 'prop-types';
export const JSONValuePropType = PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.object,
PropTypes.array,
]);
export const AuthenticationStepFieldPropType = PropTypes.shape({
name: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string]),
properties: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
),
});
export const AuthenticationStepPropType = PropTypes.shape({
type: PropTypes.oneOf(['mutation', 'openWithPopup']),
name: PropTypes.string,
arguments: PropTypes.arrayOf(AuthenticationStepFieldPropType),
});
export const FieldTextPropType = PropTypes.shape({
key: PropTypes.string,
label: PropTypes.string,
type: PropTypes.oneOf(['string']),
required: PropTypes.bool,
readOnly: PropTypes.bool,
value: PropTypes.string,
placeholder: PropTypes.oneOfType([PropTypes.string]),
description: PropTypes.string,
docUrl: PropTypes.string,
clickToCopy: PropTypes.bool,
variables: PropTypes.bool,
dependsOn: PropTypes.arrayOf(PropTypes.string),
});
export const FieldDropdownSourcePropType = PropTypes.shape({
type: PropTypes.string,
name: PropTypes.string,
arguments: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
),
});
export const FieldDropdownAdditionalFieldsPropType = PropTypes.shape({
type: PropTypes.string,
name: PropTypes.string,
arguments: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
value: PropTypes.string,
}),
),
});
export const FieldDropdownOptionPropType = PropTypes.shape({
label: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
PropTypes.number,
]),
});
export const FieldDropdownPropType = PropTypes.shape({
key: PropTypes.string,
label: PropTypes.string,
type: PropTypes.oneOf(['dropdown']),
required: PropTypes.bool,
readOnly: PropTypes.bool,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
placeholder: PropTypes.oneOfType([PropTypes.string]),
description: PropTypes.string,
docUrl: PropTypes.string,
clickToCopy: PropTypes.bool,
variables: PropTypes.bool,
dependsOn: PropTypes.arrayOf(PropTypes.string),
options: PropTypes.arrayOf(FieldDropdownOptionPropType),
source: FieldDropdownSourcePropType,
additionalFields: FieldDropdownAdditionalFieldsPropType,
});
export const FieldsPropType = PropTypes.arrayOf(
PropTypes.oneOfType([FieldDropdownPropType, FieldTextPropType]),
);
export const FieldDynamicPropType = PropTypes.shape({
key: PropTypes.string,
label: PropTypes.string,
type: PropTypes.oneOf(['dynamic']),
required: PropTypes.bool,
readOnly: PropTypes.bool,
description: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.object),
fields: FieldsPropType,
});
export const FieldPropType = PropTypes.oneOfType([
FieldDropdownPropType,
FieldTextPropType,
FieldDynamicPropType,
]);
export const SubstepPropType = PropTypes.shape({
key: PropTypes.string,
name: PropTypes.string,
arguments: PropTypes.arrayOf(FieldPropType),
});
export const RawTriggerPropType = PropTypes.shape({
name: PropTypes.string,
key: PropTypes.string,
type: PropTypes.oneOf(['webhook', 'polling']),
showWebhookUrl: PropTypes.bool,
pollInterval: PropTypes.number,
description: PropTypes.string,
useSingletonWebhook: PropTypes.bool,
singletonWebhookRefValueParameter: PropTypes.string,
getInterval: PropTypes.func,
run: PropTypes.func,
testRun: PropTypes.func,
registerHook: PropTypes.func,
unregisterHook: PropTypes.func,
arguments: PropTypes.arrayOf(FieldPropType),
});
export const TriggerPropType = PropTypes.shape({
name: PropTypes.string,
key: PropTypes.string,
type: PropTypes.oneOf(['webhook', 'polling']),
showWebhookUrl: PropTypes.bool,
pollInterval: PropTypes.number,
description: PropTypes.string,
useSingletonWebhook: PropTypes.bool,
singletonWebhookRefValueParameter: PropTypes.string,
getInterval: PropTypes.func,
run: PropTypes.func,
testRun: PropTypes.func,
registerHook: PropTypes.func,
unregisterHook: PropTypes.func,
substeps: PropTypes.arrayOf(SubstepPropType),
});
export const RawActionPropType = PropTypes.shape({
name: PropTypes.string,
key: PropTypes.string,
description: PropTypes.string,
run: PropTypes.func,
arguments: PropTypes.arrayOf(FieldPropType),
});
export const ActionPropType = PropTypes.shape({
name: PropTypes.string,
key: PropTypes.string,
description: PropTypes.string,
run: PropTypes.func,
substeps: PropTypes.arrayOf(SubstepPropType),
});
export const AuthPropType = PropTypes.shape({
generateAuthUrl: PropTypes.func,
verifyCredentials: PropTypes.func,
isStillVerified: PropTypes.func,
refreshToken: PropTypes.func,
verifyWebhook: PropTypes.func,
isRefreshTokenRequested: PropTypes.bool,
fields: PropTypes.arrayOf(FieldPropType),
authenticationSteps: PropTypes.arrayOf(AuthenticationStepPropType),
reconnectionSteps: PropTypes.arrayOf(AuthenticationStepPropType),
sharedAuthenticationSteps: PropTypes.arrayOf(AuthenticationStepPropType),
sharedReconnectionSteps: PropTypes.arrayOf(AuthenticationStepPropType),
});
export const AppPropType = PropTypes.shape({
name: PropTypes.string,
key: PropTypes.string,
iconUrl: PropTypes.string,
docUrl: PropTypes.string,
authDocUrl: PropTypes.string,
primaryColor: PropTypes.string,
supportsConnections: PropTypes.bool,
apiBaseUrl: PropTypes.string,
baseUrl: PropTypes.string,
auth: AuthPropType,
connectionCount: PropTypes.number,
flowCount: PropTypes.number,
beforeRequest: PropTypes.arrayOf(PropTypes.func),
dynamicData: PropTypes.object,
dynamicFields: PropTypes.object,
triggers: PropTypes.arrayOf(TriggerPropType),
actions: PropTypes.arrayOf(ActionPropType),
});
export const ConnectionPropType = PropTypes.shape({
id: PropTypes.string,
key: PropTypes.string,
data: PropTypes.string,
formattedData: PropTypes.object,
userId: PropTypes.string,
verified: PropTypes.bool,
count: PropTypes.number,
flowCount: PropTypes.number,
appData: AppPropType,
createdAt: PropTypes.string,
reconnectable: PropTypes.bool,
appAuthClientId: PropTypes.string,
});
AppPropType.connection = PropTypes.arrayOf(ConnectionPropType);
export const ExecutionStepPropType = PropTypes.shape({
id: PropTypes.string,
executionId: PropTypes.string,
stepId: PropTypes.string,
dataIn: PropTypes.object,
dataOut: PropTypes.object,
errorDetails: PropTypes.object,
status: PropTypes.string,
createdAt: PropTypes.string,
updatedAt: PropTypes.string,
});
export const FlowPropType = PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
userId: PropTypes.string,
active: PropTypes.bool,
status: PropTypes.oneOf(['paused', 'published', 'draft']),
createdAt: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date),
]),
updatedAt: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date),
]),
remoteWebhookId: PropTypes.string,
lastInternalId: PropTypes.func,
});
export const StepPropType = PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
flowId: PropTypes.string,
key: PropTypes.string,
appKey: PropTypes.string,
iconUrl: PropTypes.string,
webhookUrl: PropTypes.string,
type: PropTypes.oneOf(['action', 'trigger']),
connectionId: PropTypes.string,
status: PropTypes.string,
position: PropTypes.number,
parameters: PropTypes.object,
connection: ConnectionPropType,
flow: FlowPropType,
executionSteps: PropTypes.arrayOf(ExecutionStepPropType),
output: PropTypes.object,
appData: AppPropType,
});
ExecutionStepPropType.step = StepPropType;
FlowPropType.steps = PropTypes.arrayOf(StepPropType);
export const ExecutionPropType = PropTypes.shape({
id: PropTypes.string,
flowId: PropTypes.string,
flow: FlowPropType,
testRun: PropTypes.bool,
status: PropTypes.oneOf(['success', 'failure']),
executionSteps: PropTypes.arrayOf(ExecutionStepPropType),
updatedAt: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date),
]),
createdAt: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date),
]),
});
export const PermissionPropType = PropTypes.shape({
id: PropTypes.string,
action: PropTypes.string,
subject: PropTypes.string,
conditions: PropTypes.arrayOf(PropTypes.string),
});
export const RolePropType = PropTypes.shape({
id: PropTypes.string,
key: PropTypes.string,
name: PropTypes.string,
description: PropTypes.string,
isAdmin: PropTypes.bool,
permissions: PropTypes.arrayOf(PermissionPropType),
});
export const UserPropType = PropTypes.shape({
id: PropTypes.string,
fullName: PropTypes.string,
email: PropTypes.string,
password: PropTypes.string,
connections: PropTypes.arrayOf(ConnectionPropType),
flows: PropTypes.arrayOf(FlowPropType),
steps: PropTypes.arrayOf(StepPropType),
role: RolePropType,
permissions: PropTypes.arrayOf(PermissionPropType),
createdAt: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date),
]),
updatedAt: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date),
]),
trialExpiryDate: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date),
]),
});
export const PermissionCatalogPropType = PropTypes.shape({
actions: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
key: PropTypes.string,
subjects: PropTypes.arrayOf(PropTypes.string),
}),
),
subjects: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
key: PropTypes.string,
}),
),
conditions: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string,
key: PropTypes.string,
}),
),
});
export const ConfigPropType = PropTypes.shape({
id: PropTypes.string,
key: PropTypes.string,
value: PropTypes.object,
});
export const TriggerItemPropType = PropTypes.shape({
raw: PropTypes.object,
meta: PropTypes.shape({
internalId: PropTypes.string,
}),
});
export const TriggerOutputPropType = PropTypes.shape({
data: PropTypes.arrayOf(TriggerItemPropType),
error: PropTypes.object,
});
export const ActionItemPropType = PropTypes.shape({
raw: PropTypes.object,
});
export const IActionOutputPropType = PropTypes.shape({
data: ActionItemPropType,
error: PropTypes.object,
});
export const AuthenticationPropType = PropTypes.shape({
client: PropTypes.any,
verifyCredentials: PropTypes.func,
isStillVerified: PropTypes.func,
});
export const PaymentPlanPropType = PropTypes.shape({
price: PropTypes.string,
name: PropTypes.string,
limit: PropTypes.string,
productId: PropTypes.string,
});
export const BillingTextCardActionPropType = PropTypes.shape({
type: PropTypes.oneOf(['text']),
text: PropTypes.string,
});
export const BillingLinkCardActionPropType = PropTypes.shape({
type: PropTypes.oneOf(['link']),
text: PropTypes.string,
src: PropTypes.string,
});
export const BillingCardActionPropType = PropTypes.oneOfType([
BillingTextCardActionPropType,
BillingLinkCardActionPropType,
]);
export const SubscriptionPropType = PropTypes.shape({
status: PropTypes.string,
monthlyQuota: PropTypes.shape({
title: PropTypes.string,
action: BillingCardActionPropType,
}),
nextBillDate: PropTypes.shape({
title: PropTypes.string,
action: BillingCardActionPropType,
}),
nextBillAmount: PropTypes.shape({
title: PropTypes.string,
action: BillingCardActionPropType,
}),
});
export const InvoicePropType = PropTypes.shape({
id: PropTypes.number,
amount: PropTypes.number,
currency: PropTypes.string,
payout_date: PropTypes.string,
receipt_url: PropTypes.string,
});
export const SamlAuthProviderPropType = PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
certificate: PropTypes.string,
signatureAlgorithm: PropTypes.oneOf(['sha1', 'sha256', 'sha512']),
issuer: PropTypes.string,
entryPoint: PropTypes.string,
firstnameAttributeName: PropTypes.string,
surnameAttributeName: PropTypes.string,
emailAttributeName: PropTypes.string,
roleAttributeName: PropTypes.string,
defaultRoleId: PropTypes.string,
active: PropTypes.bool,
loginUrl: PropTypes.string,
});
export const SamlAuthProviderRolePropType = PropTypes.shape({
id: PropTypes.string,
samlAuthProviderId: PropTypes.string,
roleId: PropTypes.string,
remoteRoleName: PropTypes.string,
});
export const AppConfigPropType = PropTypes.shape({
id: PropTypes.string,
key: PropTypes.string,
allowCustomConnection: PropTypes.bool,
canConnect: PropTypes.bool,
canCustomConnect: PropTypes.bool,
shared: PropTypes.bool,
disabled: PropTypes.bool,
});
export const AppAuthClientPropType = PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
appConfigId: PropTypes.string,
authDefaults: PropTypes.string,
formattedAuthDefaults: PropTypes.object,
active: PropTypes.bool,
});
export const NotificationPropType = PropTypes.shape({
name: PropTypes.string,
createdAt: PropTypes.string,
documentationUrl: PropTypes.string,
description: PropTypes.string,
});

691
yarn.lock

File diff suppressed because it is too large Load Diff