@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.
@@ -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 })=>items[0] ?? null), options),
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(props) {
20
- const providerName = props.providerName ?? 'Source';
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 (props.isDisabled || !props.onRemove) return null;
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
- props.onRemove && props.onRemove();
31
+ onRemove && onRemove();
33
32
  }
34
33
  });
35
34
  }
36
35
  return _react.default.createElement(_f36components.EntryCard, {
37
- as: "a",
36
+ as: as,
38
37
  contentType: providerName,
39
38
  description: description,
40
- isSelected: props.isSelected,
39
+ isSelected: isSelected,
41
40
  customActionButton: _react.default.createElement(CustomActionButton, null),
42
- testId: "cf-ui-missing-entity-card"
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 })=>items[0] ?? null), options),
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(props) {
5
- const providerName = props.providerName ?? 'Source';
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 (props.isDisabled || !props.onRemove) return null;
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
- props.onRemove && props.onRemove();
16
+ onRemove && onRemove();
18
17
  }
19
18
  });
20
19
  }
21
20
  return React.createElement(EntryCard, {
22
- as: "a",
21
+ as: as,
23
22
  contentType: providerName,
24
23
  description: description,
25
- isSelected: props.isSelected,
24
+ isSelected: isSelected,
26
25
  customActionButton: React.createElement(CustomActionButton, null),
27
- testId: "cf-ui-missing-entity-card"
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(props: MissingEntityCardProps): React.JSX.Element;
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.2.5",
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": "5744bed7b566437cb831312e84af7444b9047b69"
67
+ "gitHead": "44c35849b76f6de924a678f578697989cd0293bf"
68
68
  }