mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 05:09:43 +02:00
feat: Move webhooks configuration from environment variable to UI
This commit is contained in:
parent
f0680831c2
commit
b22dba0d11
128 changed files with 2077 additions and 206 deletions
|
@ -8,6 +8,7 @@ import ActionTypes from '../constants/ActionTypes';
|
|||
const initializeCore = (
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
@ -33,6 +34,7 @@ const initializeCore = (
|
|||
payload: {
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
|
|
@ -8,6 +8,7 @@ import socket from './socket';
|
|||
import login from './login';
|
||||
import core from './core';
|
||||
import modals from './modals';
|
||||
import webhooks from './webhooks';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
import projectManagers from './project-managers';
|
||||
|
@ -35,6 +36,7 @@ export default {
|
|||
...login,
|
||||
...core,
|
||||
...modals,
|
||||
...webhooks,
|
||||
...users,
|
||||
...projects,
|
||||
...projectManagers,
|
||||
|
|
|
@ -14,6 +14,7 @@ const handleSocketReconnect = (
|
|||
config,
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
@ -40,6 +41,7 @@ const handleSocketReconnect = (
|
|||
config,
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
|
|
@ -67,6 +67,7 @@ const handleUserUpdate = (
|
|||
boardIds,
|
||||
config,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
@ -95,6 +96,7 @@ const handleUserUpdate = (
|
|||
boardIds,
|
||||
config,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
|
104
client/src/actions/webhooks.js
Normal file
104
client/src/actions/webhooks.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createWebhook = (webhook) => ({
|
||||
type: ActionTypes.WEBHOOK_CREATE,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
createWebhook.success = (localId, webhook) => ({
|
||||
type: ActionTypes.WEBHOOK_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
createWebhook.failure = (localId, error) => ({
|
||||
type: ActionTypes.WEBHOOK_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleWebhookCreate = (webhook) => ({
|
||||
type: ActionTypes.WEBHOOK_CREATE_HANDLE,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
const updateWebhook = (id, data) => ({
|
||||
type: ActionTypes.WEBHOOK_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
updateWebhook.success = (webhook) => ({
|
||||
type: ActionTypes.WEBHOOK_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
updateWebhook.failure = (id, error) => ({
|
||||
type: ActionTypes.WEBHOOK_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleWebhookUpdate = (webhook) => ({
|
||||
type: ActionTypes.WEBHOOK_UPDATE_HANDLE,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteWebhook = (id) => ({
|
||||
type: ActionTypes.WEBHOOK_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteWebhook.success = (webhook) => ({
|
||||
type: ActionTypes.WEBHOOK_DELETE__SUCCESS,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
deleteWebhook.failure = (id, error) => ({
|
||||
type: ActionTypes.WEBHOOK_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleWebhookDelete = (webhook) => ({
|
||||
type: ActionTypes.WEBHOOK_DELETE_HANDLE,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createWebhook,
|
||||
handleWebhookCreate,
|
||||
updateWebhook,
|
||||
handleWebhookUpdate,
|
||||
deleteWebhook,
|
||||
handleWebhookDelete,
|
||||
};
|
|
@ -7,6 +7,7 @@ import http from './http';
|
|||
import socket from './socket';
|
||||
import config from './config';
|
||||
import accessTokens from './access-tokens';
|
||||
import webhooks from './webhooks';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
import projectManagers from './project-managers';
|
||||
|
@ -35,6 +36,7 @@ export { http, socket };
|
|||
export default {
|
||||
...config,
|
||||
...accessTokens,
|
||||
...webhooks,
|
||||
...users,
|
||||
...projects,
|
||||
...projectManagers,
|
||||
|
|
23
client/src/api/webhooks.js
Executable file
23
client/src/api/webhooks.js
Executable file
|
@ -0,0 +1,23 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getWebhooks = (headers) => socket.get('/webhooks', undefined, headers);
|
||||
|
||||
const createWebhook = (data, headers) => socket.post('/webhooks', data, headers);
|
||||
|
||||
const updateWebhook = (id, data, headers) => socket.patch(`/webhooks/${id}`, data, headers);
|
||||
|
||||
const deleteWebhook = (id, headers) => socket.delete(`/webhooks/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
getWebhooks,
|
||||
createWebhook,
|
||||
updateWebhook,
|
||||
deleteWebhook,
|
||||
};
|
|
@ -12,6 +12,7 @@ import { Modal, Tab } from 'semantic-ui-react';
|
|||
import entryActions from '../../../entry-actions';
|
||||
import { useClosableModal } from '../../../hooks';
|
||||
import UsersPane from './UsersPane';
|
||||
import WebhooksPane from './WebhooksPane';
|
||||
|
||||
import styles from './AdministrationModal.module.scss';
|
||||
|
||||
|
@ -37,6 +38,12 @@ const AdministrationModal = React.memo(() => {
|
|||
}),
|
||||
render: () => <UsersPane />,
|
||||
},
|
||||
{
|
||||
menuItem: t('common.webhooks', {
|
||||
context: 'title',
|
||||
}),
|
||||
render: () => <WebhooksPane />,
|
||||
},
|
||||
];
|
||||
|
||||
const isUsersPaneActive = activeTabIndex === 0;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Tab } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import Webhooks from '../../webhooks/Webhooks';
|
||||
|
||||
import styles from './WebhooksPane.module.scss';
|
||||
|
||||
const WebhooksPane = React.memo(() => {
|
||||
const webhookIds = useSelector(selectors.selectWebhookIds);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleCreate = useCallback(
|
||||
(data) => {
|
||||
dispatch(entryActions.createWebhook(data));
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
return (
|
||||
<Tab.Pane attached={false} className={styles.wrapper}>
|
||||
<Webhooks ids={webhookIds} onCreate={handleCreate} />
|
||||
</Tab.Pane>
|
||||
);
|
||||
});
|
||||
|
||||
export default WebhooksPane;
|
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
:global(#app) {
|
||||
.wrapper {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
|
@ -28,21 +28,26 @@ const ConfirmationStep = React.memo(
|
|||
|
||||
const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (typeValue) {
|
||||
const cleanData = {
|
||||
...data,
|
||||
typeValue: data.typeValue.trim(),
|
||||
};
|
||||
const handleSubmit = useCallback(
|
||||
(event) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (cleanData.typeValue.toLowerCase() !== typeValue.toLowerCase()) {
|
||||
nameFieldRef.current.select();
|
||||
return;
|
||||
if (typeValue) {
|
||||
const cleanData = {
|
||||
...data,
|
||||
typeValue: data.typeValue.trim(),
|
||||
};
|
||||
|
||||
if (cleanData.typeValue.toLowerCase() !== typeValue.toLowerCase()) {
|
||||
nameFieldRef.current.select();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onConfirm();
|
||||
}, [typeValue, onConfirm, data, nameFieldRef]);
|
||||
onConfirm();
|
||||
},
|
||||
[typeValue, onConfirm, data, nameFieldRef],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeValue) {
|
||||
|
|
|
@ -125,7 +125,7 @@ const Item = React.memo(({ id }) => {
|
|||
dispatch(entryActions.deleteNotificationService(id));
|
||||
}, [id, dispatch]);
|
||||
|
||||
const handleUpdateSubmit = useCallback(() => {
|
||||
const handleSubmit = useCallback(() => {
|
||||
urlFieldRef.current.blur();
|
||||
}, [urlFieldRef]);
|
||||
|
||||
|
@ -153,7 +153,7 @@ const Item = React.memo(({ id }) => {
|
|||
const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);
|
||||
|
||||
return (
|
||||
<Form className={styles.wrapper} onSubmit={handleUpdateSubmit}>
|
||||
<Form className={styles.wrapper} onSubmit={handleSubmit}>
|
||||
<Input
|
||||
ref={handleUrlFieldRef}
|
||||
name="url"
|
||||
|
|
152
client/src/components/webhooks/Webhooks/Editor.jsx
Normal file
152
client/src/components/webhooks/Webhooks/Editor.jsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useCallback, useImperativeHandle } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dropdown, Input } from 'semantic-ui-react';
|
||||
|
||||
import { useNestedRef } from '../../../hooks';
|
||||
import WEBHOOK_EVENTS from '../../../constants/WebhookEvents';
|
||||
|
||||
import styles from './Editor.module.scss';
|
||||
|
||||
const Editor = React.forwardRef(({ data, isReadOnly, onFieldChange }, ref) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [nameFieldRef, handleNameFieldRef] = useNestedRef('inputRef');
|
||||
const [urlFieldRef, handleUrlFieldRef] = useNestedRef('inputRef');
|
||||
|
||||
const focusNameField = useCallback(() => {
|
||||
nameFieldRef.current.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
}, [nameFieldRef]);
|
||||
|
||||
const selectNameField = useCallback(() => {
|
||||
nameFieldRef.current.select();
|
||||
}, [nameFieldRef]);
|
||||
|
||||
const selectUrlField = useCallback(() => {
|
||||
urlFieldRef.current.select();
|
||||
}, [urlFieldRef]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
focusNameField,
|
||||
selectNameField,
|
||||
selectUrlField,
|
||||
}),
|
||||
[focusNameField, selectNameField, selectUrlField],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.text}>{t('common.title')}</div>
|
||||
<Input
|
||||
fluid
|
||||
ref={handleNameFieldRef}
|
||||
name="name"
|
||||
value={data.name}
|
||||
maxLength={128}
|
||||
readOnly={isReadOnly}
|
||||
className={styles.field}
|
||||
onChange={onFieldChange}
|
||||
/>
|
||||
<div className={styles.text}>{t('common.url')}</div>
|
||||
<Input
|
||||
fluid
|
||||
ref={handleUrlFieldRef}
|
||||
name="url"
|
||||
value={data.url}
|
||||
maxLength={2048}
|
||||
readOnly={isReadOnly}
|
||||
className={styles.field}
|
||||
onChange={onFieldChange}
|
||||
/>
|
||||
<div className={styles.text}>
|
||||
{t('common.accessToken')} (
|
||||
{t('common.optional', {
|
||||
context: 'inline',
|
||||
})}
|
||||
)
|
||||
</div>
|
||||
<Input
|
||||
fluid
|
||||
name="accessToken"
|
||||
value={data.accessToken}
|
||||
maxLength={512}
|
||||
readOnly={isReadOnly}
|
||||
className={styles.field}
|
||||
onChange={onFieldChange}
|
||||
/>
|
||||
{data.excludedEvents.length === 0 && (
|
||||
<>
|
||||
<div className={styles.text}>
|
||||
{t('common.events')} (
|
||||
{t('common.optional', {
|
||||
context: 'inline',
|
||||
})}
|
||||
)
|
||||
</div>
|
||||
<Dropdown
|
||||
selection
|
||||
multiple
|
||||
fluid
|
||||
name="events"
|
||||
options={WEBHOOK_EVENTS.map((event) => ({
|
||||
text: event,
|
||||
value: event,
|
||||
}))}
|
||||
value={data.events}
|
||||
placeholder="All"
|
||||
readOnly={isReadOnly}
|
||||
className={styles.field}
|
||||
onChange={onFieldChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{data.events.length === 0 && (
|
||||
<>
|
||||
<div className={styles.text}>
|
||||
{t('common.excludedEvents')} (
|
||||
{t('common.optional', {
|
||||
context: 'inline',
|
||||
})}
|
||||
)
|
||||
</div>
|
||||
<Dropdown
|
||||
selection
|
||||
multiple
|
||||
fluid
|
||||
name="excludedEvents"
|
||||
options={WEBHOOK_EVENTS.map((event) => ({
|
||||
text: event,
|
||||
value: event,
|
||||
}))}
|
||||
value={data.excludedEvents}
|
||||
placeholder="None"
|
||||
readOnly={isReadOnly}
|
||||
className={styles.field}
|
||||
onChange={onFieldChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Editor.propTypes = {
|
||||
data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
isReadOnly: PropTypes.bool,
|
||||
onFieldChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Editor.defaultProps = {
|
||||
isReadOnly: false,
|
||||
};
|
||||
|
||||
export default React.memo(Editor);
|
17
client/src/components/webhooks/Webhooks/Editor.module.scss
Normal file
17
client/src/components/webhooks/Webhooks/Editor.module.scss
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
:global(#app) {
|
||||
.field {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: #444444;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
}
|
137
client/src/components/webhooks/Webhooks/Item.jsx
Normal file
137
client/src/components/webhooks/Webhooks/Item.jsx
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { dequal } from 'dequal';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Accordion, Button, Form, Icon } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import { useForm, usePopupInClosableContext } from '../../../hooks';
|
||||
import { isUrl } from '../../../utils/validator';
|
||||
import Editor from './Editor';
|
||||
import ConfirmationStep from '../../common/ConfirmationStep';
|
||||
|
||||
import styles from './Item.module.scss';
|
||||
import { useToggle } from '../../../lib/hooks';
|
||||
|
||||
const Item = React.memo(({ id }) => {
|
||||
const selectWebhookById = useMemo(() => selectors.makeSelectWebhookById(), []);
|
||||
|
||||
const webhook = useSelector((state) => selectWebhookById(state, id));
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
const [isOpened, toggleOpened] = useToggle();
|
||||
|
||||
const defaultData = useMemo(
|
||||
() => ({
|
||||
name: webhook.name,
|
||||
url: webhook.url,
|
||||
accessToken: webhook.accessToken,
|
||||
events: webhook.events,
|
||||
excludedEvents: webhook.excludedEvents,
|
||||
}),
|
||||
[webhook],
|
||||
);
|
||||
|
||||
const [data, handleFieldChange] = useForm(() => ({
|
||||
name: '',
|
||||
url: '',
|
||||
...defaultData,
|
||||
accessToken: defaultData.accessToken || '',
|
||||
events: defaultData.events || [],
|
||||
excludedEvents: defaultData.excludedEvents || [],
|
||||
}));
|
||||
|
||||
const cleanData = useMemo(
|
||||
() => ({
|
||||
...data,
|
||||
name: data.name.trim(),
|
||||
url: data.url.trim(),
|
||||
accessToken: data.accessToken.trim() || null,
|
||||
events: data.events.length === 0 ? null : data.events,
|
||||
excludedEvents: data.excludedEvents.length === 0 ? null : data.excludedEvents,
|
||||
}),
|
||||
[data],
|
||||
);
|
||||
|
||||
const editorRef = useRef(null);
|
||||
|
||||
const handleDeleteConfirm = useCallback(() => {
|
||||
dispatch(entryActions.deleteWebhook(id));
|
||||
}, [id, dispatch]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (!cleanData.name) {
|
||||
editorRef.current.selectNameField();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cleanData.url || !isUrl(cleanData.url)) {
|
||||
editorRef.current.selectUrlField();
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(entryActions.updateWebhook(id, cleanData));
|
||||
}, [id, dispatch, cleanData]);
|
||||
|
||||
const handleOpenClick = useCallback(() => {
|
||||
toggleOpened();
|
||||
}, [toggleOpened]);
|
||||
|
||||
const ConfirmationPopup = usePopupInClosableContext(ConfirmationStep);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Accordion.Title active={isOpened} className={styles.title} onClick={handleOpenClick}>
|
||||
<Icon name="dropdown" />
|
||||
{defaultData.name}
|
||||
</Accordion.Title>
|
||||
<Accordion.Content active={isOpened}>
|
||||
<div>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Editor
|
||||
ref={editorRef}
|
||||
data={data}
|
||||
isReadOnly={!webhook.isPersisted}
|
||||
onFieldChange={handleFieldChange}
|
||||
/>
|
||||
<div className={styles.controls}>
|
||||
<Button
|
||||
positive
|
||||
disabled={dequal(cleanData, defaultData)}
|
||||
content={t('action.save')}
|
||||
/>
|
||||
<ConfirmationPopup
|
||||
title="common.deleteWebhook"
|
||||
content="common.areYouSureYouWantToDeleteThisWebhook"
|
||||
buttonContent="action.deleteWebhook"
|
||||
onConfirm={handleDeleteConfirm}
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={!webhook.isPersisted}
|
||||
className={styles.deleteButton}
|
||||
>
|
||||
{t('action.delete')}
|
||||
</Button>
|
||||
</ConfirmationPopup>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</Accordion.Content>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Item.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Item;
|
22
client/src/components/webhooks/Webhooks/Item.module.scss
Normal file
22
client/src/components/webhooks/Webhooks/Item.module.scss
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
:global(#app) {
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.deleteButton {
|
||||
box-shadow: 0 1px 0 #cbcccc;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
96
client/src/components/webhooks/Webhooks/Webhooks.jsx
Normal file
96
client/src/components/webhooks/Webhooks/Webhooks.jsx
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Accordion, Button, Form, Segment } from 'semantic-ui-react';
|
||||
import { useDidUpdate, useToggle } from '../../../lib/hooks';
|
||||
|
||||
import { useForm } from '../../../hooks';
|
||||
import { isUrl } from '../../../utils/validator';
|
||||
import Item from './Item';
|
||||
import Editor from './Editor';
|
||||
|
||||
const DEFAULT_DATA = {
|
||||
name: '',
|
||||
url: '',
|
||||
accessToken: '',
|
||||
events: [],
|
||||
excludedEvents: [],
|
||||
};
|
||||
|
||||
const Webhooks = React.memo(({ ids, onCreate }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
||||
const [focusNameFieldState, focusNameField] = useToggle();
|
||||
|
||||
const editorRef = useRef(null);
|
||||
|
||||
const handleCreateSubmit = useCallback(() => {
|
||||
const cleanData = {
|
||||
...data,
|
||||
name: data.name.trim(),
|
||||
url: data.url.trim(),
|
||||
accessToken: data.accessToken.trim() || null,
|
||||
events: data.events.length === 0 ? null : data.events,
|
||||
excludedEvents: data.excludedEvents.length === 0 ? null : data.excludedEvents,
|
||||
};
|
||||
|
||||
if (!cleanData.name) {
|
||||
editorRef.current.selectNameField();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cleanData.url || !isUrl(cleanData.url)) {
|
||||
editorRef.current.selectUrlField();
|
||||
return;
|
||||
}
|
||||
|
||||
onCreate(cleanData);
|
||||
setData(DEFAULT_DATA);
|
||||
focusNameField();
|
||||
}, [onCreate, data, setData, focusNameField]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.focusNameField();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidUpdate(() => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.focusNameField();
|
||||
}
|
||||
}, [focusNameFieldState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ids.length > 0 && (
|
||||
<Accordion styled fluid>
|
||||
{ids.map((id) => (
|
||||
<Item key={id} id={id} />
|
||||
))}
|
||||
</Accordion>
|
||||
)}
|
||||
{ids.length < 10 && (
|
||||
<Segment>
|
||||
<Form onSubmit={handleCreateSubmit}>
|
||||
<Editor ref={editorRef} data={data} onFieldChange={handleFieldChange} />
|
||||
<Button positive>{t('action.addWebhook')}</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Webhooks.propTypes = {
|
||||
ids: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Webhooks;
|
8
client/src/components/webhooks/Webhooks/index.js
Normal file
8
client/src/components/webhooks/Webhooks/index.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import Webhooks from './Webhooks';
|
||||
|
||||
export default Webhooks;
|
|
@ -42,6 +42,21 @@ export default {
|
|||
MODAL_OPEN: 'MODAL_OPEN',
|
||||
MODAL_CLOSE: 'MODAL_CLOSE',
|
||||
|
||||
/* Webhooks */
|
||||
|
||||
WEBHOOK_CREATE: 'WEBHOOK_CREATE',
|
||||
WEBHOOK_CREATE__SUCCESS: 'WEBHOOK_CREATE__SUCCESS',
|
||||
WEBHOOK_CREATE__FAILURE: 'WEBHOOK_CREATE__FAILURE',
|
||||
WEBHOOK_CREATE_HANDLE: 'WEBHOOK_CREATE_HANDLE',
|
||||
WEBHOOK_UPDATE: 'WEBHOOK_UPDATE',
|
||||
WEBHOOK_UPDATE__SUCCESS: 'WEBHOOK_UPDATE__SUCCESS',
|
||||
WEBHOOK_UPDATE__FAILURE: 'WEBHOOK_UPDATE__FAILURE',
|
||||
WEBHOOK_UPDATE_HANDLE: 'WEBHOOK_UPDATE_HANDLE',
|
||||
WEBHOOK_DELETE: 'WEBHOOK_DELETE',
|
||||
WEBHOOK_DELETE__SUCCESS: 'WEBHOOK_DELETE__SUCCESS',
|
||||
WEBHOOK_DELETE__FAILURE: 'WEBHOOK_DELETE__FAILURE',
|
||||
WEBHOOK_DELETE_HANDLE: 'WEBHOOK_DELETE_HANDLE',
|
||||
|
||||
/* Users */
|
||||
|
||||
USER_CREATE: 'USER_CREATE',
|
||||
|
|
|
@ -31,6 +31,15 @@ export default {
|
|||
MODAL_OPEN: `${PREFIX}/MODAL_OPEN`,
|
||||
MODAL_CLOSE: `${PREFIX}/MODAL_CLOSE`,
|
||||
|
||||
/* Webhooks */
|
||||
|
||||
WEBHOOK_CREATE: `${PREFIX}/WEBHOOK_CREATE`,
|
||||
WEBHOOK_CREATE_HANDLE: `${PREFIX}/WEBHOOK_CREATE_HANDLE`,
|
||||
WEBHOOK_UPDATE: `${PREFIX}/WEBHOOK_UPDATE`,
|
||||
WEBHOOK_UPDATE_HANDLE: `${PREFIX}/WEBHOOK_UPDATE_HANDLE`,
|
||||
WEBHOOK_DELETE: `${PREFIX}/WEBHOOK_DELETE`,
|
||||
WEBHOOK_DELETE_HANDLE: `${PREFIX}/WEBHOOK_DELETE_HANDLE`,
|
||||
|
||||
/* Users */
|
||||
|
||||
USER_CREATE: `${PREFIX}/USER_CREATE`,
|
||||
|
|
91
client/src/constants/WebhookEvents.js
Normal file
91
client/src/constants/WebhookEvents.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
export default [
|
||||
'actionCreate',
|
||||
|
||||
'attachmentCreate',
|
||||
'attachmentUpdate',
|
||||
'attachmentDelete',
|
||||
|
||||
'backgroundImageCreate',
|
||||
'backgroundImageDelete',
|
||||
|
||||
'baseCustomFieldGroupCreate',
|
||||
'baseCustomFieldGroupUpdate',
|
||||
'baseCustomFieldGroupDelete',
|
||||
|
||||
'boardCreate',
|
||||
'boardUpdate',
|
||||
'boardDelete',
|
||||
|
||||
'boardMembershipCreate',
|
||||
'boardMembershipUpdate',
|
||||
'boardMembershipDelete',
|
||||
|
||||
'cardCreate',
|
||||
'cardUpdate',
|
||||
'cardDelete',
|
||||
|
||||
'cardLabelCreate',
|
||||
'cardLabelDelete',
|
||||
|
||||
'cardMembershipCreate',
|
||||
'cardMembershipDelete',
|
||||
|
||||
'commentCreate',
|
||||
'commentUpdate',
|
||||
'commentDelete',
|
||||
|
||||
'customFieldCreate',
|
||||
'customFieldUpdate',
|
||||
'customFieldDelete',
|
||||
|
||||
'customFieldGroupCreate',
|
||||
'customFieldGroupUpdate',
|
||||
'customFieldGroupDelete',
|
||||
|
||||
'customFieldValueUpdate',
|
||||
'customFieldValueDelete',
|
||||
|
||||
'labelCreate',
|
||||
'labelUpdate',
|
||||
'labelDelete',
|
||||
|
||||
'listCreate',
|
||||
'listUpdate',
|
||||
'listClear',
|
||||
'listDelete',
|
||||
|
||||
'notificationCreate',
|
||||
'notificationUpdate',
|
||||
|
||||
'notificationServiceCreate',
|
||||
'notificationServiceUpdate',
|
||||
'notificationServiceDelete',
|
||||
|
||||
'projectCreate',
|
||||
'projectUpdate',
|
||||
'projectDelete',
|
||||
|
||||
'projectManagerCreate',
|
||||
'projectManagerDelete',
|
||||
|
||||
'taskCreate',
|
||||
'taskUpdate',
|
||||
'taskDelete',
|
||||
|
||||
'taskListCreate',
|
||||
'taskListUpdate',
|
||||
'taskListDelete',
|
||||
|
||||
'userCreate',
|
||||
'userUpdate',
|
||||
'userDelete',
|
||||
|
||||
'webhookCreate',
|
||||
'webhookUpdate',
|
||||
'webhookDelete',
|
||||
];
|
|
@ -7,6 +7,7 @@ import socket from './socket';
|
|||
import login from './login';
|
||||
import core from './core';
|
||||
import modals from './modals';
|
||||
import webhooks from './webhooks';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
import projectManagers from './project-managers';
|
||||
|
@ -33,6 +34,7 @@ export default {
|
|||
...login,
|
||||
...core,
|
||||
...modals,
|
||||
...webhooks,
|
||||
...users,
|
||||
...projects,
|
||||
...projectManagers,
|
||||
|
|
58
client/src/entry-actions/webhooks.js
Normal file
58
client/src/entry-actions/webhooks.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import EntryActionTypes from '../constants/EntryActionTypes';
|
||||
|
||||
const createWebhook = (data) => ({
|
||||
type: EntryActionTypes.WEBHOOK_CREATE,
|
||||
payload: {
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
const handleWebhookCreate = (webhook) => ({
|
||||
type: EntryActionTypes.WEBHOOK_CREATE_HANDLE,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
const updateWebhook = (id, data) => ({
|
||||
type: EntryActionTypes.WEBHOOK_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
const handleWebhookUpdate = (webhook) => ({
|
||||
type: EntryActionTypes.WEBHOOK_UPDATE_HANDLE,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteWebhook = (id) => ({
|
||||
type: EntryActionTypes.WEBHOOK_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const handleWebhookDelete = (webhook) => ({
|
||||
type: EntryActionTypes.WEBHOOK_DELETE_HANDLE,
|
||||
payload: {
|
||||
webhook,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createWebhook,
|
||||
handleWebhookCreate,
|
||||
updateWebhook,
|
||||
handleWebhookUpdate,
|
||||
deleteWebhook,
|
||||
handleWebhookDelete,
|
||||
};
|
|
@ -21,6 +21,7 @@ export default {
|
|||
translation: {
|
||||
common: {
|
||||
aboutPlanka: 'About PLANKA',
|
||||
accessToken: 'Access token',
|
||||
account: 'Account',
|
||||
actions: 'Actions',
|
||||
activateUser_title: 'Activate User',
|
||||
|
@ -69,6 +70,7 @@ export default {
|
|||
areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?',
|
||||
areYouSureYouWantToDeleteThisTaskList: 'Are you sure you want to delete this task list?',
|
||||
areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?',
|
||||
areYouSureYouWantToDeleteThisWebhook: 'Are you sure you want to delete this webhook?',
|
||||
areYouSureYouWantToEmptyTrash: 'Are you sure you want to empty the trash?',
|
||||
areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?',
|
||||
areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?',
|
||||
|
@ -152,6 +154,7 @@ export default {
|
|||
deleteTask_title: 'Delete Task',
|
||||
deleteUser_title: 'Delete User',
|
||||
deletedUser_title: 'Deleted User',
|
||||
deleteWebhook_title: 'Delete Webhook',
|
||||
description: 'Description',
|
||||
detectAutomatically: 'Detect automatically',
|
||||
display: 'Display',
|
||||
|
@ -182,6 +185,8 @@ export default {
|
|||
enterFilename: 'Enter filename',
|
||||
enterListTitle: 'Enter list title...',
|
||||
enterTaskDescription: 'Enter task description...',
|
||||
events: 'Events',
|
||||
excludedEvents: 'Excluded events',
|
||||
filterByLabels_title: 'Filter By Labels',
|
||||
filterByMembers_title: 'Filter By Members',
|
||||
forPersonalProjects: 'For personal projects.',
|
||||
|
@ -286,6 +291,7 @@ export default {
|
|||
typeTitleToConfirm: 'Type the title to confirm.',
|
||||
unsavedChanges: 'Unsaved changes',
|
||||
uploadedImages: 'Uploaded images',
|
||||
url: 'URL',
|
||||
userActions_title: 'User Actions',
|
||||
userAddedCardToList: '<0>{{user}}</0> added <2>{{card}}</2> to {{list}}',
|
||||
userAddedThisCardToList: '<0>{{user}}</0> added this card to {{list}}',
|
||||
|
@ -313,6 +319,7 @@ export default {
|
|||
userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removed {{removedUser}} from this card',
|
||||
username: 'Username',
|
||||
users: 'Users',
|
||||
webhooks: 'Webhooks',
|
||||
viewer: 'Viewer',
|
||||
viewers: 'Viewers',
|
||||
visualTaskManagementWithLists: 'Visual task management with lists.',
|
||||
|
@ -338,6 +345,7 @@ export default {
|
|||
addTaskList: 'Add task list',
|
||||
addToCard: 'Add to card',
|
||||
addUser: 'Add user',
|
||||
addWebhook: 'Add webhook',
|
||||
archive: 'Archive',
|
||||
archiveCard: 'Archive card',
|
||||
archiveCard_title: 'Archive Card',
|
||||
|
@ -378,6 +386,7 @@ export default {
|
|||
deleteTask_title: 'Delete Task',
|
||||
deleteUser: 'Delete user',
|
||||
deleteUser_title: 'Delete User',
|
||||
deleteWebhook: 'Delete webhook',
|
||||
dismissAll: 'Dismiss all',
|
||||
duplicate: 'Duplicate',
|
||||
duplicateCard_title: 'Duplicate Card',
|
||||
|
|
|
@ -16,6 +16,7 @@ export default {
|
|||
translation: {
|
||||
common: {
|
||||
aboutPlanka: 'About PLANKA',
|
||||
accessToken: 'Access token',
|
||||
account: 'Account',
|
||||
actions: 'Actions',
|
||||
activateUser_title: 'Activate User',
|
||||
|
@ -64,6 +65,7 @@ export default {
|
|||
areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?',
|
||||
areYouSureYouWantToDeleteThisTaskList: 'Are you sure you want to delete this task list?',
|
||||
areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?',
|
||||
areYouSureYouWantToDeleteThisWebhook: 'Are you sure you want to delete this webhook?',
|
||||
areYouSureYouWantToEmptyTrash: 'Are you sure you want to empty the trash?',
|
||||
areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?',
|
||||
areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?',
|
||||
|
@ -147,6 +149,7 @@ export default {
|
|||
deleteTask_title: 'Delete Task',
|
||||
deleteUser_title: 'Delete User',
|
||||
deletedUser_title: 'Deleted User',
|
||||
deleteWebhook_title: 'Delete Webhook',
|
||||
description: 'Description',
|
||||
detectAutomatically: 'Detect automatically',
|
||||
display: 'Display',
|
||||
|
@ -177,6 +180,8 @@ export default {
|
|||
enterFilename: 'Enter filename',
|
||||
enterListTitle: 'Enter list title...',
|
||||
enterTaskDescription: 'Enter task description...',
|
||||
events: 'Events',
|
||||
excludedEvents: 'Excluded events',
|
||||
filterByLabels_title: 'Filter By Labels',
|
||||
filterByMembers_title: 'Filter By Members',
|
||||
forPersonalProjects: 'For personal projects.',
|
||||
|
@ -281,6 +286,7 @@ export default {
|
|||
typeTitleToConfirm: 'Type the title to confirm.',
|
||||
unsavedChanges: 'Unsaved changes',
|
||||
uploadedImages: 'Uploaded images',
|
||||
url: 'URL',
|
||||
userActions_title: 'User Actions',
|
||||
userAddedCardToList: '<0>{{user}}</0> added <2>{{card}}</2> to {{list}}',
|
||||
userAddedThisCardToList: '<0>{{user}}</0> added this card to {{list}}',
|
||||
|
@ -308,6 +314,7 @@ export default {
|
|||
userRemovedUserFromThisCard: '<0>{{actorUser}}</0> removed {{removedUser}} from this card',
|
||||
username: 'Username',
|
||||
users: 'Users',
|
||||
webhooks: 'Webhooks',
|
||||
viewer: 'Viewer',
|
||||
viewers: 'Viewers',
|
||||
visualTaskManagementWithLists: 'Visual task management with lists.',
|
||||
|
@ -333,6 +340,7 @@ export default {
|
|||
addTaskList: 'Add task list',
|
||||
addToCard: 'Add to card',
|
||||
addUser: 'Add user',
|
||||
addWebhook: 'Add webhook',
|
||||
archive: 'Archive',
|
||||
archiveCard: 'Archive card',
|
||||
archiveCard_title: 'Archive Card',
|
||||
|
@ -373,6 +381,7 @@ export default {
|
|||
deleteTask_title: 'Delete Task',
|
||||
deleteUser: 'Delete user',
|
||||
deleteUser_title: 'Delete User',
|
||||
deleteWebhook: 'Delete webhook',
|
||||
dismissAll: 'Dismiss all',
|
||||
duplicate: 'Duplicate',
|
||||
duplicateCard_title: 'Duplicate Card',
|
||||
|
|
88
client/src/models/Webhook.js
Normal file
88
client/src/models/Webhook.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { attr, fk } from 'redux-orm';
|
||||
|
||||
import BaseModel from './BaseModel';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
export default class extends BaseModel {
|
||||
static modelName = 'Webhook';
|
||||
|
||||
static fields = {
|
||||
id: attr(),
|
||||
name: attr(),
|
||||
url: attr(),
|
||||
accessToken: attr(),
|
||||
events: attr(),
|
||||
excludedEvents: attr(),
|
||||
boardId: fk({
|
||||
to: 'Board',
|
||||
as: 'board',
|
||||
relatedName: 'webhooks',
|
||||
}),
|
||||
};
|
||||
|
||||
static reducer({ type, payload }, Webhook) {
|
||||
switch (type) {
|
||||
case ActionTypes.SOCKET_RECONNECT_HANDLE:
|
||||
Webhook.all().delete();
|
||||
|
||||
payload.webhooks.forEach((webhook) => {
|
||||
Webhook.upsert(webhook);
|
||||
});
|
||||
|
||||
break;
|
||||
case ActionTypes.CORE_INITIALIZE:
|
||||
case ActionTypes.USER_UPDATE_HANDLE:
|
||||
if (payload.webhooks) {
|
||||
payload.webhooks.forEach((webhook) => {
|
||||
Webhook.upsert(webhook);
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ActionTypes.WEBHOOK_CREATE:
|
||||
case ActionTypes.WEBHOOK_CREATE_HANDLE:
|
||||
case ActionTypes.WEBHOOK_UPDATE__SUCCESS:
|
||||
case ActionTypes.WEBHOOK_UPDATE_HANDLE:
|
||||
Webhook.upsert(payload.webhook);
|
||||
|
||||
break;
|
||||
case ActionTypes.WEBHOOK_CREATE__SUCCESS:
|
||||
Webhook.withId(payload.localId).delete();
|
||||
Webhook.upsert(payload.webhook);
|
||||
|
||||
break;
|
||||
case ActionTypes.WEBHOOK_CREATE__FAILURE:
|
||||
Webhook.withId(payload.localId).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.WEBHOOK_UPDATE:
|
||||
Webhook.withId(payload.id).update(payload.data);
|
||||
|
||||
break;
|
||||
case ActionTypes.WEBHOOK_DELETE:
|
||||
Webhook.withId(payload.id).delete();
|
||||
|
||||
break;
|
||||
case ActionTypes.WEBHOOK_DELETE__SUCCESS:
|
||||
case ActionTypes.WEBHOOK_DELETE_HANDLE: {
|
||||
const webhookModel = Webhook.withId(payload.webhook.id);
|
||||
|
||||
if (webhookModel) {
|
||||
webhookModel.delete();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
static getAllQuerySet() {
|
||||
return this.orderBy(['id.length', 'id']);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import Webhook from './Webhook';
|
||||
import User from './User';
|
||||
import Project from './Project';
|
||||
import ProjectManager from './ProjectManager';
|
||||
|
@ -25,6 +26,7 @@ import Notification from './Notification';
|
|||
import NotificationService from './NotificationService';
|
||||
|
||||
export {
|
||||
Webhook,
|
||||
User,
|
||||
Project,
|
||||
ProjectManager,
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
Task,
|
||||
TaskList,
|
||||
User,
|
||||
Webhook,
|
||||
} from './models';
|
||||
|
||||
const orm = new ORM({
|
||||
|
@ -33,6 +34,7 @@ const orm = new ORM({
|
|||
});
|
||||
|
||||
orm.register(
|
||||
Webhook,
|
||||
User,
|
||||
Project,
|
||||
ProjectManager,
|
||||
|
|
|
@ -10,6 +10,7 @@ import request from '../request';
|
|||
import api from '../../../api';
|
||||
import mergeRecords from '../../../utils/merge-records';
|
||||
import { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';
|
||||
import { UserRoles } from '../../../constants/Enums';
|
||||
|
||||
export function* fetchCore() {
|
||||
const {
|
||||
|
@ -17,6 +18,11 @@ export function* fetchCore() {
|
|||
included: { notificationServices: notificationServices1 },
|
||||
} = yield call(request, api.getCurrentUser, true);
|
||||
|
||||
let webhooks;
|
||||
if (user.role === UserRoles.ADMIN) {
|
||||
({ items: webhooks } = yield call(request, api.getWebhooks));
|
||||
}
|
||||
|
||||
let users1;
|
||||
if (isUserAdminOrProjectOwner(user)) {
|
||||
({ items: users1 } = yield call(request, api.getUsers));
|
||||
|
@ -101,6 +107,7 @@ export function* fetchCore() {
|
|||
return {
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
|
|
|
@ -21,6 +21,7 @@ export function* initializeCore() {
|
|||
const {
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
@ -50,6 +51,7 @@ export function* initializeCore() {
|
|||
actions.initializeCore(
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
|
|
@ -7,6 +7,7 @@ import router from './router';
|
|||
import socket from './socket';
|
||||
import core from './core';
|
||||
import modals from './modals';
|
||||
import webhooks from './webhooks';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
import projectManagers from './project-managers';
|
||||
|
@ -33,6 +34,7 @@ export default {
|
|||
...socket,
|
||||
...core,
|
||||
...modals,
|
||||
...webhooks,
|
||||
...users,
|
||||
...projects,
|
||||
...projectManagers,
|
||||
|
|
|
@ -24,6 +24,7 @@ export function* handleSocketReconnect() {
|
|||
let config;
|
||||
let user;
|
||||
let board;
|
||||
let webhooks;
|
||||
let users;
|
||||
let projects;
|
||||
let projectManagers;
|
||||
|
@ -51,6 +52,7 @@ export function* handleSocketReconnect() {
|
|||
({
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
@ -81,6 +83,7 @@ export function* handleSocketReconnect() {
|
|||
config,
|
||||
user,
|
||||
board,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
|
|
@ -70,6 +70,7 @@ export function* handleUserUpdate(user) {
|
|||
|
||||
let config;
|
||||
let board;
|
||||
let webhooks;
|
||||
let users1;
|
||||
let users2;
|
||||
let users3;
|
||||
|
@ -102,6 +103,7 @@ export function* handleUserUpdate(user) {
|
|||
|
||||
if (user.role === UserRoles.ADMIN) {
|
||||
({ item: config } = yield call(request, api.getConfig));
|
||||
({ items: webhooks } = yield call(request, api.getWebhooks));
|
||||
|
||||
({
|
||||
items: projects,
|
||||
|
@ -164,6 +166,7 @@ export function* handleUserUpdate(user) {
|
|||
boardIds,
|
||||
config,
|
||||
board,
|
||||
webhooks,
|
||||
mergeRecords(users1, users2, users3),
|
||||
projects,
|
||||
projectManagers,
|
||||
|
|
89
client/src/sagas/core/services/webhooks.js
Normal file
89
client/src/sagas/core/services/webhooks.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { call, put } from 'redux-saga/effects';
|
||||
|
||||
import request from '../request';
|
||||
import actions from '../../../actions';
|
||||
import api from '../../../api';
|
||||
import { createLocalId } from '../../../utils/local-id';
|
||||
|
||||
export function* createWebhook(data) {
|
||||
const localId = yield call(createLocalId);
|
||||
|
||||
yield put(
|
||||
actions.createWebhook({
|
||||
...data,
|
||||
id: localId,
|
||||
}),
|
||||
);
|
||||
|
||||
let webhook;
|
||||
try {
|
||||
({ item: webhook } = yield call(request, api.createWebhook, {
|
||||
...data,
|
||||
events: data.events && data.events.join(','),
|
||||
excludedEvents: data.excludedEvents && data.excludedEvents.join(','),
|
||||
}));
|
||||
} catch (error) {
|
||||
yield put(actions.createWebhook.failure(localId, error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(actions.createWebhook.success(localId, webhook));
|
||||
}
|
||||
|
||||
export function* handleWebhookCreate(webhook) {
|
||||
yield put(actions.handleWebhookCreate(webhook));
|
||||
}
|
||||
|
||||
export function* updateWebhook(id, data) {
|
||||
yield put(actions.updateWebhook(id, data));
|
||||
|
||||
let webhook;
|
||||
try {
|
||||
({ item: webhook } = yield call(request, api.updateWebhook, id, {
|
||||
...data,
|
||||
events: data.events && data.events.join(','),
|
||||
excludedEvents: data.excludedEvents && data.excludedEvents.join(','),
|
||||
}));
|
||||
} catch (error) {
|
||||
yield put(actions.updateWebhook.failure(id, error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(actions.updateWebhook.success(webhook));
|
||||
}
|
||||
|
||||
export function* handleWebhookUpdate(webhook) {
|
||||
yield put(actions.handleWebhookUpdate(webhook));
|
||||
}
|
||||
|
||||
export function* deleteWebhook(id) {
|
||||
yield put(actions.deleteWebhook(id));
|
||||
|
||||
let webhook;
|
||||
try {
|
||||
({ item: webhook } = yield call(request, api.deleteWebhook, id));
|
||||
} catch (error) {
|
||||
yield put(actions.deleteWebhook.failure(id, error));
|
||||
return;
|
||||
}
|
||||
|
||||
yield put(actions.deleteWebhook.success(webhook));
|
||||
}
|
||||
|
||||
export function* handleWebhookDelete(webhook) {
|
||||
yield put(actions.handleWebhookDelete(webhook));
|
||||
}
|
||||
|
||||
export default {
|
||||
createWebhook,
|
||||
handleWebhookCreate,
|
||||
updateWebhook,
|
||||
handleWebhookUpdate,
|
||||
deleteWebhook,
|
||||
handleWebhookDelete,
|
||||
};
|
|
@ -7,6 +7,7 @@ import router from './router';
|
|||
import socket from './socket';
|
||||
import core from './core';
|
||||
import modals from './modals';
|
||||
import webhooks from './webhooks';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
import projectManagers from './project-managers';
|
||||
|
@ -33,6 +34,7 @@ export default [
|
|||
socket,
|
||||
core,
|
||||
modals,
|
||||
webhooks,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
|
|
30
client/src/sagas/core/watchers/webhooks.js
Normal file
30
client/src/sagas/core/watchers/webhooks.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { all, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import services from '../services';
|
||||
import EntryActionTypes from '../../../constants/EntryActionTypes';
|
||||
|
||||
export default function* webhooksWatchers() {
|
||||
yield all([
|
||||
takeEvery(EntryActionTypes.WEBHOOK_CREATE, ({ payload: { data } }) =>
|
||||
services.createWebhook(data),
|
||||
),
|
||||
takeEvery(EntryActionTypes.WEBHOOK_CREATE_HANDLE, ({ payload: { webhook } }) =>
|
||||
services.handleWebhookCreate(webhook),
|
||||
),
|
||||
takeEvery(EntryActionTypes.WEBHOOK_UPDATE, ({ payload: { id, data } }) =>
|
||||
services.updateWebhook(id, data),
|
||||
),
|
||||
takeEvery(EntryActionTypes.WEBHOOK_UPDATE_HANDLE, ({ payload: { webhook } }) =>
|
||||
services.handleWebhookUpdate(webhook),
|
||||
),
|
||||
takeEvery(EntryActionTypes.WEBHOOK_DELETE, ({ payload: { id } }) => services.deleteWebhook(id)),
|
||||
takeEvery(EntryActionTypes.WEBHOOK_DELETE_HANDLE, ({ payload: { webhook } }) =>
|
||||
services.handleWebhookDelete(webhook),
|
||||
),
|
||||
]);
|
||||
}
|
|
@ -8,6 +8,7 @@ import common from './common';
|
|||
import core from './core';
|
||||
import modals from './modals';
|
||||
import positioning from './positioning';
|
||||
import webhooks from './webhooks';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
import projectManagers from './project-managers';
|
||||
|
@ -35,6 +36,7 @@ export default {
|
|||
...core,
|
||||
...modals,
|
||||
...positioning,
|
||||
...webhooks,
|
||||
...users,
|
||||
...projects,
|
||||
...projectManagers,
|
||||
|
|
41
client/src/selectors/webhooks.js
Normal file
41
client/src/selectors/webhooks.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { createSelector } from 'redux-orm';
|
||||
|
||||
import orm from '../orm';
|
||||
import { isLocalId } from '../utils/local-id';
|
||||
|
||||
export const makeSelectWebhookById = () =>
|
||||
createSelector(
|
||||
orm,
|
||||
(_, id) => id,
|
||||
({ Webhook }, id) => {
|
||||
const webhookModel = Webhook.withId(id);
|
||||
|
||||
if (!webhookModel) {
|
||||
return webhookModel;
|
||||
}
|
||||
|
||||
return {
|
||||
...webhookModel.ref,
|
||||
isPersisted: !isLocalId(webhookModel.id),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const selectWebhookById = makeSelectWebhookById();
|
||||
|
||||
export const selectWebhookIds = createSelector(orm, ({ Webhook }) =>
|
||||
Webhook.getAllQuerySet()
|
||||
.toRefArray()
|
||||
.map((webhook) => webhook.id),
|
||||
);
|
||||
|
||||
export default {
|
||||
makeSelectWebhookById,
|
||||
selectWebhookById,
|
||||
selectWebhookIds,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue