mirror of
https://github.com/strapi/strapi.git
synced 2025-11-03 11:25:17 +00:00
Merge pull request #16700 from strapi/webhooks/list-view
This commit is contained in:
commit
ee413187df
@ -1 +1 @@
|
||||
export const MARKETPLACE_API_URL = 'https://market-api.strapi.io';
|
||||
export const MARKETPLACE_API_URL = 'https://market-api.strapi.io';
|
||||
|
||||
@ -5,7 +5,18 @@ import { useFormikContext } from 'formik';
|
||||
import { useIntl } from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
import EventRow from './EventRow';
|
||||
import formatValue from './utils/formatValue';
|
||||
|
||||
export const formatValue = (value) =>
|
||||
value.reduce((acc, curr) => {
|
||||
const key = curr.split('.')[0];
|
||||
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push(curr);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const StyledTable = styled.table`
|
||||
td {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import formatValue from '../utils/formatValue';
|
||||
import { formatValue } from '..';
|
||||
|
||||
describe('utils | formatValue', () => {
|
||||
it('should format array to object', () => {
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
const formatValue = (value) =>
|
||||
value.reduce((acc, curr) => {
|
||||
const key = curr.split('.')[0];
|
||||
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push(curr);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
export default formatValue;
|
||||
@ -1,13 +1,9 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
/**
|
||||
*
|
||||
* ListView
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { useEffect, useReducer, useRef, useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useQuery, useMutation } from 'react-query';
|
||||
|
||||
import {
|
||||
useFetchClient,
|
||||
useRBAC,
|
||||
@ -19,6 +15,7 @@ import {
|
||||
onRowClick,
|
||||
stopPropagation,
|
||||
LinkButton,
|
||||
useAPIErrorHandler,
|
||||
} from '@strapi/helper-plugin';
|
||||
import {
|
||||
HeaderLayout,
|
||||
@ -45,212 +42,124 @@ import {
|
||||
VisuallyHidden,
|
||||
} from '@strapi/design-system';
|
||||
import { Plus, Pencil, Trash, EmptyDocuments } from '@strapi/icons';
|
||||
import reducer, { initialState } from './reducer';
|
||||
import adminPermissions from '../../../../../permissions';
|
||||
|
||||
const ListView = () => {
|
||||
const {
|
||||
isLoading,
|
||||
allowedActions: { canCreate, canRead, canUpdate, canDelete },
|
||||
} = useRBAC(adminPermissions.settings.webhooks);
|
||||
const toggleNotification = useNotification();
|
||||
const isMounted = useRef(true);
|
||||
const { formatMessage } = useIntl();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [{ webhooks, webhooksToDelete, webhookToDelete, loadingWebhooks }, dispatch] = useReducer(
|
||||
reducer,
|
||||
initialState
|
||||
);
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
|
||||
const { get, del, post, put } = useFetchClient();
|
||||
const [webhooksToDelete, setWebhooksToDelete] = useState([]);
|
||||
|
||||
const { formatMessage } = useIntl();
|
||||
const { formatAPIError } = useAPIErrorHandler();
|
||||
const toggleNotification = useNotification();
|
||||
useFocusWhenNavigate();
|
||||
const { push } = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const rowsCount = webhooks.length;
|
||||
const webhooksToDeleteLength = webhooksToDelete.length;
|
||||
const getWebhookIndex = (id) => webhooks.findIndex((webhook) => webhook.id === id);
|
||||
|
||||
const {
|
||||
isLoading: isRBACLoading,
|
||||
allowedActions: { canCreate, canUpdate, canDelete },
|
||||
} = useRBAC(adminPermissions.settings.webhooks);
|
||||
const { get, post, put } = useFetchClient();
|
||||
const { notifyStatus } = useNotifyAT();
|
||||
|
||||
const QUERY_KEY = 'webhooks';
|
||||
const {
|
||||
isLoading: isWebhooksLoading,
|
||||
data: webhooks,
|
||||
error: webhooksError,
|
||||
refetch: refetchWebhooks,
|
||||
} = useQuery(QUERY_KEY, async () => {
|
||||
const {
|
||||
data: { data },
|
||||
} = await get('/admin/webhooks');
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* TODO: refactor this, but actually refactor
|
||||
* the whole component. Needs some love.
|
||||
*/
|
||||
useEffect(() => {
|
||||
const fetchWebHooks = async () => {
|
||||
try {
|
||||
const {
|
||||
data: { data },
|
||||
} = await get('/admin/webhooks');
|
||||
|
||||
if (isMounted.current) {
|
||||
dispatch({
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
data,
|
||||
});
|
||||
notifyStatus('webhooks have been loaded');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
if (isMounted.current) {
|
||||
if (err.code !== 20) {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
}
|
||||
dispatch({
|
||||
type: 'TOGGLE_LOADING',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (canRead) {
|
||||
fetchWebHooks();
|
||||
}
|
||||
}, [canRead, get, notifyStatus, toggleNotification]);
|
||||
|
||||
const handleToggleModal = () => {
|
||||
setShowModal((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
if (webhookToDelete) {
|
||||
handleConfirmDeleteOne();
|
||||
} else {
|
||||
handleConfirmDeleteAll();
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmDeleteOne = async () => {
|
||||
try {
|
||||
await del(`/admin/webhooks/${webhookToDelete}`);
|
||||
|
||||
dispatch({
|
||||
type: 'WEBHOOK_DELETED',
|
||||
index: getWebhookIndex(webhookToDelete),
|
||||
if (webhooksError) {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: formatAPIError(webhooksError),
|
||||
});
|
||||
} catch (err) {
|
||||
/**
|
||||
* TODO: especially this.
|
||||
*/
|
||||
if (err.code !== 20) {
|
||||
|
||||
return;
|
||||
}
|
||||
if (webhooks) {
|
||||
notifyStatus(
|
||||
formatMessage({
|
||||
id: 'Settings.webhooks.list.loading.success',
|
||||
defaultMessage: 'Webhooks have been loaded',
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [webhooks, webhooksError, toggleNotification, formatMessage, notifyStatus, formatAPIError]);
|
||||
|
||||
const deleteMutation = useMutation(
|
||||
async () => {
|
||||
await post('/admin/webhooks/batch-delete', {
|
||||
ids: webhooksToDelete,
|
||||
});
|
||||
},
|
||||
{
|
||||
onError(error) {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
message: formatAPIError(error),
|
||||
});
|
||||
}
|
||||
setShowModal(false);
|
||||
},
|
||||
onSuccess() {
|
||||
setWebhooksToDelete([]);
|
||||
setShowModal(false);
|
||||
refetchWebhooks();
|
||||
},
|
||||
}
|
||||
setShowModal(false);
|
||||
};
|
||||
);
|
||||
|
||||
const handleConfirmDeleteAll = async () => {
|
||||
const body = {
|
||||
ids: webhooksToDelete,
|
||||
};
|
||||
|
||||
try {
|
||||
await post('/admin/webhooks/batch-delete', body);
|
||||
|
||||
if (isMounted.current) {
|
||||
dispatch({
|
||||
type: 'WEBHOOKS_DELETED',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if (isMounted.current) {
|
||||
if (err.code !== 20) {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (id) => {
|
||||
setShowModal(true);
|
||||
|
||||
if (id !== 'all') {
|
||||
dispatch({
|
||||
type: 'SET_WEBHOOK_TO_DELETE',
|
||||
id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnabledChange = async (value, id) => {
|
||||
const webhookIndex = getWebhookIndex(id);
|
||||
const initialWebhookProps = webhooks[webhookIndex];
|
||||
const keys = [webhookIndex, 'isEnabled'];
|
||||
|
||||
const body = {
|
||||
...initialWebhookProps,
|
||||
isEnabled: value,
|
||||
};
|
||||
|
||||
delete body.id;
|
||||
|
||||
try {
|
||||
dispatch({
|
||||
type: 'SET_WEBHOOK_ENABLED',
|
||||
keys,
|
||||
value,
|
||||
});
|
||||
const enabledMutation = useMutation(
|
||||
async ({ isEnabled, id }) => {
|
||||
const { id: _, ...webhook } = webhooks.find((webhook) => webhook.id === id) ?? {};
|
||||
const body = {
|
||||
...webhook,
|
||||
isEnabled,
|
||||
};
|
||||
|
||||
await put(`/admin/webhooks/${id}`, body);
|
||||
} catch (err) {
|
||||
if (isMounted.current) {
|
||||
dispatch({
|
||||
type: 'SET_WEBHOOK_ENABLED',
|
||||
keys,
|
||||
value: !value,
|
||||
},
|
||||
{
|
||||
onError(error) {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: formatAPIError(error),
|
||||
});
|
||||
|
||||
if (err.code !== 20) {
|
||||
toggleNotification({
|
||||
type: 'warning',
|
||||
message: { id: 'notification.error' },
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
onSuccess() {
|
||||
refetchWebhooks();
|
||||
},
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
const handleSelectAllCheckbox = () => {
|
||||
dispatch({
|
||||
type: 'SET_ALL_WEBHOOKS_TO_DELETE',
|
||||
});
|
||||
};
|
||||
const confirmDelete = () => deleteMutation.mutate();
|
||||
|
||||
const handleSelectOneCheckbox = (value, id) => {
|
||||
dispatch({
|
||||
type: 'SET_WEBHOOKS_TO_DELETE',
|
||||
value,
|
||||
id,
|
||||
});
|
||||
};
|
||||
const selectAllCheckbox = (selected) =>
|
||||
selected ? setWebhooksToDelete(webhooks.map((webhook) => webhook.id)) : setWebhooksToDelete([]);
|
||||
|
||||
const handleGoTo = (to) => {
|
||||
push(`${pathname}/${to}`);
|
||||
};
|
||||
const selectOneCheckbox = (selected, id) =>
|
||||
selected
|
||||
? setWebhooksToDelete((prev) => [...prev, id])
|
||||
: setWebhooksToDelete((prev) => prev.filter((webhookId) => webhookId !== id));
|
||||
|
||||
const goTo = (to) => push(`${pathname}/${to}`);
|
||||
|
||||
const isLoading = isRBACLoading || isWebhooksLoading;
|
||||
const numberOfWebhooks = webhooks?.length ?? 0;
|
||||
const webhooksToDeleteLength = webhooksToDelete.length;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<SettingsPageTitle name="Webhooks" />
|
||||
<Main aria-busy={isLoading || loadingWebhooks}>
|
||||
<Main aria-busy={isLoading}>
|
||||
<HeaderLayout
|
||||
title={formatMessage({ id: 'Settings.webhooks.title', defaultMessage: 'Webhooks' })}
|
||||
subtitle={formatMessage({
|
||||
@ -259,7 +168,7 @@ const ListView = () => {
|
||||
})}
|
||||
primaryAction={
|
||||
canCreate &&
|
||||
!loadingWebhooks && (
|
||||
!isLoading && (
|
||||
<LinkButton startIcon={<Plus />} variant="default" to={`${pathname}/create`} size="S">
|
||||
{formatMessage({
|
||||
id: 'Settings.webhooks.list.button.add',
|
||||
@ -278,13 +187,13 @@ const ListView = () => {
|
||||
{
|
||||
id: 'Settings.webhooks.to.delete',
|
||||
defaultMessage:
|
||||
'{webhooksToDeleteLength, plural, one {# asset} other {# assets}} selected',
|
||||
'{webhooksToDeleteLength, plural, one {# webhook} other {# webhooks}} selected',
|
||||
},
|
||||
{ webhooksToDeleteLength }
|
||||
)}
|
||||
</Typography>
|
||||
<Button
|
||||
onClick={() => handleDeleteClick('all')}
|
||||
onClick={() => setShowModal(true)}
|
||||
startIcon={<Trash />}
|
||||
size="L"
|
||||
variant="danger-light"
|
||||
@ -299,16 +208,16 @@ const ListView = () => {
|
||||
/>
|
||||
)}
|
||||
<ContentLayout>
|
||||
{isLoading || loadingWebhooks ? (
|
||||
{isLoading ? (
|
||||
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
|
||||
<LoadingIndicatorPage />
|
||||
</Box>
|
||||
) : rowsCount > 0 ? (
|
||||
) : numberOfWebhooks > 0 ? (
|
||||
<Table
|
||||
colCount={5}
|
||||
rowCount={rowsCount + 1}
|
||||
rowCount={numberOfWebhooks + 1}
|
||||
footer={
|
||||
<TFooter onClick={() => (canCreate ? handleGoTo('create') : {})} icon={<Plus />}>
|
||||
<TFooter onClick={() => (canCreate ? goTo('create') : {})} icon={<Plus />}>
|
||||
{formatMessage({
|
||||
id: 'Settings.webhooks.list.button.add',
|
||||
defaultMessage: 'Create new webhook',
|
||||
@ -325,10 +234,10 @@ const ListView = () => {
|
||||
defaultMessage: 'Select all entries',
|
||||
})}
|
||||
indeterminate={
|
||||
webhooksToDeleteLength > 0 && webhooksToDeleteLength < rowsCount
|
||||
webhooksToDeleteLength > 0 && webhooksToDeleteLength < numberOfWebhooks
|
||||
}
|
||||
value={webhooksToDeleteLength === rowsCount}
|
||||
onValueChange={handleSelectAllCheckbox}
|
||||
value={webhooksToDeleteLength === numberOfWebhooks}
|
||||
onValueChange={selectAllCheckbox}
|
||||
/>
|
||||
</Th>
|
||||
<Th width="20%">
|
||||
@ -370,7 +279,7 @@ const ListView = () => {
|
||||
<Tr
|
||||
key={webhook.id}
|
||||
{...onRowClick({
|
||||
fn: () => handleGoTo(webhook.id),
|
||||
fn: () => goTo(webhook.id),
|
||||
condition: canUpdate,
|
||||
})}
|
||||
>
|
||||
@ -381,8 +290,7 @@ const ListView = () => {
|
||||
defaultMessage: 'Select',
|
||||
})} ${webhook.name}`}
|
||||
value={webhooksToDelete?.includes(webhook.id)}
|
||||
onValueChange={(value) => handleSelectOneCheckbox(value, webhook.id)}
|
||||
id="select"
|
||||
onValueChange={(selected) => selectOneCheckbox(selected, webhook.id)}
|
||||
name="select"
|
||||
/>
|
||||
</Td>
|
||||
@ -395,7 +303,7 @@ const ListView = () => {
|
||||
<Typography textColor="neutral800">{webhook.url}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex {...stopPropagation}>
|
||||
<Flex>
|
||||
<Switch
|
||||
onLabel={formatMessage({
|
||||
id: 'global.enabled',
|
||||
@ -410,18 +318,21 @@ const ListView = () => {
|
||||
defaultMessage: 'Status',
|
||||
})}`}
|
||||
selected={webhook.isEnabled}
|
||||
onChange={() => handleEnabledChange(!webhook.isEnabled, webhook.id)}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
enabledMutation.mutate({
|
||||
isEnabled: !webhook.isEnabled,
|
||||
id: webhook.id,
|
||||
});
|
||||
}}
|
||||
visibleLabels
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>
|
||||
<Flex gap={1} {...stopPropagation}>
|
||||
<Flex gap={1}>
|
||||
{canUpdate && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handleGoTo(webhook.id);
|
||||
}}
|
||||
label={formatMessage({
|
||||
id: 'Settings.webhooks.events.update',
|
||||
defaultMessage: 'Update',
|
||||
@ -432,14 +343,17 @@ const ListView = () => {
|
||||
)}
|
||||
{canDelete && (
|
||||
<IconButton
|
||||
onClick={() => handleDeleteClick(webhook.id)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setWebhooksToDelete([webhook.id]);
|
||||
setShowModal(true);
|
||||
}}
|
||||
label={formatMessage({
|
||||
id: 'global.delete',
|
||||
defaultMessage: 'Delete',
|
||||
id: 'Settings.webhooks.events.delete',
|
||||
defaultMessage: 'Delete webhook',
|
||||
})}
|
||||
icon={<Trash />}
|
||||
noBorder
|
||||
id={`delete-${webhook.id}`}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
@ -459,7 +373,7 @@ const ListView = () => {
|
||||
<Button
|
||||
variant="secondary"
|
||||
startIcon={<Plus />}
|
||||
onClick={() => (canCreate ? handleGoTo('create') : {})}
|
||||
onClick={() => (canCreate ? goTo('create') : {})}
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'Settings.webhooks.list.button.add',
|
||||
@ -473,8 +387,9 @@ const ListView = () => {
|
||||
</Main>
|
||||
<ConfirmDialog
|
||||
isOpen={showModal}
|
||||
onToggleDialog={handleToggleModal}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onToggleDialog={() => setShowModal((prev) => !prev)}
|
||||
onConfirm={confirmDelete}
|
||||
isConfirmButtonLoading={deleteMutation.isLoading}
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@ -1,72 +0,0 @@
|
||||
import produce from 'immer';
|
||||
import set from 'lodash/set';
|
||||
|
||||
const initialState = {
|
||||
webhooks: [],
|
||||
webhooksToDelete: [],
|
||||
webhookToDelete: null,
|
||||
loadingWebhooks: true,
|
||||
};
|
||||
|
||||
const reducer = (state, action) =>
|
||||
// eslint-disable-next-line consistent-return
|
||||
produce(state, (draftState) => {
|
||||
switch (action.type) {
|
||||
case 'GET_DATA_SUCCEEDED': {
|
||||
draftState.webhooks = action.data;
|
||||
draftState.loadingWebhooks = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'TOGGLE_LOADING': {
|
||||
draftState.loadingWebhooks = !state.loadingWebhooks;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'SET_WEBHOOK_ENABLED': {
|
||||
set(draftState, ['webhooks', ...action.keys], action.value);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'SET_WEBHOOK_TO_DELETE': {
|
||||
draftState.webhookToDelete = action.id;
|
||||
break;
|
||||
}
|
||||
case 'SET_WEBHOOKS_TO_DELETE': {
|
||||
if (action.value) {
|
||||
draftState.webhooksToDelete.push(action.id);
|
||||
} else {
|
||||
draftState.webhooksToDelete = state.webhooksToDelete.filter((id) => id !== action.id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'SET_ALL_WEBHOOKS_TO_DELETE': {
|
||||
if (state.webhooksToDelete.length === 0) {
|
||||
draftState.webhooksToDelete = state.webhooks.map((webhook) => webhook.id);
|
||||
} else {
|
||||
draftState.webhooksToDelete = [];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'WEBHOOKS_DELETED': {
|
||||
draftState.webhooks = state.webhooks.filter(
|
||||
(webhook) => !state.webhooksToDelete.includes(webhook.id)
|
||||
);
|
||||
draftState.webhooksToDelete = [];
|
||||
break;
|
||||
}
|
||||
case 'WEBHOOK_DELETED': {
|
||||
draftState.webhooks = state.webhooks.filter((_, index) => index !== action.index);
|
||||
draftState.webhookToDelete = null;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return draftState;
|
||||
}
|
||||
});
|
||||
|
||||
export default reducer;
|
||||
export { initialState };
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,370 +0,0 @@
|
||||
import reducer from '../reducer';
|
||||
|
||||
describe('Admin | containers | Webhooks | ListView | reducer', () => {
|
||||
const initialState = {
|
||||
webhooks: [],
|
||||
webhooksToDelete: [],
|
||||
webhookToDelete: null,
|
||||
loadingWebhooks: true,
|
||||
};
|
||||
|
||||
describe('Load webhooks', () => {
|
||||
it('should update webhooks with received data', () => {
|
||||
const state = initialState;
|
||||
const receivedData = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
const action = {
|
||||
type: 'GET_DATA_SUCCEEDED',
|
||||
data: receivedData,
|
||||
};
|
||||
|
||||
const expectedState = { ...state, webhooks: receivedData, loadingWebhooks: false };
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update webhook', () => {
|
||||
it('should toggle isEnabled parameter', () => {
|
||||
const webhooks = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
const state = { ...initialState, webhooks };
|
||||
|
||||
const action = {
|
||||
type: 'SET_WEBHOOK_ENABLED',
|
||||
keys: [1, 'isEnabled'],
|
||||
value: true,
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
...state,
|
||||
webhooks: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete webhooks', () => {
|
||||
it('should set a webhook id to webhookToDelete', () => {
|
||||
const webhooks = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
const state = { ...initialState, webhooks };
|
||||
const action = {
|
||||
type: 'SET_WEBHOOK_TO_DELETE',
|
||||
id: 1,
|
||||
};
|
||||
|
||||
const expectedState = { ...state, webhookToDelete: 1 };
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should add a webhook id to webhooksToDelete if value is true', () => {
|
||||
const webhooks = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
const state = { ...initialState, webhooks };
|
||||
const action = {
|
||||
type: 'SET_WEBHOOKS_TO_DELETE',
|
||||
id: 1,
|
||||
value: true,
|
||||
};
|
||||
|
||||
const expectedState = { ...state, webhooksToDelete: [1] };
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should remove a webhook id to webhooksToDelete if value is false', () => {
|
||||
const webhooks = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
const state = { ...initialState, webhooks, webhooksToDelete: [1, 2] };
|
||||
|
||||
const action = {
|
||||
type: 'SET_WEBHOOKS_TO_DELETE',
|
||||
id: 1,
|
||||
value: false,
|
||||
};
|
||||
|
||||
const expectedState = { ...state, webhooksToDelete: [2] };
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should update webhooks and clear webhooksToDelete', () => {
|
||||
const webhooks = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
const updatedWebhooks = [
|
||||
{
|
||||
id: 2,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
const state = { ...initialState, webhooksToDelete: [1], webhooks };
|
||||
const action = {
|
||||
type: 'WEBHOOKS_DELETED',
|
||||
};
|
||||
|
||||
const expectedState = { ...state, webhooks: updatedWebhooks, webhooksToDelete: [] };
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should update webhooks and clear webhookToDelete', () => {
|
||||
const webhooks = [
|
||||
{
|
||||
id: 3,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
const updatedWebhooks = [
|
||||
{
|
||||
id: 3,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
const webhookIdToDelete = 4;
|
||||
|
||||
const state = { ...initialState, webhookToDelete: webhookIdToDelete, webhooks };
|
||||
|
||||
const action = {
|
||||
type: 'WEBHOOK_DELETED',
|
||||
index: 1,
|
||||
};
|
||||
|
||||
const expectedState = { ...state, webhooks: updatedWebhooks, webhookToDelete: null };
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should clear webhooksToDelete when webhooksToDelete length > 0', () => {
|
||||
const webhooks = [
|
||||
{
|
||||
id: 3,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
const state = { ...initialState, webhooks, webhooksToDelete: [3] };
|
||||
|
||||
const action = {
|
||||
type: 'SET_ALL_WEBHOOKS_TO_DELETE',
|
||||
};
|
||||
|
||||
const expectedState = { ...state, webhooks, webhooksToDelete: [] };
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should add all webhooks in webhooksToDelete when webhooksToDelete length === 0', () => {
|
||||
const webhooks = [
|
||||
{
|
||||
id: 3,
|
||||
name: 'webhook 1',
|
||||
url: 'http://localhost:5000',
|
||||
headers: {},
|
||||
events: ['entry.create', 'entry.update', 'entry.delete'],
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'webhook 2',
|
||||
url: 'http://localhost:4000',
|
||||
headers: {},
|
||||
events: ['media.create', 'media.update'],
|
||||
isEnabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
const state = { ...initialState, webhooks, webhooksToDelete: [] };
|
||||
|
||||
const action = {
|
||||
type: 'SET_ALL_WEBHOOKS_TO_DELETE',
|
||||
};
|
||||
|
||||
const expectedState = { ...state, webhooks, webhooksToDelete: [3, 4, 5] };
|
||||
|
||||
expect(reducer(state, action)).toEqual(expectedState);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,16 +1,48 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
|
||||
const initialWebhooks = [
|
||||
{ id: 1, isEnabled: true, name: 'test', url: 'http:://strapi.io' },
|
||||
{ id: 2, isEnabled: false, name: 'test2', url: 'http://me.io' },
|
||||
];
|
||||
|
||||
let webhooks = initialWebhooks;
|
||||
export const resetWebhooks = () => {
|
||||
webhooks = initialWebhooks;
|
||||
};
|
||||
|
||||
const handlers = [
|
||||
rest.get('*/webhooks', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.delay(100),
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: [
|
||||
{ id: 1, isEnabled: true, name: 'test', url: 'http:://strapi.io' },
|
||||
{ id: 2, isEnabled: false, name: 'test2', url: 'http://me.io' },
|
||||
],
|
||||
data: webhooks,
|
||||
})
|
||||
);
|
||||
}),
|
||||
rest.post('*/webhooks/batch-delete', (req, res, ctx) => {
|
||||
webhooks = webhooks.filter((webhook) => !req.body.ids.includes(webhook.id));
|
||||
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: webhooks,
|
||||
})
|
||||
);
|
||||
}),
|
||||
rest.put('*/webhooks/:id', (req, res, ctx) => {
|
||||
const { id } = req.params;
|
||||
const { isEnabled } = req.body;
|
||||
|
||||
webhooks = webhooks.map((webhook) =>
|
||||
webhook.id === Number(id) ? { ...webhook, isEnabled } : webhook
|
||||
);
|
||||
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: webhooks,
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
||||
@ -287,6 +287,7 @@
|
||||
"Settings.webhooks.event.publish-tooltip": "This event only exists for contents with Draft/Publish system enabled",
|
||||
"Settings.webhooks.events.create": "Create",
|
||||
"Settings.webhooks.events.update": "Update",
|
||||
"Settings.webhooks.events.delete": "Delete webhook",
|
||||
"Settings.webhooks.form.events": "Events",
|
||||
"Settings.webhooks.form.headers": "Headers",
|
||||
"Settings.webhooks.form.url": "URL",
|
||||
@ -299,9 +300,10 @@
|
||||
"Settings.webhooks.list.empty.title": "There are no webhooks yet",
|
||||
"Settings.webhooks.list.th.actions": "actions",
|
||||
"Settings.webhooks.list.th.status": "status",
|
||||
"Settings.webhooks.list.loading.success": "Webhooks have been loaded",
|
||||
"Settings.webhooks.singular": "webhook",
|
||||
"Settings.webhooks.title": "Webhooks",
|
||||
"Settings.webhooks.to.delete": "{webhooksToDeleteLength, plural, one {# asset} other {# assets}} selected",
|
||||
"Settings.webhooks.to.delete": "{webhooksToDeleteLength, plural, one {# webhook} other {# webhooks}} selected",
|
||||
"Settings.webhooks.trigger": "Trigger",
|
||||
"Settings.webhooks.trigger.cancel": "Cancel trigger",
|
||||
"Settings.webhooks.trigger.pending": "Pending…",
|
||||
|
||||
@ -9,7 +9,7 @@ const createContentTypeSchema = ({
|
||||
reservedModels = [],
|
||||
singularNames = [],
|
||||
pluralNames = [],
|
||||
collectionNames = []
|
||||
collectionNames = [],
|
||||
}) => {
|
||||
const shape = {
|
||||
displayName: yup
|
||||
|
||||
@ -157,7 +157,6 @@ const forms = {
|
||||
},
|
||||
},
|
||||
contentType: {
|
||||
|
||||
schema(alreadyTakenNames, isEditing, ctUid, reservedNames, extensions, contentTypes) {
|
||||
const singularNames = Object.values(contentTypes).map((contentType) => {
|
||||
return contentType.schema.singularName;
|
||||
@ -197,7 +196,7 @@ const forms = {
|
||||
reservedModels: reservedNames.models,
|
||||
singularNames: takenSingularNames,
|
||||
pluralNames: takenPluralNames,
|
||||
collectionNames
|
||||
collectionNames,
|
||||
});
|
||||
|
||||
// FIXME
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user