@atlaskit/smart-card 44.7.0 → 44.7.2
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 +18 -0
- package/dist/cjs/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-cold-cache--default.png +3 -0
- package/dist/cjs/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-low-tier--default.png +3 -0
- package/dist/cjs/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-not-low-tier--default.png +3 -0
- package/dist/cjs/messages.js +12 -1
- package/dist/cjs/state/hooks/use-current-site-cloud-id/index.js +0 -30
- package/dist/cjs/state/hooks/use-incoming-outgoing-links/index.js +3 -3
- package/dist/cjs/state/hooks/use-social-proof/index.js +44 -28
- package/dist/cjs/state/hooks/use-social-proof-experiment/index.js +48 -0
- package/dist/cjs/state/services/current-site-cloud-id/index.js +61 -74
- package/dist/cjs/state/services/personalization/index.js +59 -60
- package/dist/cjs/utils/analytics/analytics.js +1 -1
- package/dist/cjs/view/BlockCard/views/SocialProofMessage.js +36 -0
- package/dist/cjs/view/BlockCard/views/UnauthorisedView.js +85 -20
- package/dist/cjs/view/LinkUrl/index.js +1 -1
- package/dist/es2019/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-cold-cache--default.png +3 -0
- package/dist/es2019/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-low-tier--default.png +3 -0
- package/dist/es2019/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-not-low-tier--default.png +3 -0
- package/dist/es2019/messages.js +12 -1
- package/dist/es2019/state/hooks/use-current-site-cloud-id/index.js +0 -1
- package/dist/es2019/state/hooks/use-incoming-outgoing-links/index.js +2 -2
- package/dist/es2019/state/hooks/use-social-proof/index.js +41 -22
- package/dist/es2019/state/hooks/use-social-proof-experiment/index.js +41 -0
- package/dist/es2019/state/services/current-site-cloud-id/index.js +43 -67
- package/dist/es2019/state/services/personalization/index.js +40 -39
- package/dist/es2019/utils/analytics/analytics.js +1 -1
- package/dist/es2019/view/BlockCard/views/SocialProofMessage.js +27 -0
- package/dist/es2019/view/BlockCard/views/UnauthorisedView.js +83 -15
- package/dist/es2019/view/LinkUrl/index.js +1 -1
- package/dist/esm/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-cold-cache--default.png +3 -0
- package/dist/esm/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-low-tier--default.png +3 -0
- package/dist/esm/__tests__/vr-tests/__snapshots__/block-card/block-card-social-proof-message-not-low-tier--default.png +3 -0
- package/dist/esm/messages.js +12 -1
- package/dist/esm/state/hooks/use-current-site-cloud-id/index.js +0 -1
- package/dist/esm/state/hooks/use-incoming-outgoing-links/index.js +2 -2
- package/dist/esm/state/hooks/use-social-proof/index.js +45 -29
- package/dist/esm/state/hooks/use-social-proof-experiment/index.js +41 -0
- package/dist/esm/state/services/current-site-cloud-id/index.js +59 -72
- package/dist/esm/state/services/personalization/index.js +56 -59
- package/dist/esm/utils/analytics/analytics.js +1 -1
- package/dist/esm/view/BlockCard/views/SocialProofMessage.js +29 -0
- package/dist/esm/view/BlockCard/views/UnauthorisedView.js +85 -20
- package/dist/esm/view/LinkUrl/index.js +1 -1
- package/dist/types/messages.d.ts +1 -1
- package/dist/types/state/hooks/use-current-site-cloud-id/index.d.ts +0 -1
- package/dist/types/state/hooks/use-social-proof/index.d.ts +17 -4
- package/dist/types/state/hooks/use-social-proof-experiment/index.d.ts +39 -0
- package/dist/types/state/services/current-site-cloud-id/index.d.ts +9 -33
- package/dist/types/state/services/personalization/index.d.ts +14 -24
- package/dist/types/view/BlockCard/views/SocialProofMessage.d.ts +14 -0
- package/dist/types-ts4.5/messages.d.ts +1 -1
- package/dist/types-ts4.5/state/hooks/use-current-site-cloud-id/index.d.ts +0 -1
- package/dist/types-ts4.5/state/hooks/use-social-proof/index.d.ts +17 -4
- package/dist/types-ts4.5/state/hooks/use-social-proof-experiment/index.d.ts +39 -0
- package/dist/types-ts4.5/state/services/current-site-cloud-id/index.d.ts +9 -33
- package/dist/types-ts4.5/state/services/personalization/index.d.ts +14 -24
- package/dist/types-ts4.5/view/BlockCard/views/SocialProofMessage.d.ts +14 -0
- package/package.json +10 -4
- package/smart-card.docs.tsx +2 -2
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import { StorageClient } from '@atlaskit/frontend-utilities/storage-client';
|
|
3
3
|
import { request } from '@atlaskit/linking-common';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* stores rows as `<clientKey>_<itemKey>` with `clientKey === '@atlaskit/smart-card'` and
|
|
8
|
-
* `itemKey === 'site-cloud-id:v1'` (no further scope segments; unlike `pct-map:v1:`, this is a single fixed row).
|
|
9
|
-
*/
|
|
10
|
-
const SMART_CARD_STORAGE_SCOPE = '@atlaskit/smart-card';
|
|
11
|
-
export const CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY = 'site-cloud-id:v1';
|
|
4
|
+
const SMART_CARD_STORAGE_SCOPE = 'smart-card-social-proof';
|
|
5
|
+
export const CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY_PREFIX = 'site-cloud-id:v1:';
|
|
6
|
+
export const CURRENT_SITE_CLOUD_ID_TTL_MS = 24 * 60 * 60 * 1000;
|
|
12
7
|
const smartCardStorage = new StorageClient(SMART_CARD_STORAGE_SCOPE);
|
|
8
|
+
function normalizeBaseUri(baseUriWithNoTrailingSlash = '') {
|
|
9
|
+
return baseUriWithNoTrailingSlash.replace(/\/$/, '');
|
|
10
|
+
}
|
|
11
|
+
function cloudIdStorageItemKey(baseUriWithNoTrailingSlash = '') {
|
|
12
|
+
return `${CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY_PREFIX}${encodeURIComponent(normalizeBaseUri(baseUriWithNoTrailingSlash))}`;
|
|
13
|
+
}
|
|
13
14
|
|
|
14
15
|
/** Keys written by this service in localStorage when using {@link smartCardStorage}. */
|
|
15
|
-
export const
|
|
16
|
+
export const getCurrentSiteCloudIdLocalStorageKey = (baseUriWithNoTrailingSlash = '') => `${SMART_CARD_STORAGE_SCOPE}_${cloudIdStorageItemKey(baseUriWithNoTrailingSlash)}`;
|
|
17
|
+
|
|
18
|
+
/** Backwards-compatible default-scope key for existing tests and external assertions. */
|
|
19
|
+
export const CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY = getCurrentSiteCloudIdLocalStorageKey();
|
|
16
20
|
export class CurrentSiteCloudIdService {
|
|
17
21
|
constructor() {
|
|
18
|
-
|
|
19
|
-
* Holds the shared tenant_info work: one in-flight fetch, then (on success) a settled promise for the session cloud
|
|
20
|
-
* id so later callers never trigger another `tenant_info` in the same page lifetime (until {@link clearCache}).
|
|
21
|
-
*/
|
|
22
|
-
_defineProperty(this, "tenantInfoInflightPromise", null);
|
|
22
|
+
_defineProperty(this, "tenantInfoInflightPromises", new Map());
|
|
23
23
|
}
|
|
24
|
-
readStoredCloudId() {
|
|
24
|
+
readStoredCloudId(baseUriWithNoTrailingSlash = '') {
|
|
25
25
|
try {
|
|
26
|
-
const cloudId = smartCardStorage.getItem(
|
|
26
|
+
const cloudId = smartCardStorage.getItem(cloudIdStorageItemKey(baseUriWithNoTrailingSlash));
|
|
27
27
|
if (typeof cloudId !== 'string' || !cloudId || cloudId === 'undefined') {
|
|
28
28
|
return undefined;
|
|
29
29
|
}
|
|
@@ -32,87 +32,63 @@ export class CurrentSiteCloudIdService {
|
|
|
32
32
|
return undefined;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
writeStoredCloudId(cloudId) {
|
|
35
|
+
writeStoredCloudId(baseUriWithNoTrailingSlash, cloudId) {
|
|
36
36
|
if (!cloudId) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
|
-
smartCardStorage.setItemWithExpiry(
|
|
40
|
+
smartCardStorage.setItemWithExpiry(cloudIdStorageItemKey(baseUriWithNoTrailingSlash), cloudId, CURRENT_SITE_CLOUD_ID_TTL_MS);
|
|
41
41
|
} catch {
|
|
42
42
|
// Quota, private-mode, SSR, etc. — same intent as personalization-service.
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
ensureTenantInfoInflightStarted(baseUriWithNoTrailingSlash) {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const baseUri = normalizeBaseUri(baseUriWithNoTrailingSlash);
|
|
47
|
+
const existing = this.tenantInfoInflightPromises.get(baseUri);
|
|
48
|
+
if (existing) {
|
|
49
|
+
return existing;
|
|
48
50
|
}
|
|
49
|
-
|
|
51
|
+
const promise = (async () => {
|
|
50
52
|
try {
|
|
51
|
-
const response = await request('get',
|
|
53
|
+
const response = await request('get', `${baseUri}/_edge/tenant_info`);
|
|
52
54
|
const cloudId = response === null || response === void 0 ? void 0 : response.cloudId;
|
|
53
55
|
if (cloudId) {
|
|
54
|
-
this.writeStoredCloudId(cloudId);
|
|
56
|
+
this.writeStoredCloudId(baseUri, cloudId);
|
|
55
57
|
}
|
|
56
|
-
return cloudId ? cloudId : this.readStoredCloudId();
|
|
58
|
+
return cloudId ? cloudId : this.readStoredCloudId(baseUri);
|
|
57
59
|
} catch {
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
+
return this.readStoredCloudId(baseUri);
|
|
61
|
+
} finally {
|
|
62
|
+
this.tenantInfoInflightPromises.delete(baseUri);
|
|
60
63
|
}
|
|
61
64
|
})();
|
|
65
|
+
this.tenantInfoInflightPromises.set(baseUri, promise);
|
|
66
|
+
return promise;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*/
|
|
68
|
-
getCachedCloudIdAndRefresh() {
|
|
69
|
-
this.ensureTenantInfoInflightStarted('');
|
|
70
|
-
return this.readStoredCloudId();
|
|
69
|
+
/** Pure synchronous read scoped to the given base URI. */
|
|
70
|
+
getStoredCloudId(baseUriWithNoTrailingSlash = '') {
|
|
71
|
+
return this.readStoredCloudId(baseUriWithNoTrailingSlash);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
/** Writes tenant cloud id for tests or callers that intentionally warm storage before edge resolves. */
|
|
74
|
-
persistStoredCloudId(cloudId) {
|
|
75
|
-
this.writeStoredCloudId(cloudId);
|
|
75
|
+
persistStoredCloudId(cloudId, baseUriWithNoTrailingSlash = '') {
|
|
76
|
+
this.writeStoredCloudId(baseUriWithNoTrailingSlash, cloudId);
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* When local storage already has a tenant cloud id, it is returned immediately; a background tenant_info refresh
|
|
80
|
-
* is still kicked off unless one is already in flight.
|
|
81
|
-
*
|
|
82
|
-
* Without storage, this awaits the deduped in-flight tenant_info (first concurrent caller chooses the URL;
|
|
83
|
-
* all share one promise regardless of subsequent `baseUriWithNoTrailingSlash`).
|
|
84
|
-
*
|
|
85
|
-
* On network success with no cloud id, or on failure: falls back via {@link readStoredCloudId}.
|
|
86
|
-
*/
|
|
87
78
|
async get(baseUriWithNoTrailingSlash = '') {
|
|
88
|
-
const fromStorage = this.readStoredCloudId();
|
|
89
|
-
this.ensureTenantInfoInflightStarted(baseUriWithNoTrailingSlash);
|
|
79
|
+
const fromStorage = this.readStoredCloudId(baseUriWithNoTrailingSlash);
|
|
90
80
|
if (fromStorage) {
|
|
91
81
|
return fromStorage;
|
|
92
82
|
}
|
|
93
|
-
return this.
|
|
83
|
+
return this.ensureTenantInfoInflightStarted(baseUriWithNoTrailingSlash);
|
|
94
84
|
}
|
|
95
85
|
/** Clears session pin and persisted storage so the next {@link get} is a fresh tenant_info fetch. */
|
|
96
86
|
clearCache() {
|
|
97
|
-
this.
|
|
98
|
-
smartCardStorage.removeItem(CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY);
|
|
87
|
+
this.tenantInfoInflightPromises.clear();
|
|
99
88
|
}
|
|
100
89
|
}
|
|
101
90
|
export const currentSiteCloudIdService = new CurrentSiteCloudIdService();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
* `tenant_info` request and persists the result for subsequent cached reads.
|
|
107
|
-
*/
|
|
108
|
-
export const getCurrentSiteCloudId = (baseUriWithNoTrailingSlash = '') => currentSiteCloudIdService.get(baseUriWithNoTrailingSlash);
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Reads the current site cloud id from browser storage (the `site-cloud-id:v1` row) via the
|
|
112
|
-
* module-level {@link currentSiteCloudIdService} singleton, without awaiting network work.
|
|
113
|
-
* Calling this also starts the shared `tenant_info` refresh in the background when one is not
|
|
114
|
-
* already running, so a later call can observe a refreshed value when available.
|
|
115
|
-
*/
|
|
116
|
-
export function getCachedCurrentSiteCloudIdAndRefresh() {
|
|
117
|
-
return currentSiteCloudIdService.getCachedCloudIdAndRefresh();
|
|
118
|
-
}
|
|
91
|
+
export function getCurrentSiteCloudIdSync(baseUriWithNoTrailingSlash = '') {
|
|
92
|
+
return currentSiteCloudIdService.getStoredCloudId(baseUriWithNoTrailingSlash);
|
|
93
|
+
}
|
|
94
|
+
export const getCurrentSiteCloudId = (baseUriWithNoTrailingSlash = '') => currentSiteCloudIdService.get(baseUriWithNoTrailingSlash);
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import { StorageClient } from '@atlaskit/frontend-utilities/storage-client';
|
|
3
|
-
import { getCurrentSiteCloudId,
|
|
3
|
+
import { getCurrentSiteCloudId, getCurrentSiteCloudIdSync } from '../current-site-cloud-id';
|
|
4
4
|
const BASE_URL = '/gateway/api/tap-delivery/api/v3/personalization';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Logical key shape: `@atlaskit/smart-card:<feature>:<schema-version>:<scope>` (see smart-card
|
|
8
|
-
* storage conventions). {@link StorageClient} narrows the localStorage key to
|
|
9
|
-
* `<clientKey>_<itemKey>` with `clientKey === '@atlaskit/smart-card'` and
|
|
10
|
-
* `itemKey === 'pct-map:v1:<cloudId>:<traitName>'` (scope segments URI-encoded).
|
|
11
|
-
*/
|
|
12
|
-
export const PERSONALIZATION_STORAGE_SCOPE = '@atlaskit/smart-card';
|
|
5
|
+
export const PERSONALIZATION_STORAGE_SCOPE = 'smart-card-social-proof';
|
|
13
6
|
export const PERSONALIZATION_STORAGE_ITEM_KEY_PREFIX = 'pct-map:v1:';
|
|
7
|
+
export const PERSONALIZATION_PROVIDER_PCT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
8
|
+
export const SOCIAL_PROOF_TRAIT_NAME = 'sl_3p_connected_providers_site_pct';
|
|
14
9
|
const smartCardStorage = new StorageClient(PERSONALIZATION_STORAGE_SCOPE);
|
|
15
10
|
|
|
16
11
|
/** Keys written by this service in localStorage when using {@link smartCardStorage}. */
|
|
17
12
|
const LOCAL_STORAGE_ROW_KEY_PREFIX = `${PERSONALIZATION_STORAGE_SCOPE}_${PERSONALIZATION_STORAGE_ITEM_KEY_PREFIX}`;
|
|
13
|
+
function scopedCacheKey(cloudId, traitName) {
|
|
14
|
+
return `${cloudId}:${traitName}`;
|
|
15
|
+
}
|
|
18
16
|
function pctMapStorageItemKey(cloudId, traitName) {
|
|
19
17
|
return `${PERSONALIZATION_STORAGE_ITEM_KEY_PREFIX}${encodeURIComponent(cloudId)}:${encodeURIComponent(traitName)}`;
|
|
20
18
|
}
|
|
@@ -41,27 +39,24 @@ export class PersonalizationService {
|
|
|
41
39
|
constructor() {
|
|
42
40
|
_defineProperty(this, "cache", new Map());
|
|
43
41
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const fromStorage = cloudId && this.readStoredProviderPctMap(cloudId, traitName) || null;
|
|
51
|
-
void this.getProviderPctMap(traitName);
|
|
52
|
-
return fromStorage;
|
|
42
|
+
/** Pure synchronous read for an explicit cloud id / trait pair. */
|
|
43
|
+
getProviderPctMapSync(cloudId, traitName) {
|
|
44
|
+
if (!cloudId) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return this.readStoredProviderPctMap(cloudId, traitName);
|
|
53
48
|
}
|
|
54
|
-
async getProviderPctMap(traitName) {
|
|
55
|
-
|
|
49
|
+
async getProviderPctMap(cloudId, traitName) {
|
|
50
|
+
if (!cloudId) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const cacheKey = scopedCacheKey(cloudId, traitName);
|
|
54
|
+
const cachedPromise = this.cache.get(cacheKey);
|
|
56
55
|
if (cachedPromise) {
|
|
57
56
|
return cachedPromise;
|
|
58
57
|
}
|
|
59
58
|
const promise = (async () => {
|
|
60
59
|
try {
|
|
61
|
-
const cloudId = await getCurrentSiteCloudId();
|
|
62
|
-
if (!cloudId) {
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
60
|
const traits = await fetchSiteTraits(cloudId);
|
|
66
61
|
const trait = traits.find(t => t.name === traitName);
|
|
67
62
|
const mapped = this.parseTraitValue(trait === null || trait === void 0 ? void 0 : trait.value);
|
|
@@ -73,8 +68,11 @@ export class PersonalizationService {
|
|
|
73
68
|
return undefined;
|
|
74
69
|
}
|
|
75
70
|
})();
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
const retryablePromise = promise.finally(() => {
|
|
72
|
+
this.cache.delete(cacheKey);
|
|
73
|
+
});
|
|
74
|
+
this.cache.set(cacheKey, retryablePromise);
|
|
75
|
+
return retryablePromise;
|
|
78
76
|
}
|
|
79
77
|
readStoredProviderPctMap(cloudId, traitName) {
|
|
80
78
|
try {
|
|
@@ -89,7 +87,7 @@ export class PersonalizationService {
|
|
|
89
87
|
}
|
|
90
88
|
writeStoredProviderPctMap(cloudId, traitName, map) {
|
|
91
89
|
try {
|
|
92
|
-
smartCardStorage.setItemWithExpiry(pctMapStorageItemKey(cloudId, traitName), map);
|
|
90
|
+
smartCardStorage.setItemWithExpiry(pctMapStorageItemKey(cloudId, traitName), map, PERSONALIZATION_PROVIDER_PCT_TTL_MS);
|
|
93
91
|
} catch {
|
|
94
92
|
// Quota, private-mode, etc.
|
|
95
93
|
}
|
|
@@ -145,20 +143,23 @@ export class PersonalizationService {
|
|
|
145
143
|
}
|
|
146
144
|
}
|
|
147
145
|
export const personalizationService = new PersonalizationService();
|
|
146
|
+
export const getProviderPctMap = (cloudId, traitName) => personalizationService.getProviderPctMap(cloudId, traitName);
|
|
147
|
+
export function getProviderPctMapSync(cloudId, traitName) {
|
|
148
|
+
return personalizationService.getProviderPctMapSync(cloudId, traitName);
|
|
149
|
+
}
|
|
148
150
|
|
|
149
151
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Reads the provider percentage map for a trait from browser storage via the module-level
|
|
158
|
-
* {@link personalizationService} singleton, without awaiting network work.
|
|
159
|
-
* Calling this also starts the trait-scoped shared refresh in the background, so a later call can
|
|
160
|
-
* use a refreshed value when it becomes available.
|
|
152
|
+
* Backwards-compatible cache-first helper for inline-card social proof callers.
|
|
153
|
+
*
|
|
154
|
+
* Reads the persisted provider percentage map synchronously using the current site cloud id, then
|
|
155
|
+
* starts a background refresh for subsequent mounts. The async result intentionally does not affect
|
|
156
|
+
* the current call site, matching the warm-cache-only rendering contract.
|
|
161
157
|
*/
|
|
162
158
|
export function getCachedProviderPctMapAndRefresh(traitName) {
|
|
163
|
-
|
|
159
|
+
const cloudId = getCurrentSiteCloudIdSync();
|
|
160
|
+
const providerPctMap = getProviderPctMapSync(cloudId, traitName);
|
|
161
|
+
void getCurrentSiteCloudId().then(resolvedCloudId => {
|
|
162
|
+
void getProviderPctMap(resolvedCloudId, traitName);
|
|
163
|
+
});
|
|
164
|
+
return providerPctMap;
|
|
164
165
|
}
|
|
@@ -2,7 +2,7 @@ export const ANALYTICS_CHANNEL = 'media';
|
|
|
2
2
|
export const context = {
|
|
3
3
|
componentName: 'smart-cards',
|
|
4
4
|
packageName: "@atlaskit/smart-card" || '',
|
|
5
|
-
packageVersion: "44.
|
|
5
|
+
packageVersion: "44.7.1" || ''
|
|
6
6
|
};
|
|
7
7
|
export let TrackQuickActionType = /*#__PURE__*/function (TrackQuickActionType) {
|
|
8
8
|
TrackQuickActionType["StatusUpdate"] = "StatusUpdate";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import { Box, Text } from '@atlaskit/primitives/compiled';
|
|
5
|
+
import { messages } from '../../../messages';
|
|
6
|
+
// TODO: remove when social-proof-3p-unauth-block-fg is cleaned up
|
|
7
|
+
const SocialProofMessage = ({
|
|
8
|
+
tier,
|
|
9
|
+
connectedPct,
|
|
10
|
+
providerName,
|
|
11
|
+
testId = 'smart-block-social-proof-message'
|
|
12
|
+
}) => {
|
|
13
|
+
const message = tier === 'not-low' ? messages.pre_auth_block_social_proof_not_low : messages.pre_auth_block_social_proof_low;
|
|
14
|
+
return /*#__PURE__*/React.createElement(Box, {
|
|
15
|
+
testId: testId
|
|
16
|
+
}, /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, message, {
|
|
17
|
+
values: {
|
|
18
|
+
percentage: connectedPct !== null && connectedPct !== void 0 ? connectedPct : 0,
|
|
19
|
+
provider: providerName,
|
|
20
|
+
b: chunks => /*#__PURE__*/React.createElement(Text, {
|
|
21
|
+
as: "strong",
|
|
22
|
+
weight: "bold"
|
|
23
|
+
}, chunks)
|
|
24
|
+
}
|
|
25
|
+
})));
|
|
26
|
+
};
|
|
27
|
+
export default SocialProofMessage;
|
|
@@ -5,13 +5,18 @@ import * as React from 'react';
|
|
|
5
5
|
import { ax, ix } from "@compiled/react/runtime";
|
|
6
6
|
import { useCallback, useMemo } from 'react';
|
|
7
7
|
import { FormattedMessage } from 'react-intl';
|
|
8
|
+
import { di } from 'react-magnetic-di';
|
|
9
|
+
import { AnalyticsContext } from '@atlaskit/analytics-next';
|
|
8
10
|
import { extractSmartLinkProvider } from '@atlaskit/link-extractors';
|
|
11
|
+
import { useSmartLinkContext } from '@atlaskit/link-provider';
|
|
12
|
+
import { componentWithFG } from '@atlaskit/platform-feature-flags-react';
|
|
9
13
|
import { Box } from '@atlaskit/primitives/compiled';
|
|
10
14
|
import { useAnalyticsEvents } from '../../../common/analytics/generated/use-analytics-events';
|
|
11
15
|
import { ElementName, SmartLinkDirection, SmartLinkSize, SmartLinkWidth } from '../../../constants';
|
|
12
16
|
import { messages } from '../../../messages';
|
|
13
17
|
import { useFlexibleCardContext } from '../../../state/flexible-ui-context';
|
|
14
18
|
import { hasAuthScopeOverrides } from '../../../state/helpers';
|
|
19
|
+
import useSocialProofExperiment from '../../../state/hooks/use-social-proof-experiment';
|
|
15
20
|
import UnauthorisedViewContent from '../../common/UnauthorisedViewContent';
|
|
16
21
|
import FlexibleCard from '../../FlexibleCard';
|
|
17
22
|
import ActionGroup from '../../FlexibleCard/components/blocks/action-group';
|
|
@@ -21,6 +26,7 @@ import { renderElementItems } from '../../FlexibleCard/components/blocks/utils';
|
|
|
21
26
|
import { LinkIcon, Title } from '../../FlexibleCard/components/elements';
|
|
22
27
|
import { AuthorizeAction } from '../actions/AuthorizeAction';
|
|
23
28
|
import unauthIllustrationGeneral from './assets/general@2x.png';
|
|
29
|
+
import SocialProofMessage from './SocialProofMessage';
|
|
24
30
|
import { FlexibleCardUiOptions, titleBlockOptions } from './utils';
|
|
25
31
|
import { withFlexibleUIBlockCardStyle } from './utils/withFlexibleUIBlockCardStyle';
|
|
26
32
|
const contentStyles = null;
|
|
@@ -163,17 +169,15 @@ const UnauthorisedBlock = ({
|
|
|
163
169
|
* @see SmartLinkStatus
|
|
164
170
|
* @see FlexibleCardProps
|
|
165
171
|
*/
|
|
166
|
-
const
|
|
167
|
-
|
|
172
|
+
const UnauthorisedViewFrame = ({
|
|
173
|
+
content,
|
|
174
|
+
providerName,
|
|
175
|
+
testId,
|
|
168
176
|
...props
|
|
169
177
|
}) => {
|
|
170
|
-
var _extractSmartLinkProv;
|
|
171
178
|
const {
|
|
172
|
-
cardState,
|
|
173
179
|
onAuthorize
|
|
174
180
|
} = props;
|
|
175
|
-
const providerName = (_extractSmartLinkProv = extractSmartLinkProvider(cardState.details)) === null || _extractSmartLinkProv === void 0 ? void 0 : _extractSmartLinkProv.text;
|
|
176
|
-
const isProductIntegrationSupported = hasAuthScopeOverrides(cardState.details);
|
|
177
181
|
const {
|
|
178
182
|
fireEvent
|
|
179
183
|
} = useAnalyticsEvents();
|
|
@@ -183,15 +187,6 @@ const UnauthorisedView = ({
|
|
|
183
187
|
onAuthorize();
|
|
184
188
|
}
|
|
185
189
|
}, [onAuthorize, fireEvent]);
|
|
186
|
-
const content = useMemo(() => onAuthorize ? /*#__PURE__*/React.createElement(UnauthorisedViewContent, {
|
|
187
|
-
providerName: providerName,
|
|
188
|
-
isProductIntegrationSupported: isProductIntegrationSupported,
|
|
189
|
-
testId: testId
|
|
190
|
-
}) : /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, messages[providerName ? 'unauthorised_account_description' : 'unauthorised_account_description_no_provider'], {
|
|
191
|
-
values: {
|
|
192
|
-
context: providerName
|
|
193
|
-
}
|
|
194
|
-
})), [isProductIntegrationSupported, onAuthorize, providerName, testId]);
|
|
195
190
|
const actions = useMemo(() => onAuthorize ? [AuthorizeAction(handleAuthorize, providerName)] : [], [handleAuthorize, onAuthorize, providerName]);
|
|
196
191
|
return /*#__PURE__*/React.createElement(FlexibleCard, _extends({
|
|
197
192
|
appearance: "block",
|
|
@@ -210,5 +205,78 @@ const UnauthorisedView = ({
|
|
|
210
205
|
className: ax(["_11c8fhey _syazi7uo _19pku2gc", actions.length > 0 && "_1reo15vq _18m915vq _otyrpxbi _p12f1osq _o5721q9c _1bto1l2s"])
|
|
211
206
|
}, content)));
|
|
212
207
|
};
|
|
208
|
+
const UnauthorisedViewBase = ({
|
|
209
|
+
testId = 'smart-block-unauthorized-view',
|
|
210
|
+
...props
|
|
211
|
+
}) => {
|
|
212
|
+
var _extractSmartLinkProv;
|
|
213
|
+
const {
|
|
214
|
+
cardState,
|
|
215
|
+
onAuthorize
|
|
216
|
+
} = props;
|
|
217
|
+
const providerName = (_extractSmartLinkProv = extractSmartLinkProvider(cardState.details)) === null || _extractSmartLinkProv === void 0 ? void 0 : _extractSmartLinkProv.text;
|
|
218
|
+
const isProductIntegrationSupported = hasAuthScopeOverrides(cardState.details);
|
|
219
|
+
const content = useMemo(() => onAuthorize ? /*#__PURE__*/React.createElement(UnauthorisedViewContent, {
|
|
220
|
+
providerName: providerName,
|
|
221
|
+
isProductIntegrationSupported: isProductIntegrationSupported,
|
|
222
|
+
testId: testId
|
|
223
|
+
}) : /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, messages[providerName ? 'unauthorised_account_description' : 'unauthorised_account_description_no_provider'], {
|
|
224
|
+
values: {
|
|
225
|
+
context: providerName
|
|
226
|
+
}
|
|
227
|
+
})), [isProductIntegrationSupported, onAuthorize, providerName, testId]);
|
|
228
|
+
return /*#__PURE__*/React.createElement(UnauthorisedViewFrame, _extends({}, props, {
|
|
229
|
+
content: content,
|
|
230
|
+
providerName: providerName,
|
|
231
|
+
testId: testId
|
|
232
|
+
}));
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Experiment wrapper: fires social proof exposure and renders social proof UI when FG is on.
|
|
237
|
+
* TODO: remove when social-proof-3p-unauth-block-fg is cleaned up
|
|
238
|
+
*/
|
|
239
|
+
const UnauthorisedViewWithExperiment = ({
|
|
240
|
+
testId = 'smart-block-unauthorized-view',
|
|
241
|
+
...props
|
|
242
|
+
}) => {
|
|
243
|
+
var _props$cardState, _props$cardState$deta, _props$cardState$deta2, _extractSmartLinkProv2;
|
|
244
|
+
const extensionKey = (_props$cardState = props.cardState) === null || _props$cardState === void 0 ? void 0 : (_props$cardState$deta = _props$cardState.details) === null || _props$cardState$deta === void 0 ? void 0 : (_props$cardState$deta2 = _props$cardState$deta.meta) === null || _props$cardState$deta2 === void 0 ? void 0 : _props$cardState$deta2.key;
|
|
245
|
+
const providerName = (_extractSmartLinkProv2 = extractSmartLinkProvider(props.cardState.details)) === null || _extractSmartLinkProv2 === void 0 ? void 0 : _extractSmartLinkProv2.text;
|
|
246
|
+
const {
|
|
247
|
+
connections
|
|
248
|
+
} = useSmartLinkContext();
|
|
249
|
+
const {
|
|
250
|
+
isTreatment,
|
|
251
|
+
tier,
|
|
252
|
+
connectedPct
|
|
253
|
+
} = useSocialProofExperiment(providerName ? extensionKey : undefined, connections.client.baseUrlOverride);
|
|
254
|
+
if (!isTreatment || !providerName) {
|
|
255
|
+
return /*#__PURE__*/React.createElement(UnauthorisedViewBase, _extends({}, props, {
|
|
256
|
+
testId: testId
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
return /*#__PURE__*/React.createElement(AnalyticsContext, {
|
|
260
|
+
data: {
|
|
261
|
+
attributes: {
|
|
262
|
+
experiment: 'social_proof_3p_unauth_block_exp',
|
|
263
|
+
cohort: 'treatment',
|
|
264
|
+
tier
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}, /*#__PURE__*/React.createElement(UnauthorisedViewFrame, _extends({}, props, {
|
|
268
|
+
content: /*#__PURE__*/React.createElement(SocialProofMessage, {
|
|
269
|
+
tier: tier,
|
|
270
|
+
connectedPct: connectedPct,
|
|
271
|
+
providerName: providerName
|
|
272
|
+
}),
|
|
273
|
+
providerName: providerName,
|
|
274
|
+
testId: testId
|
|
275
|
+
})));
|
|
276
|
+
};
|
|
277
|
+
const UnauthorisedViewWithoutExperiment = props => {
|
|
278
|
+
return /*#__PURE__*/React.createElement(UnauthorisedViewBase, props);
|
|
279
|
+
};
|
|
280
|
+
const UnauthorisedView = componentWithFG('social-proof-3p-unauth-block-fg', UnauthorisedViewWithExperiment, UnauthorisedViewWithoutExperiment);
|
|
213
281
|
const _default_1 = withFlexibleUIBlockCardStyle(UnauthorisedView);
|
|
214
282
|
export default _default_1;
|
|
@@ -12,7 +12,7 @@ import LinkWarningModal from './LinkWarningModal';
|
|
|
12
12
|
import { useLinkWarningModal } from './LinkWarningModal/hooks/use-link-warning-modal';
|
|
13
13
|
const PACKAGE_DATA = {
|
|
14
14
|
packageName: "@atlaskit/smart-card",
|
|
15
|
-
packageVersion: "44.
|
|
15
|
+
packageVersion: "44.7.1",
|
|
16
16
|
componentName: 'linkUrl'
|
|
17
17
|
};
|
|
18
18
|
const Anchor = withLinkClickedEvent('a');
|
package/dist/esm/messages.js
CHANGED
|
@@ -988,7 +988,7 @@ export var messages = defineMessages({
|
|
|
988
988
|
},
|
|
989
989
|
rovo_prompt_message_highlight_relevant_content: {
|
|
990
990
|
id: 'fabric.linking.rovo_prompt_message_highlight_relevant_content.non-final',
|
|
991
|
-
defaultMessage:
|
|
991
|
+
defaultMessage: "<p>Based on this linked item (<a>{url}</a>) and the {context} I'm currently viewing, highlight the parts of the linked content that are most relevant to this work. Explain briefly why each part is relevant.</p>",
|
|
992
992
|
description: 'The prompt message to send to Rovo Chat. {context} refers to the content the user triggered from, e.g. Confluence page or Jira work item. {url} refers to Smart Link that the user triggers this action from. (Please make sure all html tags remain the same.)'
|
|
993
993
|
},
|
|
994
994
|
rovo_prompt_button_identify_key_trends: {
|
|
@@ -1050,5 +1050,16 @@ export var messages = defineMessages({
|
|
|
1050
1050
|
id: 'fabric.linking.rovo_prompt_button_show_me_whats_relevant.non-final',
|
|
1051
1051
|
defaultMessage: "Show me what's relevant",
|
|
1052
1052
|
description: 'The name of the action to send prompt message to Rovo Chat in relation to current Smart Link'
|
|
1053
|
+
},
|
|
1054
|
+
// TODO: remove when social-proof-3p-unauth-block-fg is cleaned up
|
|
1055
|
+
pre_auth_block_social_proof_not_low: {
|
|
1056
|
+
id: 'fabric.linking.pre_auth_block_social_proof_not_low',
|
|
1057
|
+
defaultMessage: '<b>{percentage}%</b> of your team is previewing <b>{provider}</b>.',
|
|
1058
|
+
description: 'Social proof message shown on unauthorized 3P block cards when 30% or more of the tenant has connected the provider. {percentage} is a number, {provider} is the 3P app name (e.g. OneDrive).'
|
|
1059
|
+
},
|
|
1060
|
+
pre_auth_block_social_proof_low: {
|
|
1061
|
+
id: 'fabric.linking.pre_auth_block_social_proof_low',
|
|
1062
|
+
defaultMessage: 'Your team is previewing <b>{provider}</b>.',
|
|
1063
|
+
description: 'Social proof message shown on unauthorized 3P block cards when less than 30% of the tenant has connected the provider. {provider} is the 3P app name (e.g. OneDrive).'
|
|
1053
1064
|
}
|
|
1054
1065
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { getCurrentSiteCloudId } from '../../services/current-site-cloud-id';
|
|
4
|
-
export { CURRENT_SITE_CLOUD_ID_LOCAL_STORAGE_KEY, CURRENT_SITE_CLOUD_ID_STORAGE_ITEM_KEY, currentSiteCloudIdService, getCurrentSiteCloudId, getCachedCurrentSiteCloudIdAndRefresh } from '../../services/current-site-cloud-id';
|
|
5
4
|
var useCurrentSiteCloudId = function useCurrentSiteCloudId() {
|
|
6
5
|
var _useState = useState(undefined),
|
|
7
6
|
_useState2 = _slicedToArray(_useState, 2),
|
|
@@ -3,7 +3,7 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
|
3
3
|
import { useCallback, useMemo } from 'react';
|
|
4
4
|
import { request } from '@atlaskit/linking-common';
|
|
5
5
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
6
|
-
import { getCurrentSiteCloudId } from '
|
|
6
|
+
import { getCurrentSiteCloudId } from '../../services/current-site-cloud-id';
|
|
7
7
|
import { queryIncomingOutgoingLinks as queryIncomingOutgoingAris } from './query';
|
|
8
8
|
/**
|
|
9
9
|
* @param baseUriWithNoTrailingSlash base url which will then be appended with /gateway/api/graphql to make requests to AGG
|
|
@@ -72,7 +72,7 @@ var useIncomingOutgoingAri = function useIncomingOutgoingAri() {
|
|
|
72
72
|
}
|
|
73
73
|
return _context3.abrupt("return", match[2]);
|
|
74
74
|
case 4:
|
|
75
|
-
if (!fg('
|
|
75
|
+
if (!fg('platform_sl_incoming_outgoing_tenant_info_killswitch')) {
|
|
76
76
|
_context3.next = 10;
|
|
77
77
|
break;
|
|
78
78
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
2
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { getCurrentSiteCloudId, getCurrentSiteCloudIdSync } from '../../services/current-site-cloud-id';
|
|
4
|
+
import { getProviderPctMap, getProviderPctMapSync, SOCIAL_PROOF_TRAIT_NAME } from '../../services/personalization';
|
|
4
5
|
var NOT_ENABLED_RESULT = {
|
|
5
6
|
connectedPct: undefined,
|
|
6
7
|
isEnabled: false,
|
|
@@ -8,46 +9,61 @@ var NOT_ENABLED_RESULT = {
|
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Cache-first social proof hook.
|
|
13
|
+
*
|
|
14
|
+
* On mount:
|
|
15
|
+
* 1. Reads localStorage synchronously via `getProviderPctMapSync`.
|
|
16
|
+
* - If data exists (warm cache): sets `providerPctMap` immediately, `isLoading = false`.
|
|
17
|
+
* - If no data (cold cache): leaves `providerPctMap` undefined, `isLoading = false`.
|
|
18
|
+
* 2. Always kicks off an async fetch (fire-and-forget) via `getProviderPctMap` to populate
|
|
19
|
+
* localStorage for next page load. Does NOT update state with the async result.
|
|
20
|
+
*
|
|
21
|
+
* This means:
|
|
22
|
+
* - First page visit (cold): no social proof rendered, no experiment exposure fired.
|
|
23
|
+
* Background fetch populates localStorage for next time.
|
|
24
|
+
* - Second page visit (warm): social proof renders immediately from localStorage.
|
|
25
|
+
* Background refresh keeps localStorage fresh.
|
|
26
|
+
*
|
|
27
|
+
* Callers decide separately (e.g. via Statsig experiment) whether to surface the data.
|
|
14
28
|
*/
|
|
15
|
-
var useSocialProof = function useSocialProof(
|
|
16
|
-
var isKillswitchOn = arguments.length >
|
|
29
|
+
var useSocialProof = function useSocialProof(extensionKey) {
|
|
30
|
+
var isKillswitchOn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
31
|
+
var baseUriWithNoTrailingSlash = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
|
|
17
32
|
var isEnabled = isKillswitchOn;
|
|
18
|
-
var _useState = useState(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
var _useState = useState(function () {
|
|
34
|
+
if (!isEnabled) {
|
|
35
|
+
return {
|
|
36
|
+
cloudId: undefined,
|
|
37
|
+
providerPctMap: null
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
var cloudId = getCurrentSiteCloudIdSync(baseUriWithNoTrailingSlash);
|
|
41
|
+
return {
|
|
42
|
+
cloudId: cloudId,
|
|
43
|
+
providerPctMap: getProviderPctMapSync(cloudId, SOCIAL_PROOF_TRAIT_NAME)
|
|
44
|
+
};
|
|
45
|
+
}),
|
|
46
|
+
_useState2 = _slicedToArray(_useState, 1),
|
|
47
|
+
snapshot = _useState2[0];
|
|
48
|
+
|
|
49
|
+
// Fire-and-forget: warm caches for future mounts only. Never update this mount's treatment UI.
|
|
26
50
|
useEffect(function () {
|
|
27
51
|
if (!isEnabled) {
|
|
28
52
|
return;
|
|
29
53
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
getProviderPctMap(traitName).then(function (pctMap) {
|
|
33
|
-
if (!cancelled) {
|
|
34
|
-
setProviderPctMap(pctMap);
|
|
35
|
-
setIsPersonalizationLoading(false);
|
|
36
|
-
}
|
|
54
|
+
void getCurrentSiteCloudId(baseUriWithNoTrailingSlash).then(function (cloudId) {
|
|
55
|
+
void getProviderPctMap(cloudId, SOCIAL_PROOF_TRAIT_NAME);
|
|
37
56
|
});
|
|
38
|
-
|
|
39
|
-
cancelled = true;
|
|
40
|
-
};
|
|
41
|
-
}, [isEnabled, traitName]);
|
|
57
|
+
}, [baseUriWithNoTrailingSlash, isEnabled]);
|
|
42
58
|
return useMemo(function () {
|
|
43
59
|
if (!isEnabled) {
|
|
44
60
|
return NOT_ENABLED_RESULT;
|
|
45
61
|
}
|
|
46
62
|
return {
|
|
47
|
-
connectedPct: extensionKey && providerPctMap ? providerPctMap[extensionKey] : undefined,
|
|
48
|
-
isEnabled:
|
|
49
|
-
isLoading:
|
|
63
|
+
connectedPct: extensionKey && snapshot.providerPctMap ? snapshot.providerPctMap[extensionKey] : undefined,
|
|
64
|
+
isEnabled: Boolean(snapshot.cloudId && snapshot.providerPctMap),
|
|
65
|
+
isLoading: false // sync read is instant; never in "loading" state
|
|
50
66
|
};
|
|
51
|
-
}, [extensionKey, isEnabled,
|
|
67
|
+
}, [extensionKey, isEnabled, snapshot]);
|
|
52
68
|
};
|
|
53
69
|
export default useSocialProof;
|