@contentful/field-editor-reference 6.2.5 → 6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/common/EntityStore.js +84 -4
- package/dist/cjs/components/MissingEntityCard/MissingEntityCard.js +8 -9
- package/dist/cjs/components/ResourceEntityErrorCard/FunctionInvocationErrorCard.js +85 -0
- package/dist/cjs/components/ResourceEntityErrorCard/ResourceEntityErrorCard.js +11 -0
- package/dist/cjs/resources/Cards/ResourceCard.spec.js +26 -0
- package/dist/esm/common/EntityStore.js +75 -4
- package/dist/esm/components/MissingEntityCard/MissingEntityCard.js +8 -9
- package/dist/esm/components/ResourceEntityErrorCard/FunctionInvocationErrorCard.js +34 -0
- package/dist/esm/components/ResourceEntityErrorCard/ResourceEntityErrorCard.js +12 -1
- package/dist/esm/resources/Cards/ResourceCard.spec.js +26 -0
- package/dist/types/common/EntityStore.d.ts +24 -0
- package/dist/types/components/MissingEntityCard/MissingEntityCard.d.ts +5 -2
- package/dist/types/components/ResourceEntityErrorCard/FunctionInvocationErrorCard.d.ts +11 -0
- package/package.json +2 -2
|
@@ -12,12 +12,18 @@ _export(exports, {
|
|
|
12
12
|
EntityProvider: function() {
|
|
13
13
|
return EntityProvider;
|
|
14
14
|
},
|
|
15
|
+
FunctionInvocationError: function() {
|
|
16
|
+
return FunctionInvocationError;
|
|
17
|
+
},
|
|
15
18
|
UnsupportedError: function() {
|
|
16
19
|
return UnsupportedError;
|
|
17
20
|
},
|
|
18
21
|
isContentfulResourceInfo: function() {
|
|
19
22
|
return isContentfulResourceInfo;
|
|
20
23
|
},
|
|
24
|
+
isFunctionInvocationError: function() {
|
|
25
|
+
return isFunctionInvocationError;
|
|
26
|
+
},
|
|
21
27
|
isUnsupportedError: function() {
|
|
22
28
|
return isUnsupportedError;
|
|
23
29
|
},
|
|
@@ -29,6 +35,9 @@ _export(exports, {
|
|
|
29
35
|
},
|
|
30
36
|
useResource: function() {
|
|
31
37
|
return useResource;
|
|
38
|
+
},
|
|
39
|
+
useResourceProvider: function() {
|
|
40
|
+
return useResourceProvider;
|
|
32
41
|
}
|
|
33
42
|
});
|
|
34
43
|
const _react = _interop_require_wildcard(require("react"));
|
|
@@ -101,6 +110,13 @@ function isContentfulResourceInfo(info) {
|
|
|
101
110
|
const globalQueue = new _pqueue.default({
|
|
102
111
|
concurrency: 50
|
|
103
112
|
});
|
|
113
|
+
function isFunctionInvocationErrorResponse(response) {
|
|
114
|
+
const functionInvocationErrorMessages = [
|
|
115
|
+
'An error occurred while executing the Contentful Function code',
|
|
116
|
+
'Response payload of the Contentful Function is invalid'
|
|
117
|
+
];
|
|
118
|
+
return response !== null && typeof response === 'object' && 'message' in response && typeof response.message === 'string' && functionInvocationErrorMessages.includes(response.message);
|
|
119
|
+
}
|
|
104
120
|
class UnsupportedError extends Error {
|
|
105
121
|
constructor(message){
|
|
106
122
|
super(message);
|
|
@@ -111,6 +127,30 @@ class UnsupportedError extends Error {
|
|
|
111
127
|
function isUnsupportedError(value) {
|
|
112
128
|
return typeof value === 'object' && value?.isUnsupportedError === true;
|
|
113
129
|
}
|
|
130
|
+
class FunctionInvocationError extends Error {
|
|
131
|
+
constructor(message, organizationId, appDefinitionId){
|
|
132
|
+
super(message);
|
|
133
|
+
_define_property(this, "isFunctionInvocationError", void 0);
|
|
134
|
+
_define_property(this, "organizationId", void 0);
|
|
135
|
+
_define_property(this, "appDefinitionId", void 0);
|
|
136
|
+
this.isFunctionInvocationError = true;
|
|
137
|
+
this.organizationId = organizationId;
|
|
138
|
+
this.appDefinitionId = appDefinitionId;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function isFunctionInvocationError(value) {
|
|
142
|
+
return typeof value === 'object' && value?.isFunctionInvocationError === true;
|
|
143
|
+
}
|
|
144
|
+
function handleResourceFetchError(resourceFetchError, resourceTypeEntity) {
|
|
145
|
+
const parsedError = JSON.parse(resourceFetchError.message);
|
|
146
|
+
if (isFunctionInvocationErrorResponse(parsedError)) {
|
|
147
|
+
const organizationId = resourceTypeEntity.sys.organization?.sys.id;
|
|
148
|
+
const appDefinitionId = resourceTypeEntity.sys.appDefinition?.sys.id;
|
|
149
|
+
if (!organizationId || !appDefinitionId) throw new Error('Missing resource');
|
|
150
|
+
throw new FunctionInvocationError(resourceFetchError.message, organizationId, appDefinitionId);
|
|
151
|
+
}
|
|
152
|
+
throw resourceFetchError;
|
|
153
|
+
}
|
|
114
154
|
const isEntityQueryKey = (queryKey)=>{
|
|
115
155
|
return Array.isArray(queryKey) && (queryKey[0] === 'Entry' || queryKey[0] === 'Asset') && queryKey.length === 4;
|
|
116
156
|
};
|
|
@@ -178,6 +218,7 @@ async function fetchContentfulEntry({ urn, fetch, options }) {
|
|
|
178
218
|
};
|
|
179
219
|
}
|
|
180
220
|
async function fetchExternalResource({ urn, fetch, options, spaceId, environmentId, resourceType }) {
|
|
221
|
+
let resourceFetchError;
|
|
181
222
|
const [resource, resourceTypes] = await Promise.all([
|
|
182
223
|
fetch([
|
|
183
224
|
'resource',
|
|
@@ -192,7 +233,12 @@ async function fetchExternalResource({ urn, fetch, options, spaceId, environment
|
|
|
192
233
|
query: {
|
|
193
234
|
'sys.urn[in]': urn
|
|
194
235
|
}
|
|
195
|
-
}).then(({ items })=>
|
|
236
|
+
}).then(({ items })=>{
|
|
237
|
+
return items[0] ?? null;
|
|
238
|
+
}).catch((e)=>{
|
|
239
|
+
resourceFetchError = e;
|
|
240
|
+
return null;
|
|
241
|
+
}), options),
|
|
196
242
|
fetch([
|
|
197
243
|
'resource-types',
|
|
198
244
|
spaceId,
|
|
@@ -207,6 +253,9 @@ async function fetchExternalResource({ urn, fetch, options, spaceId, environment
|
|
|
207
253
|
if (!resourceTypeEntity) {
|
|
208
254
|
throw new UnsupportedError('Unsupported resource type');
|
|
209
255
|
}
|
|
256
|
+
if (resourceFetchError instanceof Error) {
|
|
257
|
+
handleResourceFetchError(resourceFetchError, resourceTypeEntity);
|
|
258
|
+
}
|
|
210
259
|
if (!resource) {
|
|
211
260
|
throw new Error('Missing resource');
|
|
212
261
|
}
|
|
@@ -410,18 +459,35 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = (0,
|
|
|
410
459
|
getEntity,
|
|
411
460
|
onSlideInNavigation
|
|
412
461
|
]);
|
|
462
|
+
const getResourceProvider = (0, _react.useCallback)(function getResourceProvider(organizationId, appDefinitionId) {
|
|
463
|
+
const queryKey = [
|
|
464
|
+
'ResourceProvider',
|
|
465
|
+
organizationId,
|
|
466
|
+
appDefinitionId
|
|
467
|
+
];
|
|
468
|
+
return fetch(queryKey, async ({ cmaClient })=>{
|
|
469
|
+
return cmaClient.resourceProvider.get({
|
|
470
|
+
organizationId,
|
|
471
|
+
appDefinitionId
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}, [
|
|
475
|
+
fetch
|
|
476
|
+
]);
|
|
413
477
|
return {
|
|
414
478
|
ids: props.sdk.ids,
|
|
415
479
|
cmaClient,
|
|
416
480
|
fetch,
|
|
417
481
|
getResource,
|
|
418
482
|
getEntity,
|
|
419
|
-
getEntityScheduledActions
|
|
483
|
+
getEntityScheduledActions,
|
|
484
|
+
getResourceProvider
|
|
420
485
|
};
|
|
421
|
-
}, ({ fetch })=>fetch, ({ getResource, getEntity, getEntityScheduledActions })=>({
|
|
486
|
+
}, ({ fetch })=>fetch, ({ getResource, getEntity, getEntityScheduledActions, getResourceProvider })=>({
|
|
422
487
|
getResource,
|
|
423
488
|
getEntity,
|
|
424
|
-
getEntityScheduledActions
|
|
489
|
+
getEntityScheduledActions,
|
|
490
|
+
getResourceProvider
|
|
425
491
|
}), ({ ids })=>({
|
|
426
492
|
environment: ids.environmentAlias ?? ids.environment,
|
|
427
493
|
space: ids.space
|
|
@@ -459,6 +525,20 @@ function useResource(resourceType, urn, options) {
|
|
|
459
525
|
error
|
|
460
526
|
};
|
|
461
527
|
}
|
|
528
|
+
function useResourceProvider(organizationId, appDefinitionId) {
|
|
529
|
+
const queryKey = [
|
|
530
|
+
'Resource',
|
|
531
|
+
organizationId,
|
|
532
|
+
appDefinitionId
|
|
533
|
+
];
|
|
534
|
+
const { getResourceProvider } = useEntityLoader();
|
|
535
|
+
const { status, data, error } = (0, _queryClient.useQuery)(queryKey, ()=>getResourceProvider(organizationId, appDefinitionId), {});
|
|
536
|
+
return {
|
|
537
|
+
status,
|
|
538
|
+
data,
|
|
539
|
+
error
|
|
540
|
+
};
|
|
541
|
+
}
|
|
462
542
|
function EntityProvider({ children, ...props }) {
|
|
463
543
|
return _react.default.createElement(_queryClient.SharedQueryClientProvider, null, _react.default.createElement(InternalServiceProvider, props, children));
|
|
464
544
|
}
|
|
@@ -16,11 +16,10 @@ function _interop_require_default(obj) {
|
|
|
16
16
|
default: obj
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
function MissingEntityCard(
|
|
20
|
-
const
|
|
21
|
-
const description = props.customMessage ?? 'Content missing or inaccessible';
|
|
19
|
+
function MissingEntityCard({ as = 'a', providerName = 'Source', customMessage, isDisabled, isSelected, onRemove, testId = 'cf-ui-missing-entity-card', children }) {
|
|
20
|
+
const description = customMessage ?? 'Content missing or inaccessible';
|
|
22
21
|
function CustomActionButton() {
|
|
23
|
-
if (
|
|
22
|
+
if (isDisabled || !onRemove) return null;
|
|
24
23
|
return _react.default.createElement(_f36components.IconButton, {
|
|
25
24
|
"aria-label": "Actions",
|
|
26
25
|
icon: _react.default.createElement(_f36icons.CloseIcon, {
|
|
@@ -29,16 +28,16 @@ function MissingEntityCard(props) {
|
|
|
29
28
|
size: "small",
|
|
30
29
|
variant: "transparent",
|
|
31
30
|
onClick: ()=>{
|
|
32
|
-
|
|
31
|
+
onRemove && onRemove();
|
|
33
32
|
}
|
|
34
33
|
});
|
|
35
34
|
}
|
|
36
35
|
return _react.default.createElement(_f36components.EntryCard, {
|
|
37
|
-
as:
|
|
36
|
+
as: as,
|
|
38
37
|
contentType: providerName,
|
|
39
38
|
description: description,
|
|
40
|
-
isSelected:
|
|
39
|
+
isSelected: isSelected,
|
|
41
40
|
customActionButton: _react.default.createElement(CustomActionButton, null),
|
|
42
|
-
testId:
|
|
43
|
-
});
|
|
41
|
+
testId: testId
|
|
42
|
+
}, children);
|
|
44
43
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "FunctionInvocationErrorCard", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return FunctionInvocationErrorCard;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _react = _interop_require_wildcard(require("react"));
|
|
12
|
+
const _f36components = require("@contentful/f36-components");
|
|
13
|
+
const _ = require("..");
|
|
14
|
+
const _f36icons = require("@contentful/f36-icons");
|
|
15
|
+
const _EntityStore = require("../../common/EntityStore");
|
|
16
|
+
function _getRequireWildcardCache(nodeInterop) {
|
|
17
|
+
if (typeof WeakMap !== "function") return null;
|
|
18
|
+
var cacheBabelInterop = new WeakMap();
|
|
19
|
+
var cacheNodeInterop = new WeakMap();
|
|
20
|
+
return (_getRequireWildcardCache = function(nodeInterop) {
|
|
21
|
+
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
|
|
22
|
+
})(nodeInterop);
|
|
23
|
+
}
|
|
24
|
+
function _interop_require_wildcard(obj, nodeInterop) {
|
|
25
|
+
if (!nodeInterop && obj && obj.__esModule) {
|
|
26
|
+
return obj;
|
|
27
|
+
}
|
|
28
|
+
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
|
|
29
|
+
return {
|
|
30
|
+
default: obj
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
var cache = _getRequireWildcardCache(nodeInterop);
|
|
34
|
+
if (cache && cache.has(obj)) {
|
|
35
|
+
return cache.get(obj);
|
|
36
|
+
}
|
|
37
|
+
var newObj = {
|
|
38
|
+
__proto__: null
|
|
39
|
+
};
|
|
40
|
+
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
|
|
41
|
+
for(var key in obj){
|
|
42
|
+
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
43
|
+
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
|
|
44
|
+
if (desc && (desc.get || desc.set)) {
|
|
45
|
+
Object.defineProperty(newObj, key, desc);
|
|
46
|
+
} else {
|
|
47
|
+
newObj[key] = obj[key];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
newObj.default = obj;
|
|
52
|
+
if (cache) {
|
|
53
|
+
cache.set(obj, newObj);
|
|
54
|
+
}
|
|
55
|
+
return newObj;
|
|
56
|
+
}
|
|
57
|
+
function FunctionInvocationErrorCard({ providerName = 'Source', organizationId, appDefinitionId, isDisabled, isSelected, onRemove }) {
|
|
58
|
+
const { status, data } = (0, _EntityStore.useResourceProvider)(organizationId, appDefinitionId);
|
|
59
|
+
const functionId = data?.function.sys.id;
|
|
60
|
+
const functionLink = `/account/organizations/${organizationId}/apps/definitions/${appDefinitionId}/functions/${functionId}/logs`;
|
|
61
|
+
return _react.createElement(_.MissingEntityCard, {
|
|
62
|
+
as: "div",
|
|
63
|
+
providerName: providerName,
|
|
64
|
+
isDisabled: isDisabled,
|
|
65
|
+
isSelected: isSelected,
|
|
66
|
+
onRemove: onRemove,
|
|
67
|
+
customMessage: '',
|
|
68
|
+
testId: "cf-ui-function-invocation-error-card"
|
|
69
|
+
}, _react.createElement(_f36components.Flex, {
|
|
70
|
+
justifyContent: "left",
|
|
71
|
+
alignItems: "center"
|
|
72
|
+
}, _react.createElement(_f36icons.ErrorCircleOutlineIcon, {
|
|
73
|
+
variant: "negative"
|
|
74
|
+
}), _react.createElement(_f36components.Text, {
|
|
75
|
+
fontColor: "colorNegative"
|
|
76
|
+
}, " Function invocation error."), status === 'success' && functionId && _react.createElement(_f36components.Text, {
|
|
77
|
+
fontColor: "colorNegative"
|
|
78
|
+
}, " For more information, go to ", _react.createElement(_f36components.TextLink, {
|
|
79
|
+
testId: "cf-ui-function-invocation-log-link",
|
|
80
|
+
icon: _react.createElement(_f36icons.ExternalLinkIcon, null),
|
|
81
|
+
target: "_blank",
|
|
82
|
+
alignIcon: "end",
|
|
83
|
+
href: functionLink
|
|
84
|
+
}, "function logs"))));
|
|
85
|
+
}
|
|
@@ -12,6 +12,7 @@ const _react = _interop_require_wildcard(require("react"));
|
|
|
12
12
|
const _EntityStore = require("../../common/EntityStore");
|
|
13
13
|
const _getProviderName = require("../../utils/getProviderName");
|
|
14
14
|
const _MissingEntityCard = require("../MissingEntityCard/MissingEntityCard");
|
|
15
|
+
const _FunctionInvocationErrorCard = require("./FunctionInvocationErrorCard");
|
|
15
16
|
const _UnsupportedEntityCard = require("./UnsupportedEntityCard");
|
|
16
17
|
function _getRequireWildcardCache(nodeInterop) {
|
|
17
18
|
if (typeof WeakMap !== "function") return null;
|
|
@@ -62,6 +63,16 @@ function ResourceEntityErrorCard(props) {
|
|
|
62
63
|
});
|
|
63
64
|
}
|
|
64
65
|
const providerName = (0, _getProviderName.getProviderName)(props.linkType);
|
|
66
|
+
if ((0, _EntityStore.isFunctionInvocationError)(props.error)) {
|
|
67
|
+
return _react.createElement(_FunctionInvocationErrorCard.FunctionInvocationErrorCard, {
|
|
68
|
+
isSelected: props.isSelected,
|
|
69
|
+
isDisabled: props.isDisabled,
|
|
70
|
+
organizationId: props.error.organizationId,
|
|
71
|
+
appDefinitionId: props.error.appDefinitionId,
|
|
72
|
+
onRemove: props.onRemove,
|
|
73
|
+
providerName: providerName
|
|
74
|
+
});
|
|
75
|
+
}
|
|
65
76
|
return _react.createElement(_MissingEntityCard.MissingEntityCard, {
|
|
66
77
|
isDisabled: props.isDisabled,
|
|
67
78
|
isSelected: props.isSelected,
|
|
@@ -72,6 +72,7 @@ const resolvableEntryUrnWithAnotherEnvironment = 'crn:contentful:::content:space
|
|
|
72
72
|
const unknownEntryUrn = 'crn:contentful:::content:spaces/space-id/entries/unknown-entry-urn';
|
|
73
73
|
const resolvableExternalResourceType = 'External:ResourceType';
|
|
74
74
|
const resolvableExternalEntityUrn = 'external:entity-urn';
|
|
75
|
+
const unresolvableExternalEntityUrn = 'external:unresolvable-entity-urn';
|
|
75
76
|
const sdk = {
|
|
76
77
|
locales: {
|
|
77
78
|
default: 'en-US'
|
|
@@ -110,6 +111,12 @@ const sdk = {
|
|
|
110
111
|
]
|
|
111
112
|
});
|
|
112
113
|
}
|
|
114
|
+
if (query['sys.urn[in]'] === unresolvableExternalEntityUrn) {
|
|
115
|
+
const badRequestErrorBody = {
|
|
116
|
+
message: 'An error occurred while executing the Contentful Function code'
|
|
117
|
+
};
|
|
118
|
+
return Promise.reject(new Error(JSON.stringify(badRequestErrorBody)));
|
|
119
|
+
}
|
|
113
120
|
return Promise.resolve({
|
|
114
121
|
items: []
|
|
115
122
|
});
|
|
@@ -138,6 +145,17 @@ const sdk = {
|
|
|
138
145
|
},
|
|
139
146
|
Space: {
|
|
140
147
|
get: jest.fn().mockResolvedValue(_indifferent_spacejson.default)
|
|
148
|
+
},
|
|
149
|
+
ResourceProvider: {
|
|
150
|
+
get: jest.fn().mockImplementation(()=>{
|
|
151
|
+
return Promise.resolve({
|
|
152
|
+
function: {
|
|
153
|
+
sys: {
|
|
154
|
+
id: 'function-id'
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
})
|
|
141
159
|
}
|
|
142
160
|
}),
|
|
143
161
|
space: {
|
|
@@ -236,4 +254,12 @@ describe('ResourceCard', ()=>{
|
|
|
236
254
|
expect(getByText(_resourcejson.default.fields.title)).toBeDefined();
|
|
237
255
|
expect(getByText(`${_resourcetypejson.default.sys.resourceProvider.sys.id} ${_resourcetypejson.default.name}`)).toBeDefined();
|
|
238
256
|
});
|
|
257
|
+
it('renders function invocation error card when an external resource request fails', async ()=>{
|
|
258
|
+
const { getByTestId } = renderResourceCard({
|
|
259
|
+
linkType: resolvableExternalResourceType,
|
|
260
|
+
entityUrn: unresolvableExternalEntityUrn
|
|
261
|
+
});
|
|
262
|
+
await (0, _react1.waitFor)(()=>expect(getByTestId('cf-ui-function-invocation-error-card')).toBeDefined());
|
|
263
|
+
await (0, _react1.waitFor)(()=>expect(getByTestId('cf-ui-function-invocation-log-link')).toBeDefined());
|
|
264
|
+
});
|
|
239
265
|
});
|
|
@@ -22,6 +22,13 @@ export function isContentfulResourceInfo(info) {
|
|
|
22
22
|
const globalQueue = new PQueue({
|
|
23
23
|
concurrency: 50
|
|
24
24
|
});
|
|
25
|
+
function isFunctionInvocationErrorResponse(response) {
|
|
26
|
+
const functionInvocationErrorMessages = [
|
|
27
|
+
'An error occurred while executing the Contentful Function code',
|
|
28
|
+
'Response payload of the Contentful Function is invalid'
|
|
29
|
+
];
|
|
30
|
+
return response !== null && typeof response === 'object' && 'message' in response && typeof response.message === 'string' && functionInvocationErrorMessages.includes(response.message);
|
|
31
|
+
}
|
|
25
32
|
export class UnsupportedError extends Error {
|
|
26
33
|
constructor(message){
|
|
27
34
|
super(message);
|
|
@@ -32,6 +39,30 @@ export class UnsupportedError extends Error {
|
|
|
32
39
|
export function isUnsupportedError(value) {
|
|
33
40
|
return typeof value === 'object' && value?.isUnsupportedError === true;
|
|
34
41
|
}
|
|
42
|
+
export class FunctionInvocationError extends Error {
|
|
43
|
+
constructor(message, organizationId, appDefinitionId){
|
|
44
|
+
super(message);
|
|
45
|
+
_define_property(this, "isFunctionInvocationError", void 0);
|
|
46
|
+
_define_property(this, "organizationId", void 0);
|
|
47
|
+
_define_property(this, "appDefinitionId", void 0);
|
|
48
|
+
this.isFunctionInvocationError = true;
|
|
49
|
+
this.organizationId = organizationId;
|
|
50
|
+
this.appDefinitionId = appDefinitionId;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function isFunctionInvocationError(value) {
|
|
54
|
+
return typeof value === 'object' && value?.isFunctionInvocationError === true;
|
|
55
|
+
}
|
|
56
|
+
function handleResourceFetchError(resourceFetchError, resourceTypeEntity) {
|
|
57
|
+
const parsedError = JSON.parse(resourceFetchError.message);
|
|
58
|
+
if (isFunctionInvocationErrorResponse(parsedError)) {
|
|
59
|
+
const organizationId = resourceTypeEntity.sys.organization?.sys.id;
|
|
60
|
+
const appDefinitionId = resourceTypeEntity.sys.appDefinition?.sys.id;
|
|
61
|
+
if (!organizationId || !appDefinitionId) throw new Error('Missing resource');
|
|
62
|
+
throw new FunctionInvocationError(resourceFetchError.message, organizationId, appDefinitionId);
|
|
63
|
+
}
|
|
64
|
+
throw resourceFetchError;
|
|
65
|
+
}
|
|
35
66
|
const isEntityQueryKey = (queryKey)=>{
|
|
36
67
|
return Array.isArray(queryKey) && (queryKey[0] === 'Entry' || queryKey[0] === 'Asset') && queryKey.length === 4;
|
|
37
68
|
};
|
|
@@ -99,6 +130,7 @@ async function fetchContentfulEntry({ urn, fetch, options }) {
|
|
|
99
130
|
};
|
|
100
131
|
}
|
|
101
132
|
async function fetchExternalResource({ urn, fetch, options, spaceId, environmentId, resourceType }) {
|
|
133
|
+
let resourceFetchError;
|
|
102
134
|
const [resource, resourceTypes] = await Promise.all([
|
|
103
135
|
fetch([
|
|
104
136
|
'resource',
|
|
@@ -113,7 +145,12 @@ async function fetchExternalResource({ urn, fetch, options, spaceId, environment
|
|
|
113
145
|
query: {
|
|
114
146
|
'sys.urn[in]': urn
|
|
115
147
|
}
|
|
116
|
-
}).then(({ items })=>
|
|
148
|
+
}).then(({ items })=>{
|
|
149
|
+
return items[0] ?? null;
|
|
150
|
+
}).catch((e)=>{
|
|
151
|
+
resourceFetchError = e;
|
|
152
|
+
return null;
|
|
153
|
+
}), options),
|
|
117
154
|
fetch([
|
|
118
155
|
'resource-types',
|
|
119
156
|
spaceId,
|
|
@@ -128,6 +165,9 @@ async function fetchExternalResource({ urn, fetch, options, spaceId, environment
|
|
|
128
165
|
if (!resourceTypeEntity) {
|
|
129
166
|
throw new UnsupportedError('Unsupported resource type');
|
|
130
167
|
}
|
|
168
|
+
if (resourceFetchError instanceof Error) {
|
|
169
|
+
handleResourceFetchError(resourceFetchError, resourceTypeEntity);
|
|
170
|
+
}
|
|
131
171
|
if (!resource) {
|
|
132
172
|
throw new Error('Missing resource');
|
|
133
173
|
}
|
|
@@ -331,18 +371,35 @@ const [InternalServiceProvider, useFetch, useEntityLoader, useCurrentIds] = cons
|
|
|
331
371
|
getEntity,
|
|
332
372
|
onSlideInNavigation
|
|
333
373
|
]);
|
|
374
|
+
const getResourceProvider = useCallback(function getResourceProvider(organizationId, appDefinitionId) {
|
|
375
|
+
const queryKey = [
|
|
376
|
+
'ResourceProvider',
|
|
377
|
+
organizationId,
|
|
378
|
+
appDefinitionId
|
|
379
|
+
];
|
|
380
|
+
return fetch(queryKey, async ({ cmaClient })=>{
|
|
381
|
+
return cmaClient.resourceProvider.get({
|
|
382
|
+
organizationId,
|
|
383
|
+
appDefinitionId
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
}, [
|
|
387
|
+
fetch
|
|
388
|
+
]);
|
|
334
389
|
return {
|
|
335
390
|
ids: props.sdk.ids,
|
|
336
391
|
cmaClient,
|
|
337
392
|
fetch,
|
|
338
393
|
getResource,
|
|
339
394
|
getEntity,
|
|
340
|
-
getEntityScheduledActions
|
|
395
|
+
getEntityScheduledActions,
|
|
396
|
+
getResourceProvider
|
|
341
397
|
};
|
|
342
|
-
}, ({ fetch })=>fetch, ({ getResource, getEntity, getEntityScheduledActions })=>({
|
|
398
|
+
}, ({ fetch })=>fetch, ({ getResource, getEntity, getEntityScheduledActions, getResourceProvider })=>({
|
|
343
399
|
getResource,
|
|
344
400
|
getEntity,
|
|
345
|
-
getEntityScheduledActions
|
|
401
|
+
getEntityScheduledActions,
|
|
402
|
+
getResourceProvider
|
|
346
403
|
}), ({ ids })=>({
|
|
347
404
|
environment: ids.environmentAlias ?? ids.environment,
|
|
348
405
|
space: ids.space
|
|
@@ -380,6 +437,20 @@ export function useResource(resourceType, urn, options) {
|
|
|
380
437
|
error
|
|
381
438
|
};
|
|
382
439
|
}
|
|
440
|
+
export function useResourceProvider(organizationId, appDefinitionId) {
|
|
441
|
+
const queryKey = [
|
|
442
|
+
'Resource',
|
|
443
|
+
organizationId,
|
|
444
|
+
appDefinitionId
|
|
445
|
+
];
|
|
446
|
+
const { getResourceProvider } = useEntityLoader();
|
|
447
|
+
const { status, data, error } = useQuery(queryKey, ()=>getResourceProvider(organizationId, appDefinitionId), {});
|
|
448
|
+
return {
|
|
449
|
+
status,
|
|
450
|
+
data,
|
|
451
|
+
error
|
|
452
|
+
};
|
|
453
|
+
}
|
|
383
454
|
function EntityProvider({ children, ...props }) {
|
|
384
455
|
return React.createElement(SharedQueryClientProvider, null, React.createElement(InternalServiceProvider, props, children));
|
|
385
456
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { EntryCard, IconButton } from '@contentful/f36-components';
|
|
3
3
|
import { CloseIcon } from '@contentful/f36-icons';
|
|
4
|
-
export function MissingEntityCard(
|
|
5
|
-
const
|
|
6
|
-
const description = props.customMessage ?? 'Content missing or inaccessible';
|
|
4
|
+
export function MissingEntityCard({ as = 'a', providerName = 'Source', customMessage, isDisabled, isSelected, onRemove, testId = 'cf-ui-missing-entity-card', children }) {
|
|
5
|
+
const description = customMessage ?? 'Content missing or inaccessible';
|
|
7
6
|
function CustomActionButton() {
|
|
8
|
-
if (
|
|
7
|
+
if (isDisabled || !onRemove) return null;
|
|
9
8
|
return React.createElement(IconButton, {
|
|
10
9
|
"aria-label": "Actions",
|
|
11
10
|
icon: React.createElement(CloseIcon, {
|
|
@@ -14,16 +13,16 @@ export function MissingEntityCard(props) {
|
|
|
14
13
|
size: "small",
|
|
15
14
|
variant: "transparent",
|
|
16
15
|
onClick: ()=>{
|
|
17
|
-
|
|
16
|
+
onRemove && onRemove();
|
|
18
17
|
}
|
|
19
18
|
});
|
|
20
19
|
}
|
|
21
20
|
return React.createElement(EntryCard, {
|
|
22
|
-
as:
|
|
21
|
+
as: as,
|
|
23
22
|
contentType: providerName,
|
|
24
23
|
description: description,
|
|
25
|
-
isSelected:
|
|
24
|
+
isSelected: isSelected,
|
|
26
25
|
customActionButton: React.createElement(CustomActionButton, null),
|
|
27
|
-
testId:
|
|
28
|
-
});
|
|
26
|
+
testId: testId
|
|
27
|
+
}, children);
|
|
29
28
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Flex, Text, TextLink } from '@contentful/f36-components';
|
|
3
|
+
import { MissingEntityCard } from '..';
|
|
4
|
+
import { ErrorCircleOutlineIcon, ExternalLinkIcon } from '@contentful/f36-icons';
|
|
5
|
+
import { useResourceProvider } from '../../common/EntityStore';
|
|
6
|
+
export function FunctionInvocationErrorCard({ providerName = 'Source', organizationId, appDefinitionId, isDisabled, isSelected, onRemove }) {
|
|
7
|
+
const { status, data } = useResourceProvider(organizationId, appDefinitionId);
|
|
8
|
+
const functionId = data?.function.sys.id;
|
|
9
|
+
const functionLink = `/account/organizations/${organizationId}/apps/definitions/${appDefinitionId}/functions/${functionId}/logs`;
|
|
10
|
+
return React.createElement(MissingEntityCard, {
|
|
11
|
+
as: "div",
|
|
12
|
+
providerName: providerName,
|
|
13
|
+
isDisabled: isDisabled,
|
|
14
|
+
isSelected: isSelected,
|
|
15
|
+
onRemove: onRemove,
|
|
16
|
+
customMessage: '',
|
|
17
|
+
testId: "cf-ui-function-invocation-error-card"
|
|
18
|
+
}, React.createElement(Flex, {
|
|
19
|
+
justifyContent: "left",
|
|
20
|
+
alignItems: "center"
|
|
21
|
+
}, React.createElement(ErrorCircleOutlineIcon, {
|
|
22
|
+
variant: "negative"
|
|
23
|
+
}), React.createElement(Text, {
|
|
24
|
+
fontColor: "colorNegative"
|
|
25
|
+
}, " Function invocation error."), status === 'success' && functionId && React.createElement(Text, {
|
|
26
|
+
fontColor: "colorNegative"
|
|
27
|
+
}, " For more information, go to ", React.createElement(TextLink, {
|
|
28
|
+
testId: "cf-ui-function-invocation-log-link",
|
|
29
|
+
icon: React.createElement(ExternalLinkIcon, null),
|
|
30
|
+
target: "_blank",
|
|
31
|
+
alignIcon: "end",
|
|
32
|
+
href: functionLink
|
|
33
|
+
}, "function logs"))));
|
|
34
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { isUnsupportedError } from '../../common/EntityStore';
|
|
2
|
+
import { isFunctionInvocationError, isUnsupportedError } from '../../common/EntityStore';
|
|
3
3
|
import { getProviderName } from '../../utils/getProviderName';
|
|
4
4
|
import { MissingEntityCard } from '../MissingEntityCard/MissingEntityCard';
|
|
5
|
+
import { FunctionInvocationErrorCard } from './FunctionInvocationErrorCard';
|
|
5
6
|
import { UnsupportedEntityCard } from './UnsupportedEntityCard';
|
|
6
7
|
export function ResourceEntityErrorCard(props) {
|
|
7
8
|
if (isUnsupportedError(props.error)) {
|
|
@@ -11,6 +12,16 @@ export function ResourceEntityErrorCard(props) {
|
|
|
11
12
|
});
|
|
12
13
|
}
|
|
13
14
|
const providerName = getProviderName(props.linkType);
|
|
15
|
+
if (isFunctionInvocationError(props.error)) {
|
|
16
|
+
return React.createElement(FunctionInvocationErrorCard, {
|
|
17
|
+
isSelected: props.isSelected,
|
|
18
|
+
isDisabled: props.isDisabled,
|
|
19
|
+
organizationId: props.error.organizationId,
|
|
20
|
+
appDefinitionId: props.error.appDefinitionId,
|
|
21
|
+
onRemove: props.onRemove,
|
|
22
|
+
providerName: providerName
|
|
23
|
+
});
|
|
24
|
+
}
|
|
14
25
|
return React.createElement(MissingEntityCard, {
|
|
15
26
|
isDisabled: props.isDisabled,
|
|
16
27
|
isSelected: props.isSelected,
|
|
@@ -22,6 +22,7 @@ const resolvableEntryUrnWithAnotherEnvironment = 'crn:contentful:::content:space
|
|
|
22
22
|
const unknownEntryUrn = 'crn:contentful:::content:spaces/space-id/entries/unknown-entry-urn';
|
|
23
23
|
const resolvableExternalResourceType = 'External:ResourceType';
|
|
24
24
|
const resolvableExternalEntityUrn = 'external:entity-urn';
|
|
25
|
+
const unresolvableExternalEntityUrn = 'external:unresolvable-entity-urn';
|
|
25
26
|
const sdk = {
|
|
26
27
|
locales: {
|
|
27
28
|
default: 'en-US'
|
|
@@ -60,6 +61,12 @@ const sdk = {
|
|
|
60
61
|
]
|
|
61
62
|
});
|
|
62
63
|
}
|
|
64
|
+
if (query['sys.urn[in]'] === unresolvableExternalEntityUrn) {
|
|
65
|
+
const badRequestErrorBody = {
|
|
66
|
+
message: 'An error occurred while executing the Contentful Function code'
|
|
67
|
+
};
|
|
68
|
+
return Promise.reject(new Error(JSON.stringify(badRequestErrorBody)));
|
|
69
|
+
}
|
|
63
70
|
return Promise.resolve({
|
|
64
71
|
items: []
|
|
65
72
|
});
|
|
@@ -88,6 +95,17 @@ const sdk = {
|
|
|
88
95
|
},
|
|
89
96
|
Space: {
|
|
90
97
|
get: jest.fn().mockResolvedValue(space)
|
|
98
|
+
},
|
|
99
|
+
ResourceProvider: {
|
|
100
|
+
get: jest.fn().mockImplementation(()=>{
|
|
101
|
+
return Promise.resolve({
|
|
102
|
+
function: {
|
|
103
|
+
sys: {
|
|
104
|
+
id: 'function-id'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
})
|
|
91
109
|
}
|
|
92
110
|
}),
|
|
93
111
|
space: {
|
|
@@ -186,4 +204,12 @@ describe('ResourceCard', ()=>{
|
|
|
186
204
|
expect(getByText(resource.fields.title)).toBeDefined();
|
|
187
205
|
expect(getByText(`${resourceType.sys.resourceProvider.sys.id} ${resourceType.name}`)).toBeDefined();
|
|
188
206
|
});
|
|
207
|
+
it('renders function invocation error card when an external resource request fails', async ()=>{
|
|
208
|
+
const { getByTestId } = renderResourceCard({
|
|
209
|
+
linkType: resolvableExternalResourceType,
|
|
210
|
+
entityUrn: unresolvableExternalEntityUrn
|
|
211
|
+
});
|
|
212
|
+
await waitFor(()=>expect(getByTestId('cf-ui-function-invocation-error-card')).toBeDefined());
|
|
213
|
+
await waitFor(()=>expect(getByTestId('cf-ui-function-invocation-log-link')).toBeDefined());
|
|
214
|
+
});
|
|
189
215
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { BaseAppSDK } from '@contentful/app-sdk';
|
|
3
|
+
import { ResourceProvider } from 'contentful-management';
|
|
3
4
|
import { Asset, ContentType, Entry, ExternalResource, Resource, ResourceType, ScheduledAction, Space } from '../types';
|
|
4
5
|
export type ContentfulResourceInfo = {
|
|
5
6
|
resource: Entry;
|
|
@@ -50,15 +51,33 @@ type UseEntityResult<E> = {
|
|
|
50
51
|
};
|
|
51
52
|
type FetchableEntityType = 'Entry' | 'Asset';
|
|
52
53
|
type FetchableEntity = Entry | Asset;
|
|
54
|
+
export type FunctionInvocationErrorResponse = {
|
|
55
|
+
status: number;
|
|
56
|
+
statusText: string;
|
|
57
|
+
message: 'Response payload of the Contentful Function is invalid' | 'An error occurred while executing the Contentful Function code';
|
|
58
|
+
request: {
|
|
59
|
+
url: string;
|
|
60
|
+
headers: Record<string, string>;
|
|
61
|
+
method: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
53
64
|
export declare class UnsupportedError extends Error {
|
|
54
65
|
isUnsupportedError: boolean;
|
|
55
66
|
constructor(message: string);
|
|
56
67
|
}
|
|
57
68
|
export declare function isUnsupportedError(value: unknown): value is UnsupportedError;
|
|
69
|
+
export declare class FunctionInvocationError extends Error {
|
|
70
|
+
isFunctionInvocationError: boolean;
|
|
71
|
+
organizationId: string;
|
|
72
|
+
appDefinitionId: string;
|
|
73
|
+
constructor(message: string, organizationId: string, appDefinitionId: string);
|
|
74
|
+
}
|
|
75
|
+
export declare function isFunctionInvocationError(value: unknown): value is FunctionInvocationError;
|
|
58
76
|
declare const useEntityLoader: () => {
|
|
59
77
|
getResource: <R extends Resource = Resource>(resourceType: string, urn: string, options?: GetResourceOptions) => QueryResourceResult<R>;
|
|
60
78
|
getEntity: <E extends FetchableEntity>(entityType: FetchableEntityType, entityId: string, options?: GetEntityOptions) => QueryEntityResult<E>;
|
|
61
79
|
getEntityScheduledActions: (entityType: FetchableEntityType, entityId: string, options?: GetEntityOptions) => QueryEntityResult<ScheduledAction[]>;
|
|
80
|
+
getResourceProvider: (organizationId: string, appDefinitionId: string) => QueryEntityResult<ResourceProvider>;
|
|
62
81
|
};
|
|
63
82
|
export declare function useEntity<E extends FetchableEntity>(entityType: FetchableEntityType, entityId: string, options?: UseEntityOptions): UseEntityResult<E>;
|
|
64
83
|
export declare function useResource<R extends Resource = Resource>(resourceType: string, urn: string, options?: UseResourceOptions): {
|
|
@@ -66,5 +85,10 @@ export declare function useResource<R extends Resource = Resource>(resourceType:
|
|
|
66
85
|
data: ResourceInfo<R> | undefined;
|
|
67
86
|
error: unknown;
|
|
68
87
|
};
|
|
88
|
+
export declare function useResourceProvider(organizationId: string, appDefinitionId: string): {
|
|
89
|
+
status: "error" | "success" | "loading";
|
|
90
|
+
data: ResourceProvider | undefined;
|
|
91
|
+
error: unknown;
|
|
92
|
+
};
|
|
69
93
|
declare function EntityProvider({ children, ...props }: React.PropsWithChildren<EntityStoreProps>): React.JSX.Element;
|
|
70
94
|
export { EntityProvider, useEntityLoader };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
2
|
type MissingEntityCardProps = {
|
|
3
3
|
customMessage?: string;
|
|
4
4
|
isDisabled?: boolean;
|
|
5
5
|
isSelected?: boolean;
|
|
6
6
|
onRemove?: Function;
|
|
7
7
|
providerName?: string;
|
|
8
|
+
as?: 'a' | 'article' | 'button' | 'div' | 'fieldset';
|
|
9
|
+
testId?: string;
|
|
10
|
+
children?: ReactNode;
|
|
8
11
|
};
|
|
9
|
-
export declare function MissingEntityCard(
|
|
12
|
+
export declare function MissingEntityCard({ as, providerName, customMessage, isDisabled, isSelected, onRemove, testId, children, }: MissingEntityCardProps): React.JSX.Element;
|
|
10
13
|
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
type FunctionInvocationErrorCardProps = {
|
|
3
|
+
isSelected?: boolean;
|
|
4
|
+
isDisabled?: boolean;
|
|
5
|
+
organizationId: string;
|
|
6
|
+
appDefinitionId: string;
|
|
7
|
+
onRemove?: Function;
|
|
8
|
+
providerName?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function FunctionInvocationErrorCard({ providerName, organizationId, appDefinitionId, isDisabled, isSelected, onRemove, }: FunctionInvocationErrorCardProps): React.JSX.Element;
|
|
11
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/field-editor-reference",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.1",
|
|
4
4
|
"main": "dist/cjs/index.js",
|
|
5
5
|
"module": "dist/esm/index.js",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"publishConfig": {
|
|
65
65
|
"registry": "https://npm.pkg.github.com/"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "44c35849b76f6de924a678f578697989cd0293bf"
|
|
68
68
|
}
|