Merge pull request #16700 from strapi/webhooks/list-view

This commit is contained in:
Jamie Howard 2023-05-19 16:16:38 +01:00 committed by GitHub
commit ee413187df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 311 additions and 1980 deletions

View File

@ -1 +1 @@
export const MARKETPLACE_API_URL = 'https://market-api.strapi.io';
export const MARKETPLACE_API_URL = 'https://market-api.strapi.io';

View File

@ -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 {

View File

@ -1,4 +1,4 @@
import formatValue from '../utils/formatValue';
import { formatValue } from '..';
describe('utils | formatValue', () => {
it('should format array to object', () => {

View File

@ -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;

View File

@ -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>
);

View File

@ -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 };

View File

@ -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);
});
});
});

View File

@ -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,
})
);
}),

View File

@ -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…",

View File

@ -9,7 +9,7 @@ const createContentTypeSchema = ({
reservedModels = [],
singularNames = [],
pluralNames = [],
collectionNames = []
collectionNames = [],
}) => {
const shape = {
displayName: yup

View File

@ -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