@atlaskit/smart-card 43.11.3 → 43.12.0
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/CHANGELOG.md +8 -0
- package/analytics.spec.yaml +7 -1
- package/dist/cjs/view/CardWithUrl/component.js +327 -3
- package/dist/cjs/view/EmbedCard/components/ExpandedFrame.js +93 -2
- package/dist/cjs/view/EmbedCard/components/Frame.js +121 -3
- package/dist/cjs/view/EmbedCard/components/IframeDwellTracker.js +25 -4
- package/dist/cjs/view/EmbedCard/index.js +204 -1
- package/dist/cjs/view/EmbedCard/views/ResolvedView.js +95 -2
- package/dist/es2019/view/CardWithUrl/component.js +324 -2
- package/dist/es2019/view/EmbedCard/components/ExpandedFrame.js +87 -2
- package/dist/es2019/view/EmbedCard/components/Frame.js +112 -2
- package/dist/es2019/view/EmbedCard/components/IframeDwellTracker.js +25 -4
- package/dist/es2019/view/EmbedCard/index.js +208 -0
- package/dist/es2019/view/EmbedCard/views/ResolvedView.js +91 -3
- package/dist/esm/view/CardWithUrl/component.js +326 -2
- package/dist/esm/view/EmbedCard/components/ExpandedFrame.js +95 -2
- package/dist/esm/view/EmbedCard/components/Frame.js +122 -2
- package/dist/esm/view/EmbedCard/components/IframeDwellTracker.js +25 -4
- package/dist/esm/view/EmbedCard/index.js +203 -0
- package/dist/esm/view/EmbedCard/views/ResolvedView.js +97 -3
- package/dist/types/common/analytics/generated/analytics.types.d.ts +1 -0
- package/dist/types/view/EmbedCard/components/ExpandedFrame.d.ts +8 -1
- package/dist/types/view/EmbedCard/components/Frame.d.ts +6 -0
- package/dist/types/view/EmbedCard/index.d.ts +4 -0
- package/dist/types/view/EmbedCard/types.d.ts +4 -0
- package/dist/types/view/EmbedCard/views/ResolvedView.d.ts +6 -1
- package/dist/types-ts4.5/common/analytics/generated/analytics.types.d.ts +1 -0
- package/dist/types-ts4.5/view/EmbedCard/components/ExpandedFrame.d.ts +8 -1
- package/dist/types-ts4.5/view/EmbedCard/components/Frame.d.ts +6 -0
- package/dist/types-ts4.5/view/EmbedCard/index.d.ts +4 -0
- package/dist/types-ts4.5/view/EmbedCard/types.d.ts +4 -0
- package/dist/types-ts4.5/view/EmbedCard/views/ResolvedView.d.ts +6 -1
- package/package.json +5 -1
|
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo } from 'react';
|
|
|
2
2
|
import { useAnalyticsEvents as useAnalyticsEventsNext } from '@atlaskit/analytics-next';
|
|
3
3
|
import { extractSmartLinkEmbed } from '@atlaskit/link-extractors';
|
|
4
4
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { componentWithFG } from '@atlaskit/platform-feature-flags-react';
|
|
5
6
|
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
6
7
|
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
|
|
7
8
|
import { useAnalyticsEvents } from '../../common/analytics/generated/use-analytics-events';
|
|
@@ -17,11 +18,12 @@ import { SmartLinkAnalyticsContext } from '../../utils/analytics/SmartLinkAnalyt
|
|
|
17
18
|
import { isFlexibleUiCard } from '../../utils/flexible';
|
|
18
19
|
import * as measure from '../../utils/performance';
|
|
19
20
|
import { BlockCard } from '../BlockCard';
|
|
20
|
-
import { EmbedCard } from '../EmbedCard';
|
|
21
|
+
import { EmbedCard, EmbedCardUpdated } from '../EmbedCard';
|
|
21
22
|
import FlexibleCard from '../FlexibleCard';
|
|
22
23
|
import { InlineCard } from '../InlineCard';
|
|
23
24
|
import { useFire3PWorkflowsClickEvent } from '../SmartLinkEvents/useSmartLinkEvents';
|
|
24
25
|
const thirdPartyARIPrefix = 'ari:third-party';
|
|
26
|
+
const EmbedCardComponent = componentWithFG('rovo_chat_embed_card_dwell_and_hover_metrics', EmbedCardUpdated, EmbedCard);
|
|
25
27
|
function Component({
|
|
26
28
|
id,
|
|
27
29
|
url,
|
|
@@ -322,11 +324,331 @@ function Component({
|
|
|
322
324
|
});
|
|
323
325
|
}
|
|
324
326
|
}
|
|
327
|
+
function ComponentUpdated({
|
|
328
|
+
id,
|
|
329
|
+
url,
|
|
330
|
+
isSelected,
|
|
331
|
+
isHovered,
|
|
332
|
+
frameStyle,
|
|
333
|
+
platform,
|
|
334
|
+
onClick,
|
|
335
|
+
appearance,
|
|
336
|
+
onResolve,
|
|
337
|
+
onError,
|
|
338
|
+
testId,
|
|
339
|
+
actionOptions: actionOptionsProp,
|
|
340
|
+
inheritDimensions,
|
|
341
|
+
embedIframeRef,
|
|
342
|
+
embedIframeUrlType,
|
|
343
|
+
inlinePreloaderStyle,
|
|
344
|
+
ui,
|
|
345
|
+
children,
|
|
346
|
+
showHoverPreview,
|
|
347
|
+
hoverPreviewOptions,
|
|
348
|
+
removeTextHighlightingFromTitle,
|
|
349
|
+
resolvingPlaceholder,
|
|
350
|
+
truncateInline,
|
|
351
|
+
CompetitorPrompt,
|
|
352
|
+
hideIconLoadingSkeleton,
|
|
353
|
+
disablePreviewPanel,
|
|
354
|
+
placeholderData
|
|
355
|
+
}) {
|
|
356
|
+
const {
|
|
357
|
+
createAnalyticsEvent
|
|
358
|
+
} = useAnalyticsEventsNext();
|
|
359
|
+
const {
|
|
360
|
+
fireEvent
|
|
361
|
+
} = useAnalyticsEvents();
|
|
362
|
+
let isFlexibleUi = useMemo(() => isFlexibleUiCard(children, ui), [children, ui]);
|
|
363
|
+
|
|
364
|
+
// Get state, actions for this card.
|
|
365
|
+
const {
|
|
366
|
+
state,
|
|
367
|
+
actions,
|
|
368
|
+
config,
|
|
369
|
+
renderers,
|
|
370
|
+
error,
|
|
371
|
+
isPreviewPanelAvailable,
|
|
372
|
+
openPreviewPanel
|
|
373
|
+
} = useSmartLink(id, url);
|
|
374
|
+
const ari = getObjectAri(state.details);
|
|
375
|
+
const name = getObjectName(state.details);
|
|
376
|
+
const definitionId = getDefinitionId(state.details);
|
|
377
|
+
const extensionKey = getExtensionKey(state.details);
|
|
378
|
+
const resourceType = getResourceType(state.details);
|
|
379
|
+
const services = getServices(state.details);
|
|
380
|
+
const thirdPartyARI = getThirdPartyARI(state.details);
|
|
381
|
+
const firstPartyIdentifier = getFirstPartyIdentifier();
|
|
382
|
+
const actionOptions = combineActionOptions({
|
|
383
|
+
actionOptions: actionOptionsProp,
|
|
384
|
+
platform
|
|
385
|
+
});
|
|
386
|
+
const fire3PClickEvent = fg('platform_smartlink_3pclick_analytics') ?
|
|
387
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
388
|
+
useFire3PWorkflowsClickEvent(firstPartyIdentifier, thirdPartyARI) : undefined;
|
|
389
|
+
|
|
390
|
+
// Setup UI handlers.
|
|
391
|
+
const handleClickWrapper = useCallback(event => {
|
|
392
|
+
const isModifierKeyPressed = isSpecialKey(event) || isSpecialClick(event);
|
|
393
|
+
fireEvent('ui.smartLink.clicked', {
|
|
394
|
+
id,
|
|
395
|
+
display: isFlexibleUi ? CardDisplay.Flexible : appearance,
|
|
396
|
+
definitionId: definitionId !== null && definitionId !== void 0 ? definitionId : null,
|
|
397
|
+
isModifierKeyPressed
|
|
398
|
+
});
|
|
399
|
+
if (fg('platform_smartlink_3pclick_analytics')) {
|
|
400
|
+
if (thirdPartyARI && thirdPartyARI.startsWith(thirdPartyARIPrefix)) {
|
|
401
|
+
const clickURL = getClickUrl(url, state.details);
|
|
402
|
+
if (clickURL === url && fire3PClickEvent) {
|
|
403
|
+
// For questions or concerns about this event,
|
|
404
|
+
// please reach out to the 3P Workflows Team via Slack in #help-3p-connector-workflow
|
|
405
|
+
fire3PClickEvent();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const isDisablePreviewPanel = disablePreviewPanel && editorExperiment('platform_editor_preview_panel_linking_exp', true, {
|
|
410
|
+
exposure: true
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// If preview panel is available and the user clicked on the link,
|
|
414
|
+
// delegate the click to the preview panel handler
|
|
415
|
+
if (!isModifierKeyPressed && ari && name && openPreviewPanel && isPreviewPanelAvailable !== null && isPreviewPanelAvailable !== void 0 && isPreviewPanelAvailable({
|
|
416
|
+
ari
|
|
417
|
+
}) && !isDisablePreviewPanel) {
|
|
418
|
+
var _extractSmartLinkEmbe2;
|
|
419
|
+
event.preventDefault();
|
|
420
|
+
event.stopPropagation();
|
|
421
|
+
openPreviewPanel({
|
|
422
|
+
url,
|
|
423
|
+
ari,
|
|
424
|
+
name,
|
|
425
|
+
iconUrl: getObjectIconUrl(state.details),
|
|
426
|
+
panelData: {
|
|
427
|
+
embedUrl: expValEquals('platform_hover_card_preview_panel', 'cohort', 'test') ? (_extractSmartLinkEmbe2 = extractSmartLinkEmbed(state.details)) === null || _extractSmartLinkEmbe2 === void 0 ? void 0 : _extractSmartLinkEmbe2.src : undefined
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
fireLinkClickedEvent(createAnalyticsEvent)(event, {
|
|
431
|
+
attributes: {
|
|
432
|
+
clickOutcome: 'previewPanel'
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
return;
|
|
436
|
+
} else if (!onClick && !isFlexibleUi) {
|
|
437
|
+
const clickUrl = getClickUrl(url, state.details);
|
|
438
|
+
|
|
439
|
+
// Ctrl+left click on mac typically doesn't trigger onClick
|
|
440
|
+
// The event could have potentially had `e.preventDefault()` called on it by now
|
|
441
|
+
// event by smart card internally
|
|
442
|
+
// If it has been called then only then can `isSpecialEvent` be true.
|
|
443
|
+
const target = isSpecialEvent(event) ? '_blank' : '_self';
|
|
444
|
+
window.open(clickUrl, target);
|
|
445
|
+
fireLinkClickedEvent(createAnalyticsEvent)(event, {
|
|
446
|
+
attributes: {
|
|
447
|
+
clickOutcome: target === '_blank' ? 'clickThroughNewTabOrWindow' : 'clickThrough'
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
} else {
|
|
451
|
+
if (onClick) {
|
|
452
|
+
onClick(event);
|
|
453
|
+
}
|
|
454
|
+
fireLinkClickedEvent(createAnalyticsEvent)(event);
|
|
455
|
+
}
|
|
456
|
+
}, [fireEvent, id, isFlexibleUi, appearance, definitionId, onClick, url, state.details, ari, name, fire3PClickEvent, isPreviewPanelAvailable, openPreviewPanel, createAnalyticsEvent, thirdPartyARI, disablePreviewPanel]);
|
|
457
|
+
const handleAuthorize = useCallback(() => actions.authorize(appearance), [actions, appearance]);
|
|
458
|
+
const handleRetry = useCallback(() => {
|
|
459
|
+
actions.reload();
|
|
460
|
+
}, [actions]);
|
|
461
|
+
const handleInvoke = useCallback(opts => actions.invoke(opts, appearance), [actions, appearance]);
|
|
462
|
+
|
|
463
|
+
// NB: for each status change in a Smart Link, a performance mark is created.
|
|
464
|
+
// Measures are sent relative to the first mark, matching what a user sees.
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
measure.mark(id, state.status);
|
|
467
|
+
if (state.status !== 'pending' && state.status !== 'resolving') {
|
|
468
|
+
var _state$error3, _state$error4;
|
|
469
|
+
measure.create(id, state.status);
|
|
470
|
+
if (state.status === 'resolved') {
|
|
471
|
+
var _measure$getMeasure$d2, _measure$getMeasure2;
|
|
472
|
+
fireEvent('operational.smartLink.resolved', {
|
|
473
|
+
definitionId: definitionId !== null && definitionId !== void 0 ? definitionId : null,
|
|
474
|
+
duration: (_measure$getMeasure$d2 = (_measure$getMeasure2 = measure.getMeasure(id, state.status)) === null || _measure$getMeasure2 === void 0 ? void 0 : _measure$getMeasure2.duration) !== null && _measure$getMeasure$d2 !== void 0 ? _measure$getMeasure$d2 : null
|
|
475
|
+
});
|
|
476
|
+
} else if (((_state$error3 = state.error) === null || _state$error3 === void 0 ? void 0 : _state$error3.type) !== 'ResolveUnsupportedError' && ((_state$error4 = state.error) === null || _state$error4 === void 0 ? void 0 : _state$error4.type) !== 'UnsupportedError') {
|
|
477
|
+
fireEvent('operational.smartLink.unresolved', {
|
|
478
|
+
definitionId: definitionId !== null && definitionId !== void 0 ? definitionId : null,
|
|
479
|
+
reason: state.status,
|
|
480
|
+
error: state.error === undefined ? null : {
|
|
481
|
+
name: state.error.name,
|
|
482
|
+
kind: state.error.kind,
|
|
483
|
+
type: state.error.type
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}, [id, appearance, state.status, state.error, definitionId, extensionKey, resourceType, fireEvent]);
|
|
489
|
+
|
|
490
|
+
// NB: once the smart-card has rendered into an end state, we capture
|
|
491
|
+
// this as a successful render. These can be one of:
|
|
492
|
+
// - the resolved state: when metadata is shown;
|
|
493
|
+
// - the unresolved states: viz. forbidden, not_found, unauthorized, errored.
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
if (isFinalState(state.status)) {
|
|
496
|
+
succeedUfoExperience('smart-link-rendered', id || 'NULL', {
|
|
497
|
+
extensionKey,
|
|
498
|
+
display: isFlexibleUi ? 'flexible' : appearance
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// UFO will disregard this if authentication experience has not yet been started
|
|
502
|
+
succeedUfoExperience('smart-link-authenticated', id || 'NULL', {
|
|
503
|
+
display: isFlexibleUi ? 'flexible' : appearance
|
|
504
|
+
});
|
|
505
|
+
fireEvent('ui.smartLink.renderSuccess', {
|
|
506
|
+
display: isFlexibleUi ? 'flexible' : appearance
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}, [appearance, extensionKey, fireEvent, id, isFlexibleUi, state.status]);
|
|
510
|
+
const onIframeDwell = useCallback((dwellTime, dwellPercentVisible) => {
|
|
511
|
+
fireEvent('ui.smartLinkIframe.dwelled', {
|
|
512
|
+
id,
|
|
513
|
+
definitionId: definitionId !== null && definitionId !== void 0 ? definitionId : null,
|
|
514
|
+
display: isFlexibleUi ? 'flexible' : appearance,
|
|
515
|
+
dwellPercentVisible,
|
|
516
|
+
dwellTime
|
|
517
|
+
});
|
|
518
|
+
}, [id, appearance, definitionId, isFlexibleUi, fireEvent]);
|
|
519
|
+
const onIframeFocus = useCallback(() => {
|
|
520
|
+
fireEvent('ui.smartLinkIframe.focused', {
|
|
521
|
+
id,
|
|
522
|
+
definitionId: definitionId !== null && definitionId !== void 0 ? definitionId : null,
|
|
523
|
+
display: isFlexibleUi ? 'flexible' : appearance,
|
|
524
|
+
interactionType: 'focus'
|
|
525
|
+
});
|
|
526
|
+
}, [id, appearance, definitionId, isFlexibleUi, fireEvent]);
|
|
527
|
+
const onIframeMouseEnter = useCallback(() => {
|
|
528
|
+
fireEvent('ui.smartLinkIframe.focused', {
|
|
529
|
+
id,
|
|
530
|
+
definitionId: definitionId !== null && definitionId !== void 0 ? definitionId : null,
|
|
531
|
+
display: isFlexibleUi ? 'flexible' : appearance,
|
|
532
|
+
interactionType: 'mouseenter'
|
|
533
|
+
});
|
|
534
|
+
}, [id, appearance, definitionId, isFlexibleUi, fireEvent]);
|
|
535
|
+
const onIframeMouseLeave = useCallback(() => {
|
|
536
|
+
fireEvent('ui.smartLinkIframe.focused', {
|
|
537
|
+
id,
|
|
538
|
+
definitionId: definitionId !== null && definitionId !== void 0 ? definitionId : null,
|
|
539
|
+
display: isFlexibleUi ? 'flexible' : appearance,
|
|
540
|
+
interactionType: 'mouseleave'
|
|
541
|
+
});
|
|
542
|
+
}, [id, appearance, definitionId, isFlexibleUi, fireEvent]);
|
|
543
|
+
if (isFlexibleUi) {
|
|
544
|
+
let cardState = state;
|
|
545
|
+
if (error) {
|
|
546
|
+
if ((error === null || error === void 0 ? void 0 : error.name) === 'APIError') {
|
|
547
|
+
cardState = {
|
|
548
|
+
status: 'errored'
|
|
549
|
+
};
|
|
550
|
+
} else {
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return /*#__PURE__*/React.createElement(FlexibleCard, {
|
|
555
|
+
id: id,
|
|
556
|
+
cardState: cardState,
|
|
557
|
+
placeholderData: fg('platform_initial_data_for_smart_cards') ? placeholderData : undefined,
|
|
558
|
+
onAuthorize: services.length && handleAuthorize || undefined,
|
|
559
|
+
onClick: handleClickWrapper,
|
|
560
|
+
origin: "smartLinkCard",
|
|
561
|
+
renderers: renderers,
|
|
562
|
+
ui: ui,
|
|
563
|
+
showHoverPreview: showHoverPreview,
|
|
564
|
+
hoverPreviewOptions: hoverPreviewOptions,
|
|
565
|
+
actionOptions: actionOptions,
|
|
566
|
+
url: url,
|
|
567
|
+
testId: testId,
|
|
568
|
+
onResolve: onResolve,
|
|
569
|
+
onError: onError
|
|
570
|
+
}, children);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// We have to keep this last to prevent hook order from being violated
|
|
574
|
+
if (error) {
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
switch (appearance) {
|
|
578
|
+
case 'inline':
|
|
579
|
+
return /*#__PURE__*/React.createElement(InlineCard, {
|
|
580
|
+
id: id,
|
|
581
|
+
url: url,
|
|
582
|
+
renderers: renderers,
|
|
583
|
+
cardState: state,
|
|
584
|
+
handleAuthorize: services.length && handleAuthorize || undefined,
|
|
585
|
+
handleFrameClick: handleClickWrapper,
|
|
586
|
+
isSelected: isSelected,
|
|
587
|
+
isHovered: isHovered,
|
|
588
|
+
onResolve: onResolve,
|
|
589
|
+
onError: onError,
|
|
590
|
+
testId: testId,
|
|
591
|
+
inlinePreloaderStyle: inlinePreloaderStyle,
|
|
592
|
+
showHoverPreview: showHoverPreview,
|
|
593
|
+
hoverPreviewOptions: hoverPreviewOptions,
|
|
594
|
+
actionOptions: actionOptions,
|
|
595
|
+
removeTextHighlightingFromTitle: removeTextHighlightingFromTitle,
|
|
596
|
+
resolvingPlaceholder: resolvingPlaceholder,
|
|
597
|
+
truncateInline: truncateInline,
|
|
598
|
+
hideIconLoadingSkeleton: hideIconLoadingSkeleton
|
|
599
|
+
});
|
|
600
|
+
case 'block':
|
|
601
|
+
return /*#__PURE__*/React.createElement(BlockCard, {
|
|
602
|
+
id: id,
|
|
603
|
+
url: url,
|
|
604
|
+
renderers: renderers,
|
|
605
|
+
authFlow: config && config.authFlow,
|
|
606
|
+
cardState: state,
|
|
607
|
+
handleAuthorize: services.length && handleAuthorize || undefined,
|
|
608
|
+
handleFrameClick: handleClickWrapper,
|
|
609
|
+
isSelected: isSelected,
|
|
610
|
+
onResolve: onResolve,
|
|
611
|
+
onError: onError,
|
|
612
|
+
testId: testId,
|
|
613
|
+
actionOptions: actionOptions,
|
|
614
|
+
CompetitorPrompt: CompetitorPrompt,
|
|
615
|
+
hideIconLoadingSkeleton: hideIconLoadingSkeleton
|
|
616
|
+
});
|
|
617
|
+
case 'embed':
|
|
618
|
+
return /*#__PURE__*/React.createElement(EmbedCardComponent, {
|
|
619
|
+
id: id,
|
|
620
|
+
url: url,
|
|
621
|
+
renderers: renderers,
|
|
622
|
+
cardState: state,
|
|
623
|
+
iframeUrlType: embedIframeUrlType,
|
|
624
|
+
handleAuthorize: services.length && handleAuthorize || undefined,
|
|
625
|
+
handleErrorRetry: handleRetry,
|
|
626
|
+
handleFrameClick: handleClickWrapper,
|
|
627
|
+
handleInvoke: handleInvoke,
|
|
628
|
+
isSelected: isSelected,
|
|
629
|
+
frameStyle: frameStyle,
|
|
630
|
+
platform: platform,
|
|
631
|
+
onResolve: onResolve,
|
|
632
|
+
onError: onError,
|
|
633
|
+
testId: testId,
|
|
634
|
+
inheritDimensions: inheritDimensions,
|
|
635
|
+
actionOptions: actionOptions,
|
|
636
|
+
ref: embedIframeRef,
|
|
637
|
+
onIframeDwell: onIframeDwell,
|
|
638
|
+
onIframeFocus: onIframeFocus,
|
|
639
|
+
onIframeMouseEnter: onIframeMouseEnter,
|
|
640
|
+
onIframeMouseLeave: onIframeMouseLeave,
|
|
641
|
+
CompetitorPrompt: CompetitorPrompt,
|
|
642
|
+
hideIconLoadingSkeleton: hideIconLoadingSkeleton
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const CardWithUrlContentComponent = componentWithFG('rovo_chat_embed_card_dwell_and_hover_metrics', ComponentUpdated, Component);
|
|
325
647
|
export const CardWithUrlContent = props => {
|
|
326
648
|
const display = isFlexibleUiCard(props.children, props === null || props === void 0 ? void 0 : props.ui) ? CardDisplay.Flexible : props.appearance;
|
|
327
649
|
return /*#__PURE__*/React.createElement(SmartLinkModalProvider, null, /*#__PURE__*/React.createElement(SmartLinkAnalyticsContext, {
|
|
328
650
|
url: props.url,
|
|
329
651
|
id: props.id,
|
|
330
652
|
display: display
|
|
331
|
-
}, /*#__PURE__*/React.createElement(
|
|
653
|
+
}, /*#__PURE__*/React.createElement(CardWithUrlContentComponent, props)));
|
|
332
654
|
};
|
|
@@ -3,12 +3,15 @@ import _extends from "@babel/runtime/helpers/extends";
|
|
|
3
3
|
import "./ExpandedFrame.compiled.css";
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import { ax, ix } from "@compiled/react/runtime";
|
|
6
|
+
// eslint-disable-next-line no-unused-vars
|
|
7
|
+
|
|
6
8
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
9
|
+
import { componentWithFG } from '@atlaskit/platform-feature-flags-react';
|
|
7
10
|
import Tooltip from '@atlaskit/tooltip';
|
|
8
11
|
import { useMouseDownEvent } from '../../../state/analytics/useLinkClicked';
|
|
9
12
|
import { handleClickCommon } from '../../common/utils';
|
|
10
13
|
import { className } from './styled';
|
|
11
|
-
|
|
14
|
+
const ExpandedFrame = ({
|
|
12
15
|
isPlaceholder = false,
|
|
13
16
|
children,
|
|
14
17
|
onClick,
|
|
@@ -96,4 +99,86 @@ const styles = {
|
|
|
96
99
|
contentStyle: "_19itdlqj _2rko12b0 _1reo15vq _18m915vq _v56414au _bfhkhp5a _16jlkb7n _4t3i1osq _1pbykb7n",
|
|
97
100
|
contentInteractiveActiveBorder: "_1jhm1tt7",
|
|
98
101
|
contentOverflowAuto: "_1reo1wug _18m91wug"
|
|
99
|
-
};
|
|
102
|
+
};
|
|
103
|
+
const ExpandedFrameUpdated = ({
|
|
104
|
+
isPlaceholder = false,
|
|
105
|
+
children,
|
|
106
|
+
onClick,
|
|
107
|
+
icon,
|
|
108
|
+
text,
|
|
109
|
+
isSelected,
|
|
110
|
+
frameStyle = 'showOnHover',
|
|
111
|
+
href,
|
|
112
|
+
minWidth,
|
|
113
|
+
maxWidth,
|
|
114
|
+
testId = 'expanded-frame',
|
|
115
|
+
inheritDimensions,
|
|
116
|
+
allowScrollBar = false,
|
|
117
|
+
setOverflow = true,
|
|
118
|
+
CompetitorPrompt,
|
|
119
|
+
onContentMouseEnter,
|
|
120
|
+
onContentMouseLeave
|
|
121
|
+
}) => {
|
|
122
|
+
const isInteractive = () => !isPlaceholder && (Boolean(href) || Boolean(onClick));
|
|
123
|
+
const handleClick = event => handleClickCommon(event, onClick);
|
|
124
|
+
const handleMouseDown = useMouseDownEvent();
|
|
125
|
+
|
|
126
|
+
// Note: cleanup fg based on results of prompt_whiteboard_competitor_link experiment
|
|
127
|
+
const CompetitorPromptComponent = CompetitorPrompt && href && fg('prompt_whiteboard_competitor_link_gate') ? /*#__PURE__*/React.createElement(CompetitorPrompt, {
|
|
128
|
+
sourceUrl: href,
|
|
129
|
+
linkType: "embed"
|
|
130
|
+
}) : null;
|
|
131
|
+
const renderHeader = () => {
|
|
132
|
+
return frameStyle !== 'hide' && /*#__PURE__*/React.createElement("div", {
|
|
133
|
+
className: ax([styles.header, "embed-header"])
|
|
134
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
135
|
+
className: ax([styles.leftSection])
|
|
136
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
137
|
+
className: ax([styles.headerIcon])
|
|
138
|
+
}, icon), /*#__PURE__*/React.createElement("div", {
|
|
139
|
+
className: ax([styles.tooltipWrapper])
|
|
140
|
+
}, !isPlaceholder && /*#__PURE__*/React.createElement(Tooltip, {
|
|
141
|
+
content: text,
|
|
142
|
+
hideTooltipOnMouseDown: true
|
|
143
|
+
}, /*#__PURE__*/React.createElement("a", {
|
|
144
|
+
href: href,
|
|
145
|
+
onClick: handleClick,
|
|
146
|
+
onMouseDown: handleMouseDown,
|
|
147
|
+
className: ax([styles.headerAnchor])
|
|
148
|
+
}, text)))), CompetitorPromptComponent);
|
|
149
|
+
};
|
|
150
|
+
const interactive = isInteractive();
|
|
151
|
+
const showBackgroundAlways = frameStyle === 'show' || isSelected && frameStyle !== 'hide';
|
|
152
|
+
const showBackgroundOnHover = interactive && frameStyle !== 'hide';
|
|
153
|
+
const renderContent = () => {
|
|
154
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
155
|
+
"data-testid": "embed-content-wrapper",
|
|
156
|
+
// This fixes an issue with input fields in cross domain iframes (ie. databases and jira fields from different domains)
|
|
157
|
+
// See: HOT-107830
|
|
158
|
+
contentEditable: false,
|
|
159
|
+
onMouseEnter: onContentMouseEnter,
|
|
160
|
+
onMouseLeave: onContentMouseLeave,
|
|
161
|
+
onFocus: onContentMouseEnter,
|
|
162
|
+
onBlur: onContentMouseLeave,
|
|
163
|
+
className: ax([styles.contentStyle, setOverflow && allowScrollBar && styles.contentOverflowAuto, interactive && !showBackgroundAlways && !showBackgroundOnHover && styles.contentInteractiveActiveBorder])
|
|
164
|
+
}, children);
|
|
165
|
+
};
|
|
166
|
+
return /*#__PURE__*/React.createElement("div", _extends({
|
|
167
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
|
|
168
|
+
className: ax([styles.linkWrapper, inheritDimensions && styles.linkWrapperInheritDimensions, isSelected && frameStyle !== 'hide' && styles.linkWrapperSelected, showBackgroundAlways && styles.linkWrapperBorderAndBackground, showBackgroundOnHover && !showBackgroundAlways && styles.linkWrapperInteractiveNotHidden, className]),
|
|
169
|
+
style: {
|
|
170
|
+
minWidth: minWidth ? `${minWidth}px` : '',
|
|
171
|
+
maxWidth: maxWidth ? `${maxWidth}px` : ''
|
|
172
|
+
},
|
|
173
|
+
"data-testid": testId,
|
|
174
|
+
"data-trello-do-not-use-override": testId
|
|
175
|
+
// Due to limitations of testing library, we can't assert ::after
|
|
176
|
+
,
|
|
177
|
+
"data-is-selected": isSelected
|
|
178
|
+
}, (isPlaceholder || !href) && {
|
|
179
|
+
'data-wrapper-type': 'default',
|
|
180
|
+
'data-is-interactive': isInteractive()
|
|
181
|
+
}), renderHeader(), renderContent());
|
|
182
|
+
};
|
|
183
|
+
const ExpandedFrameWithFG = componentWithFG('rovo_chat_embed_card_dwell_and_hover_metrics', ExpandedFrameUpdated, ExpandedFrame);
|
|
184
|
+
export { ExpandedFrameWithFG as ExpandedFrame };
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import "./Frame.compiled.css";
|
|
3
3
|
import { ax, ix } from "@compiled/react/runtime";
|
|
4
4
|
import React, { useEffect, useRef, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line no-unused-vars
|
|
7
|
+
|
|
5
8
|
import { di } from 'react-magnetic-di';
|
|
6
9
|
import { getIframeSandboxAttribute } from '../../../utils';
|
|
7
10
|
import { IFrame } from './IFrame';
|
|
@@ -87,8 +90,115 @@ export const Frame = /*#__PURE__*/React.forwardRef(({
|
|
|
87
90
|
src: url,
|
|
88
91
|
"data-testid": `${testId}-frame`,
|
|
89
92
|
"data-iframe-loaded": isIframeLoaded,
|
|
90
|
-
onMouseEnter: () =>
|
|
91
|
-
|
|
93
|
+
onMouseEnter: () => {
|
|
94
|
+
setMouseOver(true);
|
|
95
|
+
},
|
|
96
|
+
onMouseLeave: () => {
|
|
97
|
+
setMouseOver(false);
|
|
98
|
+
},
|
|
99
|
+
allowFullScreen: true,
|
|
100
|
+
scrolling: "yes",
|
|
101
|
+
allow: "autoplay; encrypted-media; clipboard-write",
|
|
102
|
+
onLoad: () => {
|
|
103
|
+
setIframeLoaded(true);
|
|
104
|
+
},
|
|
105
|
+
sandbox: getIframeSandboxAttribute(isTrusted),
|
|
106
|
+
title: title,
|
|
107
|
+
extensionKey: extensionKey,
|
|
108
|
+
className: ax(["_19itidpf _1reo15vq _18m915vq _2rkofajl _154iidpf _1ltvidpf _1bsb1osq _4t3i1osq _kqswh2mm"])
|
|
109
|
+
}));
|
|
110
|
+
});
|
|
111
|
+
export const FrameUpdated = /*#__PURE__*/React.forwardRef(({
|
|
112
|
+
url,
|
|
113
|
+
isTrusted = false,
|
|
114
|
+
testId,
|
|
115
|
+
onIframeDwell,
|
|
116
|
+
onIframeFocus,
|
|
117
|
+
onIframeMouseEnter,
|
|
118
|
+
onIframeMouseLeave,
|
|
119
|
+
isMouseOver: isMouseOverProp,
|
|
120
|
+
title,
|
|
121
|
+
extensionKey
|
|
122
|
+
}, iframeRef) => {
|
|
123
|
+
const [isIframeLoaded, setIframeLoaded] = useState(false);
|
|
124
|
+
const [isMouseOver, setMouseOver] = useState(false);
|
|
125
|
+
const [isWindowFocused, setWindowFocused] = useState(document.hasFocus());
|
|
126
|
+
|
|
127
|
+
// Use prop if provided (from wrapper), otherwise use local state (for backward compatibility)
|
|
128
|
+
const effectiveMouseOver = isMouseOverProp !== undefined ? isMouseOverProp : isMouseOver;
|
|
129
|
+
const ref = useRef();
|
|
130
|
+
const mergedRef = mergeRefs([iframeRef, ref]);
|
|
131
|
+
const [percentVisible, setPercentVisible] = useState(0);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* These are the 'percent visible' thresholds at which the intersectionObserver will
|
|
135
|
+
* trigger a state change. Eg. when the user scrolls and moves from 74% to 76%, or
|
|
136
|
+
* vice versa. It's in a state object so that its static for the useEffect
|
|
137
|
+
*/
|
|
138
|
+
const [threshold] = useState([0.75, 0.8, 0.85, 0.9, 0.95, 1]);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (!ref || !ref.current) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const observer = new IntersectionObserver(entries => {
|
|
144
|
+
entries.forEach(entry => {
|
|
145
|
+
setPercentVisible(entry === null || entry === void 0 ? void 0 : entry.intersectionRatio);
|
|
146
|
+
});
|
|
147
|
+
}, {
|
|
148
|
+
threshold
|
|
149
|
+
});
|
|
150
|
+
observer.observe(ref.current);
|
|
151
|
+
return () => {
|
|
152
|
+
observer.disconnect();
|
|
153
|
+
};
|
|
154
|
+
}, [threshold, mergedRef]);
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
// Initialize with current focus state
|
|
157
|
+
setWindowFocused(document.hasFocus());
|
|
158
|
+
const onBlur = () => {
|
|
159
|
+
setWindowFocused(false);
|
|
160
|
+
if (document.activeElement === ref.current) {
|
|
161
|
+
onIframeFocus && onIframeFocus();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const onFocus = () => {
|
|
165
|
+
setWindowFocused(true);
|
|
166
|
+
};
|
|
167
|
+
window.addEventListener('blur', onBlur);
|
|
168
|
+
window.addEventListener('focus', onFocus);
|
|
169
|
+
return () => {
|
|
170
|
+
window.removeEventListener('blur', onBlur);
|
|
171
|
+
window.removeEventListener('focus', onFocus);
|
|
172
|
+
};
|
|
173
|
+
}, [ref, onIframeFocus]);
|
|
174
|
+
if (!url) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(IframeDwellTracker, {
|
|
178
|
+
isIframeLoaded: isIframeLoaded,
|
|
179
|
+
isMouseOver: effectiveMouseOver,
|
|
180
|
+
isWindowFocused: isWindowFocused,
|
|
181
|
+
iframePercentVisible: percentVisible,
|
|
182
|
+
onIframeDwell: onIframeDwell
|
|
183
|
+
}), /*#__PURE__*/React.createElement(IFrame, {
|
|
184
|
+
childRef: mergedRef,
|
|
185
|
+
src: url,
|
|
186
|
+
"data-testid": `${testId}-frame`,
|
|
187
|
+
"data-iframe-loaded": isIframeLoaded,
|
|
188
|
+
onMouseEnter: () => {
|
|
189
|
+
onIframeMouseEnter === null || onIframeMouseEnter === void 0 ? void 0 : onIframeMouseEnter();
|
|
190
|
+
// Use local state if prop not provided, otherwise prop takes precedence
|
|
191
|
+
if (isMouseOverProp === undefined) {
|
|
192
|
+
setMouseOver(true);
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
onMouseLeave: () => {
|
|
196
|
+
onIframeMouseLeave === null || onIframeMouseLeave === void 0 ? void 0 : onIframeMouseLeave();
|
|
197
|
+
// Use local state if prop not provided, otherwise prop takes precedence
|
|
198
|
+
if (isMouseOverProp === undefined) {
|
|
199
|
+
setMouseOver(false);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
92
202
|
allowFullScreen: true,
|
|
93
203
|
scrolling: "yes",
|
|
94
204
|
allow: "autoplay; encrypted-media; clipboard-write",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @jsx jsx
|
|
4
4
|
*/
|
|
5
5
|
import { useEffect, useRef, useState } from 'react';
|
|
6
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
6
7
|
/**
|
|
7
8
|
* A kind of cheap logarithmic backoff. Fire analytics after the user has
|
|
8
9
|
* dwelled for 5 seconds, then 10 seconds, and so on.
|
|
@@ -42,11 +43,31 @@ export const IframeDwellTracker = ({
|
|
|
42
43
|
};
|
|
43
44
|
});
|
|
44
45
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
|
|
47
|
+
// Require: iframe loaded, mouse over, and >75% visible
|
|
48
|
+
const isDwellAndHoverMetricsEnabled = fg('rovo_chat_embed_card_dwell_and_hover_metrics');
|
|
49
|
+
if (isDwellAndHoverMetricsEnabled) {
|
|
50
|
+
// Note: Removed isWindowFocused requirement as it's unreliable and prevents tracking
|
|
51
|
+
// The mouse over check is sufficient to indicate user engagement
|
|
52
|
+
const shouldTrack = isIframeLoaded && isMouseOver && iframePercentVisible > 0.75;
|
|
53
|
+
if (shouldTrack) {
|
|
54
|
+
if (dwellTimeoutId.current) {
|
|
55
|
+
clearInterval(dwellTimeoutId.current);
|
|
56
|
+
}
|
|
57
|
+
dwellTimeoutId.current = setInterval(incrementDwellTime, 1000);
|
|
58
|
+
} else {
|
|
59
|
+
if (dwellTimeoutId.current) {
|
|
60
|
+
clearInterval(dwellTimeoutId.current);
|
|
61
|
+
dwellTimeoutId.current = undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
if (isIframeLoaded && isMouseOver && isWindowFocused && iframePercentVisible > 0.75) {
|
|
66
|
+
if (dwellTimeoutId.current) {
|
|
67
|
+
clearInterval(dwellTimeoutId.current);
|
|
68
|
+
}
|
|
69
|
+
dwellTimeoutId.current = setInterval(incrementDwellTime, 1000);
|
|
48
70
|
}
|
|
49
|
-
dwellTimeoutId.current = setInterval(incrementDwellTime, 1000);
|
|
50
71
|
}
|
|
51
72
|
return () => {
|
|
52
73
|
if (dwellTimeoutId.current) {
|