mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-18 01:18:23 +02:00
refactor: refactor addons to TSX and remove unused files (#676)
* refactor: refactor addons to TSX and remove unused files * refactor: change AddonIcon to getAddonIcon * refactor: add PermissionButton instead of conditional render * refactor: wrap icon buttons inside PermissionIconButtons * feat: add confirm delete dialog * fix: create addon form * fix: refactor addons * fix: remove addon store folder * fix: update index * fix: rebase * fix: update exports * fix: update snapshot * fix: add dev dep Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
500d405fa5
commit
2a9a3ac569
@ -67,6 +67,7 @@
|
|||||||
"fetch-mock": "9.11.0",
|
"fetch-mock": "9.11.0",
|
||||||
"http-proxy-middleware": "2.0.2",
|
"http-proxy-middleware": "2.0.2",
|
||||||
"immutable": "4.0.0",
|
"immutable": "4.0.0",
|
||||||
|
"@types/lodash.clonedeep": "^4.5.6",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"lodash.flow": "3.5.0",
|
"lodash.flow": "3.5.0",
|
||||||
"node-fetch": "2.6.7",
|
"node-fetch": "2.6.7",
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Grid, FormControlLabel, Checkbox } from '@material-ui/core';
|
||||||
|
|
||||||
|
import { styles as commonStyles } from '../../../common';
|
||||||
|
import { IAddonProvider } from '../../../../interfaces/addons';
|
||||||
|
|
||||||
|
interface IAddonProps {
|
||||||
|
provider: IAddonProvider;
|
||||||
|
checkedEvents: string[];
|
||||||
|
setEventValue: (name: string) => void;
|
||||||
|
error: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddonEvents = ({
|
||||||
|
provider,
|
||||||
|
checkedEvents,
|
||||||
|
setEventValue,
|
||||||
|
error,
|
||||||
|
}: IAddonProps) => {
|
||||||
|
if (!provider) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<h4>Events</h4>
|
||||||
|
<span className={commonStyles.error}>{error}</span>
|
||||||
|
<Grid container spacing={0}>
|
||||||
|
{provider.events.map(e => (
|
||||||
|
<Grid item xs={4} key={e}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={checkedEvents.includes(e)}
|
||||||
|
onChange={setEventValue(e)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={e}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
@ -1,23 +1,29 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { TextField, FormControlLabel, Switch } from '@material-ui/core';
|
import { TextField, FormControlLabel, Switch } from '@material-ui/core';
|
||||||
|
import { FormButtons, styles as commonStyles } from '../../common';
|
||||||
import { FormButtons, styles as commonStyles } from '../common';
|
import { trim } from '../../common/util';
|
||||||
import { trim } from '../common/util';
|
import { AddonParameters } from './AddonParameters/AddonParameters';
|
||||||
import AddonParameters from './form-addon-parameters';
|
import { AddonEvents } from './AddonEvents/AddonEvents';
|
||||||
import AddonEvents from './form-addon-events';
|
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
import PageContent from '../../common/PageContent/PageContent';
|
||||||
import styles from './form-addon-component.module.scss';
|
|
||||||
import PageContent from '../common/PageContent/PageContent';
|
|
||||||
import useAddonsApi from '../../hooks/api/actions/useAddonsApi/useAddonsApi';
|
|
||||||
import useToast from '../../hooks/useToast';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import useAddonsApi from '../../../hooks/api/actions/useAddonsApi/useAddonsApi';
|
||||||
|
import useToast from '../../../hooks/useToast';
|
||||||
|
import { makeStyles } from '@material-ui/styles';
|
||||||
|
|
||||||
const AddonFormComponent = ({ editMode, provider, addon, fetch }) => {
|
const useStyles = makeStyles(theme => ({
|
||||||
|
nameInput: {
|
||||||
|
marginRight: '1.5rem',
|
||||||
|
},
|
||||||
|
formSection: { padding: '10px 28px' },
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const AddonForm = ({ editMode, provider, addon, fetch }) => {
|
||||||
const { createAddon, updateAddon } = useAddonsApi();
|
const { createAddon, updateAddon } = useAddonsApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
const [config, setConfig] = useState(addon);
|
const [config, setConfig] = useState(addon);
|
||||||
const [errors, setErrors] = useState({
|
const [errors, setErrors] = useState({
|
||||||
@ -116,6 +122,7 @@ const AddonFormComponent = ({ editMode, provider, addon, fetch }) => {
|
|||||||
history.push('/addons');
|
history.push('/addons');
|
||||||
setToastData({
|
setToastData({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
|
confetti: true,
|
||||||
title: 'Addon created successfully',
|
title: 'Addon created successfully',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -196,14 +203,17 @@ const AddonFormComponent = ({ editMode, provider, addon, fetch }) => {
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<section className={styles.formSection}>
|
<section className={styles.formSection}>
|
||||||
<FormButtons submitText={submitText} onCancel={handleCancel} />
|
<FormButtons
|
||||||
|
submitText={submitText}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
AddonFormComponent.propTypes = {
|
AddonForm.propTypes = {
|
||||||
provider: PropTypes.object,
|
provider: PropTypes.object,
|
||||||
addon: PropTypes.object.isRequired,
|
addon: PropTypes.object.isRequired,
|
||||||
fetch: PropTypes.func.isRequired,
|
fetch: PropTypes.func.isRequired,
|
||||||
@ -211,5 +221,3 @@ AddonFormComponent.propTypes = {
|
|||||||
cancel: PropTypes.func.isRequired,
|
cancel: PropTypes.func.isRequired,
|
||||||
editMode: PropTypes.bool.isRequired,
|
editMode: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddonFormComponent;
|
|
@ -0,0 +1,60 @@
|
|||||||
|
import { TextField } from '@material-ui/core';
|
||||||
|
import {
|
||||||
|
IAddonConfig,
|
||||||
|
IAddonProvider,
|
||||||
|
IAddonProviderParams,
|
||||||
|
} from '../../../../../interfaces/addons';
|
||||||
|
|
||||||
|
const resolveType = ({ type = 'text', sensitive = false }, value: string) => {
|
||||||
|
if (sensitive && value === MASKED_VALUE) {
|
||||||
|
return 'text';
|
||||||
|
}
|
||||||
|
if (type === 'textfield') {
|
||||||
|
return 'text';
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MASKED_VALUE = '*****';
|
||||||
|
|
||||||
|
interface IAddonParameterProps {
|
||||||
|
provider: IAddonProvider;
|
||||||
|
errors: Record<string, string>;
|
||||||
|
definition: IAddonProviderParams;
|
||||||
|
setParameterValue: (param: string) => void;
|
||||||
|
config: IAddonConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddonParameter = ({
|
||||||
|
definition,
|
||||||
|
config,
|
||||||
|
errors,
|
||||||
|
setParameterValue,
|
||||||
|
}: IAddonParameterProps) => {
|
||||||
|
const value = config.parameters[definition.name] || '';
|
||||||
|
const type = resolveType(definition, value);
|
||||||
|
const error = errors.parameters[definition.name];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: '80%', marginTop: '25px' }}>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
rows={definition.type === 'textfield' ? 9 : 0}
|
||||||
|
multiline={definition.type === 'textfield'}
|
||||||
|
type={type}
|
||||||
|
label={definition.displayName}
|
||||||
|
name={definition.name}
|
||||||
|
placeholder={definition.placeholder || ''}
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true,
|
||||||
|
}}
|
||||||
|
value={value}
|
||||||
|
error={error}
|
||||||
|
onChange={setParameterValue(definition.name)}
|
||||||
|
variant="outlined"
|
||||||
|
helperText={definition.description}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IAddonConfig, IAddonProvider } from '../../../../interfaces/addons';
|
||||||
|
import { AddonParameter } from './AddonParameter/AddonParameter';
|
||||||
|
|
||||||
|
interface IAddonParametersProps {
|
||||||
|
provider: IAddonProvider;
|
||||||
|
errors: Record<string, string>;
|
||||||
|
editMode: boolean;
|
||||||
|
setParameterValue: (param: string) => void;
|
||||||
|
config: IAddonConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddonParameters = ({
|
||||||
|
provider,
|
||||||
|
config,
|
||||||
|
errors,
|
||||||
|
setParameterValue,
|
||||||
|
editMode,
|
||||||
|
}: IAddonParametersProps) => {
|
||||||
|
if (!provider) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<h4>Parameters</h4>
|
||||||
|
{editMode ? (
|
||||||
|
<p>
|
||||||
|
Sensitive parameters will be masked with value "<i>*****</i>
|
||||||
|
". If you don't change the value they will not be updated
|
||||||
|
when saving.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
{provider.parameters.map(parameter => (
|
||||||
|
<AddonParameter
|
||||||
|
key={parameter.name}
|
||||||
|
definition={parameter}
|
||||||
|
errors={errors}
|
||||||
|
config={config}
|
||||||
|
setParameterValue={setParameterValue}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
@ -1,12 +1,9 @@
|
|||||||
import { useContext, useEffect } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import ConfiguredAddons from './ConfiguredAddons';
|
import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons';
|
||||||
import AvailableAddons from './AvailableAddons';
|
import { AvailableAddons } from './AvailableAddons/AvailableAddons';
|
||||||
import { Avatar } from '@material-ui/core';
|
import { Avatar } from '@material-ui/core';
|
||||||
import { DeviceHub } from '@material-ui/icons';
|
import { DeviceHub } from '@material-ui/icons';
|
||||||
|
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
|
||||||
|
|
||||||
import slackIcon from '../../../assets/icons/slack.svg';
|
import slackIcon from '../../../assets/icons/slack.svg';
|
||||||
import jiraIcon from '../../../assets/icons/jira.svg';
|
import jiraIcon from '../../../assets/icons/jira.svg';
|
||||||
import webhooksIcon from '../../../assets/icons/webhooks.svg';
|
import webhooksIcon from '../../../assets/icons/webhooks.svg';
|
||||||
@ -14,7 +11,6 @@ import teamsIcon from '../../../assets/icons/teams.svg';
|
|||||||
import dataDogIcon from '../../../assets/icons/datadog.svg';
|
import dataDogIcon from '../../../assets/icons/datadog.svg';
|
||||||
import { formatAssetPath } from '../../../utils/format-path';
|
import { formatAssetPath } from '../../../utils/format-path';
|
||||||
import useAddons from '../../../hooks/api/getters/useAddons/useAddons';
|
import useAddons from '../../../hooks/api/getters/useAddons/useAddons';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
width: '40px',
|
width: '40px',
|
||||||
@ -23,7 +19,7 @@ const style = {
|
|||||||
float: 'left',
|
float: 'left',
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIcon = name => {
|
const getAddonIcon = (name: string): ReactElement => {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'slack':
|
case 'slack':
|
||||||
return (
|
return (
|
||||||
@ -74,40 +70,21 @@ const getIcon = name => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddonList = () => {
|
export const AddonList = () => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { providers, addons } = useAddons();
|
||||||
const { addons, providers, refetchAddons } = useAddons();
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (addons.length === 0) {
|
|
||||||
refetchAddons();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [addons.length]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={addons.length > 0}
|
condition={addons.length > 0}
|
||||||
show={
|
show={<ConfiguredAddons getAddonIcon={getAddonIcon} />}
|
||||||
<ConfiguredAddons
|
|
||||||
addons={addons}
|
|
||||||
hasAccess={hasAccess}
|
|
||||||
getIcon={getIcon}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<AvailableAddons
|
<AvailableAddons
|
||||||
providers={providers}
|
providers={providers}
|
||||||
hasAccess={hasAccess}
|
getAddonIcon={getAddonIcon}
|
||||||
history={history}
|
|
||||||
getIcon={getIcon}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AddonList;
|
|
@ -1,57 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PageContent from '../../../common/PageContent/PageContent';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemAvatar,
|
|
||||||
ListItemSecondaryAction,
|
|
||||||
ListItemText,
|
|
||||||
} from '@material-ui/core';
|
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { CREATE_ADDON } from '../../../providers/AccessProvider/permissions';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const AvailableAddons = ({ providers, getIcon, hasAccess, history }) => {
|
|
||||||
|
|
||||||
const renderProvider = provider => (
|
|
||||||
<ListItem key={provider.name}>
|
|
||||||
<ListItemAvatar>{getIcon(provider.name)}</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
primary={provider.displayName}
|
|
||||||
secondary={provider.description}
|
|
||||||
/>
|
|
||||||
<ListItemSecondaryAction>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={hasAccess(CREATE_ADDON)}
|
|
||||||
show={
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
name="device_hub"
|
|
||||||
onClick={() =>
|
|
||||||
history.push(`/addons/create/${provider.name}`)
|
|
||||||
}
|
|
||||||
title="Configure"
|
|
||||||
>
|
|
||||||
Configure
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<PageContent headerContent="Available addons">
|
|
||||||
<List>{providers.map(provider => renderProvider(provider))}</List>
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
AvailableAddons.propTypes = {
|
|
||||||
providers: PropTypes.array.isRequired,
|
|
||||||
getIcon: PropTypes.func.isRequired,
|
|
||||||
hasAccess: PropTypes.func.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AvailableAddons;
|
|
@ -0,0 +1,63 @@
|
|||||||
|
import { ReactElement } from 'react';
|
||||||
|
import PageContent from '../../../common/PageContent/PageContent';
|
||||||
|
import {
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemAvatar,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
ListItemText,
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import { CREATE_ADDON } from '../../../providers/AccessProvider/permissions';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
|
||||||
|
|
||||||
|
interface IProvider {
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string;
|
||||||
|
documentationUrl: string;
|
||||||
|
parameters: object[];
|
||||||
|
events: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAvailableAddonsProps {
|
||||||
|
getAddonIcon: (name: string) => ReactElement;
|
||||||
|
providers: IProvider[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AvailableAddons = ({
|
||||||
|
providers,
|
||||||
|
getAddonIcon,
|
||||||
|
}: IAvailableAddonsProps) => {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const renderProvider = (provider: IProvider) => (
|
||||||
|
<ListItem key={provider.name}>
|
||||||
|
<ListItemAvatar>{getAddonIcon(provider.name)}</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={provider.displayName}
|
||||||
|
secondary={provider.description}
|
||||||
|
/>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<PermissionButton
|
||||||
|
permission={CREATE_ADDON}
|
||||||
|
onClick={() =>
|
||||||
|
history.push(`/addons/create/${provider.name}`)
|
||||||
|
}
|
||||||
|
tooltip={`Configure ${provider.name} Addon`}
|
||||||
|
>
|
||||||
|
Configure
|
||||||
|
</PermissionButton>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<PageContent headerContent="Available addons">
|
||||||
|
<List>
|
||||||
|
{providers.map((provider: IProvider) =>
|
||||||
|
renderProvider(provider)
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +0,0 @@
|
|||||||
import AvailableAddons from './AvailableAddons';
|
|
||||||
|
|
||||||
export default AvailableAddons;
|
|
@ -1,6 +1,4 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
import {
|
||||||
IconButton,
|
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListItemAvatar,
|
ListItemAvatar,
|
||||||
@ -8,7 +6,6 @@ import {
|
|||||||
ListItemText,
|
ListItemText,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import { Visibility, VisibilityOff, Delete } from '@material-ui/icons';
|
import { Visibility, VisibilityOff, Delete } from '@material-ui/icons';
|
||||||
|
|
||||||
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import {
|
import {
|
||||||
DELETE_ADDON,
|
DELETE_ADDON,
|
||||||
@ -16,17 +13,43 @@ import {
|
|||||||
} from '../../../providers/AccessProvider/permissions';
|
} from '../../../providers/AccessProvider/permissions';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import PageContent from '../../../common/PageContent/PageContent';
|
import PageContent from '../../../common/PageContent/PageContent';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import useAddons from '../../../../hooks/api/getters/useAddons/useAddons';
|
import useAddons from '../../../../hooks/api/getters/useAddons/useAddons';
|
||||||
import useToast from '../../../../hooks/useToast';
|
import useToast from '../../../../hooks/useToast';
|
||||||
import useAddonsApi from '../../../../hooks/api/actions/useAddonsApi/useAddonsApi';
|
import useAddonsApi from '../../../../hooks/api/actions/useAddonsApi/useAddonsApi';
|
||||||
|
import { ReactElement, useContext, useState } from 'react';
|
||||||
|
import AccessContext from '../../../../contexts/AccessContext';
|
||||||
|
import { IAddon } from '../../../../interfaces/addons';
|
||||||
|
import PermissionIconButton from '../../../common/PermissionIconButton/PermissionIconButton';
|
||||||
|
import Dialogue from '../../../common/Dialogue';
|
||||||
|
|
||||||
const ConfiguredAddons = ({ addons, hasAccess, getIcon }) => {
|
interface IConfigureAddonsProps {
|
||||||
const { refetchAddons } = useAddons();
|
getAddonIcon: (name: string) => ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConfiguredAddons = ({ getAddonIcon }: IConfigureAddonsProps) => {
|
||||||
|
const { refetchAddons, addons } = useAddons();
|
||||||
const { updateAddon, removeAddon } = useAddonsApi();
|
const { updateAddon, removeAddon } = useAddonsApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
|
const [deletedAddon, setDeletedAddon] = useState<IAddon>({
|
||||||
|
id: 0,
|
||||||
|
provider: '',
|
||||||
|
description: '',
|
||||||
|
enabled: false,
|
||||||
|
events: [],
|
||||||
|
parameters: {},
|
||||||
|
});
|
||||||
|
|
||||||
const toggleAddon = async addon => {
|
const sortAddons = (addons: IAddon[]) => {
|
||||||
|
if (!addons) return [];
|
||||||
|
|
||||||
|
return addons.sort((addonA: IAddon, addonB: IAddon) => {
|
||||||
|
return addonA.id - addonB.id;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleAddon = async (addon: IAddon) => {
|
||||||
try {
|
try {
|
||||||
await updateAddon({ ...addon, enabled: !addon.enabled });
|
await updateAddon({ ...addon, enabled: !addon.enabled });
|
||||||
refetchAddons();
|
refetchAddons();
|
||||||
@ -35,12 +58,12 @@ const ConfiguredAddons = ({ addons, hasAccess, getIcon }) => {
|
|||||||
title: 'Success',
|
title: 'Success',
|
||||||
text: 'Addon state switched successfully',
|
text: 'Addon state switched successfully',
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
setToastApiError(e.toString());
|
setToastApiError(e.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemoveAddon = addon => async () => {
|
const onRemoveAddon = async (addon: IAddon) => {
|
||||||
try {
|
try {
|
||||||
await removeAddon(addon.id);
|
await removeAddon(addon.id);
|
||||||
refetchAddons();
|
refetchAddons();
|
||||||
@ -58,9 +81,9 @@ const ConfiguredAddons = ({ addons, hasAccess, getIcon }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderAddon = addon => (
|
const renderAddon = (addon: IAddon) => (
|
||||||
<ListItem key={addon.id}>
|
<ListItem key={addon.id}>
|
||||||
<ListItemAvatar>{getIcon(addon.provider)}</ListItemAvatar>
|
<ListItemAvatar>{getAddonIcon(addon.provider)}</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={
|
primary={
|
||||||
<span>
|
<span>
|
||||||
@ -85,14 +108,9 @@ const ConfiguredAddons = ({ addons, hasAccess, getIcon }) => {
|
|||||||
secondary={addon.description}
|
secondary={addon.description}
|
||||||
/>
|
/>
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<ConditionallyRender
|
<PermissionIconButton
|
||||||
condition={hasAccess(UPDATE_ADDON)}
|
permission={UPDATE_ADDON}
|
||||||
show={
|
tooltip={addon.enabled ? 'Disable addon' : 'Enable addon'}
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
title={
|
|
||||||
addon.enabled ? 'Disable addon' : 'Enable addon'
|
|
||||||
}
|
|
||||||
onClick={() => toggleAddon(addon)}
|
onClick={() => toggleAddon(addon)}
|
||||||
>
|
>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -100,35 +118,38 @@ const ConfiguredAddons = ({ addons, hasAccess, getIcon }) => {
|
|||||||
show={<Visibility />}
|
show={<Visibility />}
|
||||||
elseShow={<VisibilityOff />}
|
elseShow={<VisibilityOff />}
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</PermissionIconButton>
|
||||||
}
|
<PermissionIconButton
|
||||||
/>
|
permission={DELETE_ADDON}
|
||||||
<ConditionallyRender
|
tooltip={'Remove Addon'}
|
||||||
condition={hasAccess(DELETE_ADDON)}
|
onClick={() => {
|
||||||
show={
|
setDeletedAddon(addon);
|
||||||
<IconButton
|
setShowDelete(true);
|
||||||
size="small"
|
}}
|
||||||
title="Remove addon"
|
|
||||||
onClick={onRemoveAddon(addon)}
|
|
||||||
>
|
>
|
||||||
<Delete />
|
<Delete />
|
||||||
</IconButton>
|
</PermissionIconButton>
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<PageContent headerContent="Configured addons">
|
<PageContent headerContent="Configured addons">
|
||||||
<List>{addons.map(addon => renderAddon(addon))}</List>
|
<List>
|
||||||
|
{sortAddons(addons).map((addon: IAddon) => renderAddon(addon))}
|
||||||
|
</List>
|
||||||
|
<Dialogue
|
||||||
|
open={showDelete}
|
||||||
|
onClick={() => {
|
||||||
|
onRemoveAddon(deletedAddon);
|
||||||
|
setShowDelete(false);
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setShowDelete(false);
|
||||||
|
}}
|
||||||
|
title="Confirm deletion"
|
||||||
|
>
|
||||||
|
<div>Are you sure you want to delete this Addon?</div>
|
||||||
|
</Dialogue>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
ConfiguredAddons.propTypes = {
|
|
||||||
addons: PropTypes.array.isRequired,
|
|
||||||
hasAccess: PropTypes.func.isRequired,
|
|
||||||
toggleAddon: PropTypes.func.isRequired,
|
|
||||||
getIcon: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConfiguredAddons;
|
|
@ -1,3 +0,0 @@
|
|||||||
import ConfiguredAddons from './ConfiguredAddons';
|
|
||||||
|
|
||||||
export default ConfiguredAddons;
|
|
@ -1,3 +0,0 @@
|
|||||||
import AddonListComponent from './AddonList';
|
|
||||||
|
|
||||||
export default AddonListComponent;
|
|
42
frontend/src/component/addons/CreateAddon/CreateAddon.tsx
Normal file
42
frontend/src/component/addons/CreateAddon/CreateAddon.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import useAddons from '../../../hooks/api/getters/useAddons/useAddons';
|
||||||
|
import { AddonForm } from '../AddonForm/AddonForm';
|
||||||
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
|
||||||
|
interface IAddonCreateParams {
|
||||||
|
providerId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DATA = {
|
||||||
|
provider: '',
|
||||||
|
description: '',
|
||||||
|
enabled: true,
|
||||||
|
parameters: {},
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CreateAddon = () => {
|
||||||
|
const { providerId } = useParams<IAddonCreateParams>();
|
||||||
|
|
||||||
|
const { providers, refetchAddons } = useAddons();
|
||||||
|
|
||||||
|
const editMode = false;
|
||||||
|
const provider = providers.find(
|
||||||
|
(providerItem: any) => providerItem.name === providerId
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultAddon = {
|
||||||
|
...cloneDeep(DEFAULT_DATA),
|
||||||
|
provider: provider ? provider.name : '',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
// @ts-expect-error
|
||||||
|
<AddonForm
|
||||||
|
editMode={editMode}
|
||||||
|
provider={provider}
|
||||||
|
fetch={refetchAddons}
|
||||||
|
addon={defaultAddon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
41
frontend/src/component/addons/EditAddon/EditAddon.tsx
Normal file
41
frontend/src/component/addons/EditAddon/EditAddon.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import useAddons from '../../../hooks/api/getters/useAddons/useAddons';
|
||||||
|
import { AddonForm } from '../AddonForm/AddonForm';
|
||||||
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
import { IAddon } from '../../../interfaces/addons';
|
||||||
|
|
||||||
|
interface IAddonEditParams {
|
||||||
|
addonId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DATA = {
|
||||||
|
provider: '',
|
||||||
|
description: '',
|
||||||
|
enabled: true,
|
||||||
|
parameters: {},
|
||||||
|
events: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditAddon = () => {
|
||||||
|
const { addonId } = useParams<IAddonEditParams>();
|
||||||
|
|
||||||
|
const { providers, addons, refetchAddons } = useAddons();
|
||||||
|
|
||||||
|
const editMode = true;
|
||||||
|
const addon = addons.find(
|
||||||
|
(addon: IAddon) => addon.id === Number(addonId)
|
||||||
|
) || { ...cloneDeep(DEFAULT_DATA) };
|
||||||
|
const provider = addon
|
||||||
|
? providers.find(provider => provider.name === addon.provider)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
// @ts-expect-error
|
||||||
|
<AddonForm
|
||||||
|
editMode={editMode}
|
||||||
|
provider={provider}
|
||||||
|
fetch={refetchAddons}
|
||||||
|
addon={addon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,17 +0,0 @@
|
|||||||
.nameInput {
|
|
||||||
margin-right: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formContainer {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
max-width: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formSection {
|
|
||||||
padding: 10px 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
font-size: var(--h1-size);
|
|
||||||
padding: var(--card-header-padding);
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import FormComponent from './form-addon-component';
|
|
||||||
import { updateAddon, createAddon, fetchAddons } from '../../store/addons/actions';
|
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
|
||||||
|
|
||||||
// Required for to fill the initial form.
|
|
||||||
const DEFAULT_DATA = {
|
|
||||||
provider: '',
|
|
||||||
description: '',
|
|
||||||
enabled: true,
|
|
||||||
parameters: {},
|
|
||||||
events: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state, params) => {
|
|
||||||
const defaultAddon = cloneDeep(DEFAULT_DATA);
|
|
||||||
const editMode = !!params.addonId;
|
|
||||||
const addons = state.addons.get('addons').toJS();
|
|
||||||
const providers = state.addons.get('providers').toJS();
|
|
||||||
|
|
||||||
let addon;
|
|
||||||
let provider;
|
|
||||||
|
|
||||||
if (editMode) {
|
|
||||||
addon = addons.find(addon => addon.id === +params.addonId) || defaultAddon;
|
|
||||||
provider = addon ? providers.find(provider => provider.name === addon.provider) : undefined;
|
|
||||||
} else {
|
|
||||||
provider = providers.find(provider => provider.name === params.provider);
|
|
||||||
addon = { ...defaultAddon, provider: provider ? provider.name : '' };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
provider,
|
|
||||||
addon,
|
|
||||||
editMode,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, ownProps) => {
|
|
||||||
const { addonId, history } = ownProps;
|
|
||||||
const submit = addonId ? updateAddon : createAddon;
|
|
||||||
|
|
||||||
return {
|
|
||||||
submit: async addonConfig => {
|
|
||||||
await submit(addonConfig)(dispatch);
|
|
||||||
history.push('/addons');
|
|
||||||
},
|
|
||||||
fetch: () => fetchAddons()(dispatch),
|
|
||||||
cancel: () => history.push('/addons'),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const FormAddContainer = connect(mapStateToProps, mapDispatchToProps)(FormComponent);
|
|
||||||
|
|
||||||
export default FormAddContainer;
|
|
@ -1,35 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Grid, FormControlLabel, Checkbox } from '@material-ui/core';
|
|
||||||
|
|
||||||
import { styles as commonStyles } from '../common';
|
|
||||||
|
|
||||||
const AddonEvents = ({ provider, checkedEvents, setEventValue, error }) => {
|
|
||||||
if (!provider) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<h4>Events</h4>
|
|
||||||
<span className={commonStyles.error}>{error}</span>
|
|
||||||
<Grid container spacing={0}>
|
|
||||||
{provider.events.map(e => (
|
|
||||||
<Grid item xs={4} key={e}>
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Checkbox checked={checkedEvents.includes(e)} onChange={setEventValue(e)} />}
|
|
||||||
label={e}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
AddonEvents.propTypes = {
|
|
||||||
provider: PropTypes.object,
|
|
||||||
checkedEvents: PropTypes.array.isRequired,
|
|
||||||
setEventValue: PropTypes.func.isRequired,
|
|
||||||
error: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddonEvents;
|
|
@ -1,86 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { TextField } from '@material-ui/core';
|
|
||||||
|
|
||||||
const MASKED_VALUE = '*****';
|
|
||||||
|
|
||||||
const resolveType = ({ type = 'text', sensitive = false }, value) => {
|
|
||||||
if (sensitive && value === MASKED_VALUE) {
|
|
||||||
return 'text';
|
|
||||||
}
|
|
||||||
if (type === 'textfield') {
|
|
||||||
return 'text';
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddonParameter = ({ definition, config, errors, setParameterValue }) => {
|
|
||||||
const value = config.parameters[definition.name] || '';
|
|
||||||
const type = resolveType(definition, value);
|
|
||||||
const error = errors.parameters[definition.name];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ width: '80%', marginTop: '25px' }}>
|
|
||||||
<TextField
|
|
||||||
size="small"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
rows={definition.type === 'textfield' ? 9 : 0}
|
|
||||||
multiline={definition.type === 'textfield'}
|
|
||||||
type={type}
|
|
||||||
label={definition.displayName}
|
|
||||||
name={definition.name}
|
|
||||||
placeholder={definition.placeholder || ''}
|
|
||||||
InputLabelProps={{
|
|
||||||
shrink: true,
|
|
||||||
}}
|
|
||||||
value={value}
|
|
||||||
error={error}
|
|
||||||
onChange={setParameterValue(definition.name)}
|
|
||||||
variant="outlined"
|
|
||||||
helperText={definition.description}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
AddonParameter.propTypes = {
|
|
||||||
definition: PropTypes.object.isRequired,
|
|
||||||
config: PropTypes.object.isRequired,
|
|
||||||
errors: PropTypes.object.isRequired,
|
|
||||||
setParameterValue: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddonParameters = ({ provider, config, errors, setParameterValue, editMode }) => {
|
|
||||||
if (!provider) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<h4>Parameters</h4>
|
|
||||||
{editMode ? (
|
|
||||||
<p>
|
|
||||||
Sensitive parameters will be masked with value "<i>*****</i>
|
|
||||||
". If you don't change the value they will not be updated when saving.
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{provider.parameters.map(p => (
|
|
||||||
<AddonParameter
|
|
||||||
key={p.name}
|
|
||||||
definition={p}
|
|
||||||
errors={errors}
|
|
||||||
config={config}
|
|
||||||
setParameterValue={setParameterValue}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
AddonParameters.propTypes = {
|
|
||||||
provider: PropTypes.object,
|
|
||||||
config: PropTypes.object.isRequired,
|
|
||||||
errors: PropTypes.object.isRequired,
|
|
||||||
setParameterValue: PropTypes.func.isRequired,
|
|
||||||
editMode: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddonParameters;
|
|
@ -1,30 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import AddonsListComponent from './AddonList';
|
|
||||||
import { fetchAddons, removeAddon, updateAddon } from '../../store/addons/actions';
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const list = state.addons.toJS();
|
|
||||||
|
|
||||||
return {
|
|
||||||
addons: list.addons,
|
|
||||||
providers: list.providers,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
removeAddon: addon => {
|
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
if (window.confirm('Are you sure you want to remove this addon?')) {
|
|
||||||
removeAddon(addon)(dispatch);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchAddons: () => fetchAddons()(dispatch),
|
|
||||||
toggleAddon: addon => {
|
|
||||||
const updatedAddon = { ...addon, enabled: !addon.enabled };
|
|
||||||
return updateAddon(updatedAddon)(dispatch);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const AddonsListContainer = connect(mapStateToProps, mapDispatchToProps)(AddonsListComponent);
|
|
||||||
|
|
||||||
export default AddonsListContainer;
|
|
@ -286,7 +286,7 @@ Array [
|
|||||||
"layout": "main",
|
"layout": "main",
|
||||||
"menu": Object {},
|
"menu": Object {},
|
||||||
"parent": "/addons",
|
"parent": "/addons",
|
||||||
"path": "/addons/create/:provider",
|
"path": "/addons/create/:providerId",
|
||||||
"title": "Create",
|
"title": "Create",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
@ -295,7 +295,7 @@ Array [
|
|||||||
"layout": "main",
|
"layout": "main",
|
||||||
"menu": Object {},
|
"menu": Object {},
|
||||||
"parent": "/addons",
|
"parent": "/addons",
|
||||||
"path": "/addons/edit/:id",
|
"path": "/addons/edit/:addonId",
|
||||||
"title": "Edit",
|
"title": "Edit",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
|
@ -9,9 +9,7 @@ import { ArchiveListContainer } from '../archive/ArchiveListContainer';
|
|||||||
import Applications from '../../page/applications';
|
import Applications from '../../page/applications';
|
||||||
import ApplicationView from '../../page/applications/view';
|
import ApplicationView from '../../page/applications/view';
|
||||||
import { TagTypeList } from '../tags/TagTypeList/TagTypeList';
|
import { TagTypeList } from '../tags/TagTypeList/TagTypeList';
|
||||||
import Addons from '../../page/addons';
|
import { AddonList } from '../addons/AddonList/AddonList';
|
||||||
import AddonsCreate from '../../page/addons/create';
|
|
||||||
import AddonsEdit from '../../page/addons/edit';
|
|
||||||
import Admin from '../admin';
|
import Admin from '../admin';
|
||||||
import AdminApi from '../admin/api';
|
import AdminApi from '../admin/api';
|
||||||
import AdminInvoice from '../admin/invoice/InvoiceAdminPage';
|
import AdminInvoice from '../admin/invoice/InvoiceAdminPage';
|
||||||
@ -45,6 +43,8 @@ import CreateFeature from '../feature/CreateFeature/CreateFeature';
|
|||||||
import EditFeature from '../feature/EditFeature/EditFeature';
|
import EditFeature from '../feature/EditFeature/EditFeature';
|
||||||
import ContextList from '../context/ContextList/ContextList';
|
import ContextList from '../context/ContextList/ContextList';
|
||||||
import RedirectFeatureView from '../feature/RedirectFeatureView/RedirectFeatureView';
|
import RedirectFeatureView from '../feature/RedirectFeatureView/RedirectFeatureView';
|
||||||
|
import { CreateAddon } from '../addons/CreateAddon/CreateAddon';
|
||||||
|
import { EditAddon } from '../addons/EditAddon/EditAddon';
|
||||||
|
|
||||||
export const routes = [
|
export const routes = [
|
||||||
// Project
|
// Project
|
||||||
@ -322,19 +322,19 @@ export const routes = [
|
|||||||
|
|
||||||
// Addons
|
// Addons
|
||||||
{
|
{
|
||||||
path: '/addons/create/:provider',
|
path: '/addons/create/:providerId',
|
||||||
parent: '/addons',
|
parent: '/addons',
|
||||||
title: 'Create',
|
title: 'Create',
|
||||||
component: AddonsCreate,
|
component: CreateAddon,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
layout: 'main',
|
layout: 'main',
|
||||||
menu: {},
|
menu: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/addons/edit/:id',
|
path: '/addons/edit/:addonId',
|
||||||
parent: '/addons',
|
parent: '/addons',
|
||||||
title: 'Edit',
|
title: 'Edit',
|
||||||
component: AddonsEdit,
|
component: EditAddon,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
layout: 'main',
|
layout: 'main',
|
||||||
menu: {},
|
menu: {},
|
||||||
@ -342,7 +342,7 @@ export const routes = [
|
|||||||
{
|
{
|
||||||
path: '/addons',
|
path: '/addons',
|
||||||
title: 'Addons',
|
title: 'Addons',
|
||||||
component: Addons,
|
component: AddonList,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
type: 'protected',
|
type: 'protected',
|
||||||
layout: 'main',
|
layout: 'main',
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IAddons } from '../../../../interfaces/addons';
|
import { IAddon } from '../../../../interfaces/addons';
|
||||||
import useAPI from '../useApi/useApi';
|
import useAPI from '../useApi/useApi';
|
||||||
|
|
||||||
const useAddonsApi = () => {
|
const useAddonsApi = () => {
|
||||||
@ -8,7 +8,7 @@ const useAddonsApi = () => {
|
|||||||
|
|
||||||
const URI = 'api/admin/addons';
|
const URI = 'api/admin/addons';
|
||||||
|
|
||||||
const createAddon = async (addonConfig: IAddons) => {
|
const createAddon = async (addonConfig: IAddon) => {
|
||||||
const path = URI;
|
const path = URI;
|
||||||
const req = createRequest(path, {
|
const req = createRequest(path, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -38,7 +38,7 @@ const useAddonsApi = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAddon = async (addonConfig: IAddons) => {
|
const updateAddon = async (addonConfig: IAddon) => {
|
||||||
const path = `${URI}/${addonConfig.id}`;
|
const path = `${URI}/${addonConfig.id}`;
|
||||||
const req = createRequest(path, {
|
const req = createRequest(path, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export interface IAddons {
|
import { ITagType } from './tags';
|
||||||
|
|
||||||
|
export interface IAddon {
|
||||||
id: number;
|
id: number;
|
||||||
provider: string;
|
provider: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -6,3 +8,32 @@ export interface IAddons {
|
|||||||
events: string[];
|
events: string[];
|
||||||
parameters: object;
|
parameters: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAddonProvider {
|
||||||
|
description: string;
|
||||||
|
displayName: string;
|
||||||
|
documentationUrl: string;
|
||||||
|
events: string[];
|
||||||
|
name: string;
|
||||||
|
parameters: IAddonProviderParams[];
|
||||||
|
tagTypes: ITagType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAddonProviderParams {
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
type: string;
|
||||||
|
required: boolean;
|
||||||
|
sensitive: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAddonConfig {
|
||||||
|
description: string;
|
||||||
|
enabled: boolean;
|
||||||
|
events: string[];
|
||||||
|
id: number;
|
||||||
|
parameters: Record<string, any>;
|
||||||
|
provider: string;
|
||||||
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import AddonForm from '../../component/addons/form-addon-container';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const render = ({ match: { params }, history }) => (
|
|
||||||
<AddonForm provider={params.provider} title="Configure addon" history={history} />
|
|
||||||
);
|
|
||||||
|
|
||||||
render.propTypes = {
|
|
||||||
match: PropTypes.object.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default render;
|
|
@ -1,14 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import AddonForm from '../../component/addons/form-addon-container';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const render = ({ match: { params }, history }) => (
|
|
||||||
<AddonForm addonId={params.id} title="Edit addon" history={history} />
|
|
||||||
);
|
|
||||||
|
|
||||||
render.propTypes = {
|
|
||||||
match: PropTypes.object.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default render;
|
|
@ -1,11 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import Addons from '../../component/addons';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const render = ({ history }) => <Addons history={history} />;
|
|
||||||
|
|
||||||
render.propTypes = {
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default render;
|
|
@ -1,69 +0,0 @@
|
|||||||
export const addonSimple = {
|
|
||||||
addons: [],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
name: 'webhook',
|
|
||||||
displayName: 'Webhook',
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: 'url',
|
|
||||||
displayName: 'Webhook URL',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'unleashUrl',
|
|
||||||
displayName: 'Unleash Admin UI url',
|
|
||||||
type: 'text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bodyTemplate',
|
|
||||||
displayName: 'Body template',
|
|
||||||
description: 'You may format the body using a mustache template.',
|
|
||||||
type: 'text',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
events: ['feature-created', 'feature-updated', 'feature-archived', 'feature-revived'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addonConfig = {
|
|
||||||
id: 1,
|
|
||||||
provider: 'webhook',
|
|
||||||
enabled: true,
|
|
||||||
description: null,
|
|
||||||
parameters: {
|
|
||||||
url: 'http://localhost:4242/webhook',
|
|
||||||
bodyTemplate: "{'name': '{{event.data.name}}' }",
|
|
||||||
},
|
|
||||||
events: ['feature-updated', 'feature-created'],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addonsWithConfig = {
|
|
||||||
addons: [addonConfig],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
name: 'webhook',
|
|
||||||
displayName: 'Webhook',
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: 'url',
|
|
||||||
displayName: 'Webhook URL',
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'unleashUrl',
|
|
||||||
displayName: 'Unleash Admin UI url',
|
|
||||||
type: 'text',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bodyTemplate',
|
|
||||||
displayName: 'Body template',
|
|
||||||
description: 'You may format the body using a mustache template.',
|
|
||||||
type: 'text',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
events: ['feature-created', 'feature-updated', 'feature-archived', 'feature-revived'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
@ -1,189 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`should add addon-config 1`] = `
|
|
||||||
Object {
|
|
||||||
"addons": Array [
|
|
||||||
Object {
|
|
||||||
"description": null,
|
|
||||||
"enabled": true,
|
|
||||||
"events": Array [
|
|
||||||
"feature-updated",
|
|
||||||
"feature-created",
|
|
||||||
],
|
|
||||||
"id": 1,
|
|
||||||
"parameters": Object {
|
|
||||||
"bodyTemplate": "{'name': '{{event.data.name}}' }",
|
|
||||||
"url": "http://localhost:4242/webhook",
|
|
||||||
},
|
|
||||||
"provider": "webhook",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"providers": Array [
|
|
||||||
Object {
|
|
||||||
"displayName": "Webhook",
|
|
||||||
"events": Array [
|
|
||||||
"feature-created",
|
|
||||||
"feature-updated",
|
|
||||||
"feature-archived",
|
|
||||||
"feature-revived",
|
|
||||||
],
|
|
||||||
"name": "webhook",
|
|
||||||
"parameters": Array [
|
|
||||||
Object {
|
|
||||||
"displayName": "Webhook URL",
|
|
||||||
"name": "url",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"displayName": "Unleash Admin UI url",
|
|
||||||
"name": "unleashUrl",
|
|
||||||
"type": "text",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"description": "You may format the body using a mustache template.",
|
|
||||||
"displayName": "Body template",
|
|
||||||
"name": "bodyTemplate",
|
|
||||||
"type": "text",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should be default state 1`] = `
|
|
||||||
Object {
|
|
||||||
"addons": Array [],
|
|
||||||
"providers": Array [],
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should be merged state all 1`] = `
|
|
||||||
Object {
|
|
||||||
"addons": Array [],
|
|
||||||
"providers": Array [
|
|
||||||
Object {
|
|
||||||
"displayName": "Webhook",
|
|
||||||
"events": Array [
|
|
||||||
"feature-created",
|
|
||||||
"feature-updated",
|
|
||||||
"feature-archived",
|
|
||||||
"feature-revived",
|
|
||||||
],
|
|
||||||
"name": "webhook",
|
|
||||||
"parameters": Array [
|
|
||||||
Object {
|
|
||||||
"displayName": "Webhook URL",
|
|
||||||
"name": "url",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"displayName": "Unleash Admin UI url",
|
|
||||||
"name": "unleashUrl",
|
|
||||||
"type": "text",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"description": "You may format the body using a mustache template.",
|
|
||||||
"displayName": "Body template",
|
|
||||||
"name": "bodyTemplate",
|
|
||||||
"type": "text",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should clear addon-config on logout 1`] = `
|
|
||||||
Object {
|
|
||||||
"addons": Array [],
|
|
||||||
"providers": Array [],
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should remove addon-config 1`] = `
|
|
||||||
Object {
|
|
||||||
"addons": Array [],
|
|
||||||
"providers": Array [
|
|
||||||
Object {
|
|
||||||
"displayName": "Webhook",
|
|
||||||
"events": Array [
|
|
||||||
"feature-created",
|
|
||||||
"feature-updated",
|
|
||||||
"feature-archived",
|
|
||||||
"feature-revived",
|
|
||||||
],
|
|
||||||
"name": "webhook",
|
|
||||||
"parameters": Array [
|
|
||||||
Object {
|
|
||||||
"displayName": "Webhook URL",
|
|
||||||
"name": "url",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"displayName": "Unleash Admin UI url",
|
|
||||||
"name": "unleashUrl",
|
|
||||||
"type": "text",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"description": "You may format the body using a mustache template.",
|
|
||||||
"displayName": "Body template",
|
|
||||||
"name": "bodyTemplate",
|
|
||||||
"type": "text",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`should update addon-config 1`] = `
|
|
||||||
Object {
|
|
||||||
"addons": Array [
|
|
||||||
Object {
|
|
||||||
"description": "new desc",
|
|
||||||
"enabled": false,
|
|
||||||
"events": Array [
|
|
||||||
"feature-updated",
|
|
||||||
"feature-created",
|
|
||||||
],
|
|
||||||
"id": 1,
|
|
||||||
"parameters": Object {
|
|
||||||
"bodyTemplate": "{'name': '{{event.data.name}}' }",
|
|
||||||
"url": "http://localhost:4242/webhook",
|
|
||||||
},
|
|
||||||
"provider": "webhook",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"providers": Array [
|
|
||||||
Object {
|
|
||||||
"displayName": "Webhook",
|
|
||||||
"events": Array [
|
|
||||||
"feature-created",
|
|
||||||
"feature-updated",
|
|
||||||
"feature-archived",
|
|
||||||
"feature-revived",
|
|
||||||
],
|
|
||||||
"name": "webhook",
|
|
||||||
"parameters": Array [
|
|
||||||
Object {
|
|
||||||
"displayName": "Webhook URL",
|
|
||||||
"name": "url",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"displayName": "Unleash Admin UI url",
|
|
||||||
"name": "unleashUrl",
|
|
||||||
"type": "text",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"description": "You may format the body using a mustache template.",
|
|
||||||
"displayName": "Body template",
|
|
||||||
"name": "bodyTemplate",
|
|
||||||
"type": "text",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
`;
|
|
@ -1,113 +0,0 @@
|
|||||||
import configureMockStore from 'redux-mock-store';
|
|
||||||
import thunk from 'redux-thunk';
|
|
||||||
import fetchMock from 'fetch-mock';
|
|
||||||
|
|
||||||
import {
|
|
||||||
RECEIVE_ADDON_CONFIG,
|
|
||||||
ERROR_RECEIVE_ADDON_CONFIG,
|
|
||||||
REMOVE_ADDON_CONFIG,
|
|
||||||
UPDATE_ADDON_CONFIG,
|
|
||||||
ADD_ADDON_CONFIG,
|
|
||||||
fetchAddons,
|
|
||||||
removeAddon,
|
|
||||||
updateAddon,
|
|
||||||
createAddon,
|
|
||||||
} from '../actions';
|
|
||||||
|
|
||||||
const middlewares = [thunk];
|
|
||||||
const mockStore = configureMockStore(middlewares);
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
fetchMock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('creates RECEIVE_ADDON_CONFIG when fetching addons has been done', () => {
|
|
||||||
fetchMock.getOnce('api/admin/addons', {
|
|
||||||
body: { addons: { providers: [{ name: 'webhook' }] } },
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedActions = [{ type: RECEIVE_ADDON_CONFIG, value: { addons: { providers: [{ name: 'webhook' }] } } }];
|
|
||||||
const store = mockStore({ addons: [] });
|
|
||||||
|
|
||||||
return store.dispatch(fetchAddons()).then(() => {
|
|
||||||
// return of async actions
|
|
||||||
expect(store.getActions()).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('creates RECEIVE_ADDON_CONFIG_ when fetching addons has been done', () => {
|
|
||||||
fetchMock.getOnce('api/admin/addons', {
|
|
||||||
body: { message: 'Server error' },
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
status: 500,
|
|
||||||
});
|
|
||||||
|
|
||||||
const store = mockStore({ addons: [] });
|
|
||||||
|
|
||||||
return store.dispatch(fetchAddons()).catch(e => {
|
|
||||||
// return of async actions
|
|
||||||
expect(store.getActions()[0].type).toEqual(ERROR_RECEIVE_ADDON_CONFIG);
|
|
||||||
expect(e.message).toEqual('Unexpected exception when talking to unleash-api');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('creates REMOVE_ADDON_CONFIG when delete addon has been done', () => {
|
|
||||||
const addon = {
|
|
||||||
id: 1,
|
|
||||||
provider: 'webhook',
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchMock.deleteOnce('api/admin/addons/1', {
|
|
||||||
status: 200,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedActions = [{ type: REMOVE_ADDON_CONFIG, value: addon }];
|
|
||||||
const store = mockStore({ addons: [] });
|
|
||||||
|
|
||||||
return store.dispatch(removeAddon(addon)).then(() => {
|
|
||||||
// return of async actions
|
|
||||||
expect(store.getActions()).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('creates UPDATE_ADDON_CONFIG when delete addon has been done', () => {
|
|
||||||
const addon = {
|
|
||||||
id: 1,
|
|
||||||
provider: 'webhook',
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchMock.putOnce('api/admin/addons/1', {
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
status: 200,
|
|
||||||
body: addon,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedActions = [{ type: UPDATE_ADDON_CONFIG, value: addon }];
|
|
||||||
const store = mockStore({ addons: [] });
|
|
||||||
|
|
||||||
return store.dispatch(updateAddon(addon)).then(() => {
|
|
||||||
// return of async actions
|
|
||||||
expect(store.getActions()).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('creates ADD_ADDON_CONFIG when delete addon has been done', () => {
|
|
||||||
const addon = {
|
|
||||||
provider: 'webhook',
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchMock.postOnce('api/admin/addons', {
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
status: 200,
|
|
||||||
body: addon,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expectedActions = [{ type: ADD_ADDON_CONFIG, value: addon }];
|
|
||||||
const store = mockStore({ addons: [] });
|
|
||||||
|
|
||||||
return store.dispatch(createAddon(addon)).then(() => {
|
|
||||||
// return of async actions
|
|
||||||
expect(store.getActions()).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,54 +0,0 @@
|
|||||||
import reducer from '../index';
|
|
||||||
import { RECEIVE_ADDON_CONFIG, ADD_ADDON_CONFIG, REMOVE_ADDON_CONFIG, UPDATE_ADDON_CONFIG } from '../actions';
|
|
||||||
import { addonSimple, addonsWithConfig, addonConfig } from '../__testdata__/data';
|
|
||||||
import { USER_LOGOUT } from '../../user/actions';
|
|
||||||
|
|
||||||
test('should be default state', () => {
|
|
||||||
const state = reducer(undefined, {});
|
|
||||||
expect(state.toJS()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should be merged state all', () => {
|
|
||||||
const state = reducer(undefined, { type: RECEIVE_ADDON_CONFIG, value: addonSimple });
|
|
||||||
expect(state.toJS()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should add addon-config', () => {
|
|
||||||
let state = reducer(undefined, { type: RECEIVE_ADDON_CONFIG, value: addonSimple });
|
|
||||||
state = reducer(state, { type: ADD_ADDON_CONFIG, value: addonConfig });
|
|
||||||
|
|
||||||
const data = state.toJS();
|
|
||||||
expect(data).toMatchSnapshot();
|
|
||||||
expect(data.addons.length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should remove addon-config', () => {
|
|
||||||
let state = reducer(undefined, { type: RECEIVE_ADDON_CONFIG, value: addonsWithConfig });
|
|
||||||
state = reducer(state, { type: REMOVE_ADDON_CONFIG, value: addonConfig });
|
|
||||||
|
|
||||||
const data = state.toJS();
|
|
||||||
expect(data).toMatchSnapshot();
|
|
||||||
expect(data.addons.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should update addon-config', () => {
|
|
||||||
const updateAdddonConfig = { ...addonConfig, description: 'new desc', enabled: false };
|
|
||||||
|
|
||||||
let state = reducer(undefined, { type: RECEIVE_ADDON_CONFIG, value: addonsWithConfig });
|
|
||||||
state = reducer(state, { type: UPDATE_ADDON_CONFIG, value: updateAdddonConfig });
|
|
||||||
|
|
||||||
const data = state.toJS();
|
|
||||||
expect(data).toMatchSnapshot();
|
|
||||||
expect(data.addons.length).toBe(1);
|
|
||||||
expect(data.addons[0].description).toBe('new desc');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should clear addon-config on logout', () => {
|
|
||||||
let state = reducer(undefined, { type: RECEIVE_ADDON_CONFIG, value: addonsWithConfig });
|
|
||||||
state = reducer(state, { type: USER_LOGOUT });
|
|
||||||
|
|
||||||
const data = state.toJS();
|
|
||||||
expect(data).toMatchSnapshot();
|
|
||||||
expect(data.addons.length).toBe(0);
|
|
||||||
expect(data.providers.length).toBe(0);
|
|
||||||
});
|
|
@ -1,51 +0,0 @@
|
|||||||
import api from './api';
|
|
||||||
import { dispatchError } from '../util';
|
|
||||||
|
|
||||||
export const RECEIVE_ADDON_CONFIG = 'RECEIVE_ADDON_CONFIG';
|
|
||||||
export const ERROR_RECEIVE_ADDON_CONFIG = 'ERROR_RECEIVE_ADDON_CONFIG';
|
|
||||||
export const REMOVE_ADDON_CONFIG = 'REMOVE_ADDON_CONFIG';
|
|
||||||
export const ERROR_REMOVING_ADDON_CONFIG = 'ERROR_REMOVING_ADDON_CONFIG';
|
|
||||||
export const ADD_ADDON_CONFIG = 'ADD_ADDON_CONFIG';
|
|
||||||
export const ERROR_ADD_ADDON_CONFIG = 'ERROR_ADD_ADDON_CONFIG';
|
|
||||||
export const UPDATE_ADDON_CONFIG = 'UPDATE_ADDON_CONFIG';
|
|
||||||
export const ERROR_UPDATE_ADDON_CONFIG = 'ERROR_UPDATE_ADDON_CONFIG';
|
|
||||||
|
|
||||||
// const receiveAddonConfig = value => ({ type: RECEIVE_ADDON_CONFIG, value });
|
|
||||||
const addAddonConfig = value => ({ type: ADD_ADDON_CONFIG, value });
|
|
||||||
const updateAdddonConfig = value => ({ type: UPDATE_ADDON_CONFIG, value });
|
|
||||||
const removeAddonconfig = value => ({ type: REMOVE_ADDON_CONFIG, value });
|
|
||||||
|
|
||||||
const success = (dispatch, type) => value => dispatch({ type, value });
|
|
||||||
|
|
||||||
export function fetchAddons() {
|
|
||||||
return dispatch =>
|
|
||||||
api
|
|
||||||
.fetchAll()
|
|
||||||
.then(success(dispatch, RECEIVE_ADDON_CONFIG))
|
|
||||||
.catch(dispatchError(dispatch, ERROR_RECEIVE_ADDON_CONFIG));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeAddon(addon) {
|
|
||||||
return dispatch =>
|
|
||||||
api
|
|
||||||
.remove(addon)
|
|
||||||
.then(() => dispatch(removeAddonconfig(addon)))
|
|
||||||
.catch(dispatchError(dispatch, ERROR_REMOVING_ADDON_CONFIG));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createAddon(addon) {
|
|
||||||
return dispatch =>
|
|
||||||
api
|
|
||||||
.create(addon)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(value => dispatch(addAddonConfig(value)))
|
|
||||||
.catch(dispatchError(dispatch, ERROR_ADD_ADDON_CONFIG));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateAddon(addon) {
|
|
||||||
return dispatch =>
|
|
||||||
api
|
|
||||||
.update(addon)
|
|
||||||
.then(() => dispatch(updateAdddonConfig(addon)))
|
|
||||||
.catch(dispatchError(dispatch, ERROR_UPDATE_ADDON_CONFIG));
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
import { formatApiPath } from '../../utils/format-path';
|
|
||||||
import { throwIfNotSuccess, headers } from '../api-helper';
|
|
||||||
|
|
||||||
const URI = formatApiPath(`api/admin/addons`);
|
|
||||||
|
|
||||||
function fetchAll() {
|
|
||||||
return fetch(URI, { credentials: 'include' })
|
|
||||||
.then(throwIfNotSuccess)
|
|
||||||
.then(response => response.json());
|
|
||||||
}
|
|
||||||
|
|
||||||
function create(addonConfig) {
|
|
||||||
return fetch(URI, {
|
|
||||||
method: 'POST',
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(addonConfig),
|
|
||||||
credentials: 'include',
|
|
||||||
}).then(throwIfNotSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(addonConfig) {
|
|
||||||
return fetch(`${URI}/${addonConfig.id}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(addonConfig),
|
|
||||||
credentials: 'include',
|
|
||||||
}).then(throwIfNotSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(addonConfig) {
|
|
||||||
return fetch(`${URI}/${addonConfig.id}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers,
|
|
||||||
credentials: 'include',
|
|
||||||
}).then(throwIfNotSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = {
|
|
||||||
fetchAll,
|
|
||||||
create,
|
|
||||||
update,
|
|
||||||
remove,
|
|
||||||
};
|
|
||||||
export default api;
|
|
@ -1,33 +0,0 @@
|
|||||||
import { Map as $Map, List, fromJS } from 'immutable';
|
|
||||||
import { RECEIVE_ADDON_CONFIG, ADD_ADDON_CONFIG, REMOVE_ADDON_CONFIG, UPDATE_ADDON_CONFIG } from './actions';
|
|
||||||
import { USER_LOGOUT, USER_LOGIN } from '../user/actions';
|
|
||||||
|
|
||||||
function getInitState() {
|
|
||||||
return new $Map({
|
|
||||||
providers: new List(),
|
|
||||||
addons: new List(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const strategies = (state = getInitState(), action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case RECEIVE_ADDON_CONFIG:
|
|
||||||
return fromJS(action.value);
|
|
||||||
case ADD_ADDON_CONFIG: {
|
|
||||||
return state.update('addons', arr => arr.push(fromJS(action.value)));
|
|
||||||
}
|
|
||||||
case REMOVE_ADDON_CONFIG:
|
|
||||||
return state.update('addons', arr => arr.filter(a => a.get('id') !== action.value.id));
|
|
||||||
case UPDATE_ADDON_CONFIG: {
|
|
||||||
const index = state.get('addons').findIndex(item => item.get('id') === action.value.id);
|
|
||||||
return state.setIn(['addons', index], fromJS(action.value));
|
|
||||||
}
|
|
||||||
case USER_LOGOUT:
|
|
||||||
case USER_LOGIN:
|
|
||||||
return getInitState();
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default strategies;
|
|
@ -22,12 +22,6 @@ import {
|
|||||||
ERROR_UPDATE_PROJECT,
|
ERROR_UPDATE_PROJECT,
|
||||||
} from '../project/actions';
|
} from '../project/actions';
|
||||||
|
|
||||||
import {
|
|
||||||
ERROR_ADD_ADDON_CONFIG,
|
|
||||||
ERROR_UPDATE_ADDON_CONFIG,
|
|
||||||
ERROR_REMOVING_ADDON_CONFIG,
|
|
||||||
} from '../addons/actions';
|
|
||||||
|
|
||||||
import { UPDATE_APPLICATION_FIELD } from '../application/actions';
|
import { UPDATE_APPLICATION_FIELD } from '../application/actions';
|
||||||
|
|
||||||
import { FORBIDDEN } from '../util';
|
import { FORBIDDEN } from '../util';
|
||||||
@ -59,9 +53,6 @@ const strategies = (state = getInitState(), action) => {
|
|||||||
case ERROR_RECEIVE_STRATEGIES:
|
case ERROR_RECEIVE_STRATEGIES:
|
||||||
case ERROR_REMOVING_PROJECT:
|
case ERROR_REMOVING_PROJECT:
|
||||||
case ERROR_UPDATE_PROJECT:
|
case ERROR_UPDATE_PROJECT:
|
||||||
case ERROR_ADD_ADDON_CONFIG:
|
|
||||||
case ERROR_UPDATE_ADDON_CONFIG:
|
|
||||||
case ERROR_REMOVING_ADDON_CONFIG:
|
|
||||||
case ERROR_ADD_PROJECT:
|
case ERROR_ADD_PROJECT:
|
||||||
return addErrorIfNotAlreadyInList(state, action.error.message);
|
return addErrorIfNotAlreadyInList(state, action.error.message);
|
||||||
case FORBIDDEN:
|
case FORBIDDEN:
|
||||||
|
@ -5,7 +5,6 @@ import error from './error';
|
|||||||
import user from './user';
|
import user from './user';
|
||||||
import applications from './application';
|
import applications from './application';
|
||||||
import projects from './project';
|
import projects from './project';
|
||||||
import addons from './addons';
|
|
||||||
import apiCalls from './api-calls';
|
import apiCalls from './api-calls';
|
||||||
|
|
||||||
const unleashStore = combineReducers({
|
const unleashStore = combineReducers({
|
||||||
@ -15,7 +14,6 @@ const unleashStore = combineReducers({
|
|||||||
user,
|
user,
|
||||||
applications,
|
applications,
|
||||||
projects,
|
projects,
|
||||||
addons,
|
|
||||||
apiCalls,
|
apiCalls,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2077,6 +2077,18 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
|
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
|
||||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||||
|
|
||||||
|
"@types/lodash.clonedeep@^4.5.6":
|
||||||
|
version "4.5.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b"
|
||||||
|
integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==
|
||||||
|
dependencies:
|
||||||
|
"@types/lodash" "*"
|
||||||
|
|
||||||
|
"@types/lodash@*":
|
||||||
|
version "4.14.178"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
|
||||||
|
integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==
|
||||||
|
|
||||||
"@types/minimatch@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz"
|
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user