@backstage/plugin-user-settings 0.8.32-next.1 → 0.9.0-next.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 +19 -0
- package/README.md +1 -1
- package/dist/alpha.d.ts +2 -2
- package/dist/apis/StorageApi/CacheMap.esm.js +35 -0
- package/dist/apis/StorageApi/CacheMap.esm.js.map +1 -0
- package/dist/apis/StorageApi/UserSettingsStorage.esm.js +85 -22
- package/dist/apis/StorageApi/UserSettingsStorage.esm.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/package.json.esm.js +4 -3
- package/dist/package.json.esm.js.map +1 -1
- package/package.json +16 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @backstage/plugin-user-settings
|
|
2
2
|
|
|
3
|
+
## 0.9.0-next.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 104ca74: User-settings will now use DataLoader to batch consecutive calls into one API call to improve performance
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components.
|
|
12
|
+
- Updated dependencies
|
|
13
|
+
- @backstage/plugin-catalog-react@2.0.0-next.2
|
|
14
|
+
- @backstage/core-app-api@1.19.5-next.1
|
|
15
|
+
- @backstage/plugin-user-settings-common@0.1.0-next.0
|
|
16
|
+
- @backstage/frontend-plugin-api@0.14.0-next.2
|
|
17
|
+
- @backstage/core-components@0.18.7-next.2
|
|
18
|
+
- @backstage/core-plugin-api@1.12.3-next.1
|
|
19
|
+
- @backstage/plugin-signals-react@0.0.19-next.1
|
|
20
|
+
- @backstage/theme@0.7.2-next.1
|
|
21
|
+
|
|
3
22
|
## 0.8.32-next.1
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ import {
|
|
|
90
90
|
To standardize the UI of all setting tabs,
|
|
91
91
|
make sure you use a similar component structure as the other tabs.
|
|
92
92
|
You can take a look at
|
|
93
|
-
[the example extra tab](https://github.com/backstage/backstage/blob/master/packages/app/src/components/advancedSettings/AdvancedSettings.tsx)
|
|
93
|
+
[the example extra tab](https://github.com/backstage/backstage/blob/master/packages/app-legacy/src/components/advancedSettings/AdvancedSettings.tsx)
|
|
94
94
|
we have created in Backstage's example app.
|
|
95
95
|
|
|
96
96
|
To change the layout altogether, create a custom page in `packages/app/src/components/user-settings/SettingsPage.tsx`:
|
package/dist/alpha.d.ts
CHANGED
|
@@ -42,10 +42,10 @@ declare const userSettingsTranslationRef: _backstage_frontend_plugin_api.Transla
|
|
|
42
42
|
readonly "emptyProviders.action.title": "Open app-config.yaml and make the changes as highlighted below:";
|
|
43
43
|
readonly "emptyProviders.action.readMoreButtonTitle": "Read More";
|
|
44
44
|
readonly "emptyProviders.description": "You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself.";
|
|
45
|
-
readonly "providerSettingsItem.title.signIn": "Sign in to {{title}}";
|
|
46
45
|
readonly "providerSettingsItem.title.signOut": "Sign out from {{title}}";
|
|
47
|
-
readonly "providerSettingsItem.
|
|
46
|
+
readonly "providerSettingsItem.title.signIn": "Sign in to {{title}}";
|
|
48
47
|
readonly "providerSettingsItem.buttonTitle.signOut": "Sign out";
|
|
48
|
+
readonly "providerSettingsItem.buttonTitle.signIn": "Sign in";
|
|
49
49
|
readonly "authProviders.title": "Available Providers";
|
|
50
50
|
readonly "defaultSettingsPage.tabsTitle.featureFlags": "Feature Flags";
|
|
51
51
|
readonly "defaultSettingsPage.tabsTitle.authProviders": "Authentication Providers";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class CacheMap extends Map {
|
|
2
|
+
#ttlMs;
|
|
3
|
+
#timestamps = /* @__PURE__ */ new Map();
|
|
4
|
+
constructor(ttlMs) {
|
|
5
|
+
super();
|
|
6
|
+
this.#ttlMs = ttlMs;
|
|
7
|
+
}
|
|
8
|
+
set(key, value) {
|
|
9
|
+
const result = super.set(key, value);
|
|
10
|
+
this.#timestamps.set(key, Date.now());
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
get(key) {
|
|
14
|
+
if (!this.has(key)) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
const timestamp = this.#timestamps.get(key);
|
|
18
|
+
if (Date.now() - timestamp > this.#ttlMs) {
|
|
19
|
+
this.delete(key);
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
return super.get(key);
|
|
23
|
+
}
|
|
24
|
+
delete(key) {
|
|
25
|
+
this.#timestamps.delete(key);
|
|
26
|
+
return super.delete(key);
|
|
27
|
+
}
|
|
28
|
+
clear() {
|
|
29
|
+
this.#timestamps.clear();
|
|
30
|
+
return super.clear();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { CacheMap };
|
|
35
|
+
//# sourceMappingURL=CacheMap.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CacheMap.esm.js","sources":["../../../src/apis/StorageApi/CacheMap.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A Map<K, V> with TTL (time-to-live) support, for the Dataloader of user settings.\n */\nexport class CacheMap<K, V> extends Map<K, V> {\n #ttlMs: number;\n #timestamps: Map<K, number> = new Map();\n\n constructor(ttlMs: number) {\n super();\n this.#ttlMs = ttlMs;\n }\n\n set(key: K, value: V) {\n const result = super.set(key, value);\n this.#timestamps.set(key, Date.now());\n return result;\n }\n\n get(key: K) {\n if (!this.has(key)) {\n return undefined;\n }\n const timestamp = this.#timestamps.get(key)!;\n if (Date.now() - timestamp > this.#ttlMs) {\n this.delete(key);\n return undefined;\n }\n return super.get(key);\n }\n\n delete(key: K) {\n this.#timestamps.delete(key);\n return super.delete(key);\n }\n\n clear() {\n this.#timestamps.clear();\n return super.clear();\n }\n}\n"],"names":[],"mappings":"AAmBO,MAAM,iBAAuB,GAAA,CAAU;AAAA,EAC5C,MAAA;AAAA,EACA,WAAA,uBAAkC,GAAA,EAAI;AAAA,EAEtC,YAAY,KAAA,EAAe;AACzB,IAAA,KAAA,EAAM;AACN,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AAAA,EAChB;AAAA,EAEA,GAAA,CAAI,KAAQ,KAAA,EAAU;AACpB,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AACnC,IAAA,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,KAAK,CAAA;AACpC,IAAA,OAAO,MAAA;AAAA,EACT;AAAA,EAEA,IAAI,GAAA,EAAQ;AACV,IAAA,IAAI,CAAC,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG;AAClB,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA;AAC1C,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,GAAY,KAAK,MAAA,EAAQ;AACxC,MAAA,IAAA,CAAK,OAAO,GAAG,CAAA;AACf,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,KAAA,CAAM,IAAI,GAAG,CAAA;AAAA,EACtB;AAAA,EAEA,OAAO,GAAA,EAAQ;AACb,IAAA,IAAA,CAAK,WAAA,CAAY,OAAO,GAAG,CAAA;AAC3B,IAAA,OAAO,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,EACzB;AAAA,EAEA,KAAA,GAAQ;AACN,IAAA,IAAA,CAAK,YAAY,KAAA,EAAM;AACvB,IAAA,OAAO,MAAM,KAAA,EAAM;AAAA,EACrB;AACF;;;;"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { WebStorage } from '@backstage/core-app-api';
|
|
2
2
|
import { ResponseError } from '@backstage/errors';
|
|
3
3
|
import ObservableImpl from 'zen-observable';
|
|
4
|
+
import DataLoader from 'dataloader';
|
|
5
|
+
import { CacheMap } from './CacheMap.esm.js';
|
|
4
6
|
|
|
5
7
|
const JSON_HEADERS = {
|
|
6
8
|
"Content-Type": "application/json; charset=utf-8",
|
|
7
9
|
Accept: "application/json"
|
|
8
10
|
};
|
|
9
11
|
const buckets = /* @__PURE__ */ new Map();
|
|
12
|
+
const DATALOADER_CACHE_TTL_MS = 2 * 1e3;
|
|
13
|
+
const DATALOADER_WINDOW_MS = 10;
|
|
10
14
|
class UserSettingsStorage {
|
|
11
15
|
subscribers = /* @__PURE__ */ new Set();
|
|
12
16
|
observables = /* @__PURE__ */ new Map();
|
|
@@ -17,7 +21,8 @@ class UserSettingsStorage {
|
|
|
17
21
|
identityApi;
|
|
18
22
|
fallback;
|
|
19
23
|
signalApi;
|
|
20
|
-
|
|
24
|
+
userSettingsLoader;
|
|
25
|
+
constructor(namespace, fetchApi, discoveryApi, errorApi, identityApi, fallback, signalApi, userSettingsLoader) {
|
|
21
26
|
this.namespace = namespace;
|
|
22
27
|
this.fetchApi = fetchApi;
|
|
23
28
|
this.discoveryApi = discoveryApi;
|
|
@@ -25,6 +30,25 @@ class UserSettingsStorage {
|
|
|
25
30
|
this.identityApi = identityApi;
|
|
26
31
|
this.fallback = fallback;
|
|
27
32
|
this.signalApi = signalApi;
|
|
33
|
+
this.userSettingsLoader = userSettingsLoader ?? new DataLoader(
|
|
34
|
+
async (bucketAndKeyList) => this.getMulti(bucketAndKeyList),
|
|
35
|
+
{
|
|
36
|
+
name: "UserSettingsStorage.userSettingsLoader",
|
|
37
|
+
cacheMap: new CacheMap(DATALOADER_CACHE_TTL_MS),
|
|
38
|
+
cacheKeyFn: (bucketAndKey) => this.stringifyDataLoaderKey(bucketAndKey),
|
|
39
|
+
maxBatchSize: 100,
|
|
40
|
+
batchScheduleFn: (cb) => setTimeout(cb, DATALOADER_WINDOW_MS)
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
stringifyDataLoaderKey({
|
|
45
|
+
bucket,
|
|
46
|
+
key
|
|
47
|
+
}) {
|
|
48
|
+
return `${encodeURIComponent(bucket)}/${encodeURIComponent(key)}`;
|
|
49
|
+
}
|
|
50
|
+
clearCacheKey(key) {
|
|
51
|
+
this.userSettingsLoader.clear({ bucket: this.namespace, key });
|
|
28
52
|
}
|
|
29
53
|
static create(options) {
|
|
30
54
|
return new UserSettingsStorage(
|
|
@@ -51,7 +75,9 @@ class UserSettingsStorage {
|
|
|
51
75
|
this.discoveryApi,
|
|
52
76
|
this.errorApi,
|
|
53
77
|
this.identityApi,
|
|
54
|
-
this.fallback
|
|
78
|
+
this.fallback,
|
|
79
|
+
this.signalApi,
|
|
80
|
+
this.userSettingsLoader
|
|
55
81
|
)
|
|
56
82
|
);
|
|
57
83
|
}
|
|
@@ -65,14 +91,17 @@ class UserSettingsStorage {
|
|
|
65
91
|
if (!response.ok && response.status !== 404) {
|
|
66
92
|
throw await ResponseError.fromResponse(response);
|
|
67
93
|
}
|
|
94
|
+
this.clearCacheKey(key);
|
|
68
95
|
this.notifyChanges({ key, presence: "absent" });
|
|
69
96
|
}
|
|
70
97
|
async set(key, data) {
|
|
71
98
|
if (!await this.isSignedIn()) {
|
|
72
99
|
await this.fallback.set(key, data);
|
|
73
100
|
this.notifyChanges({ key, presence: "present", value: data });
|
|
101
|
+
this.clearCacheKey(key);
|
|
74
102
|
return;
|
|
75
103
|
}
|
|
104
|
+
this.clearCacheKey(key);
|
|
76
105
|
const fetchUrl = await this.getFetchUrl(key);
|
|
77
106
|
const response = await this.fetchApi.fetch(fetchUrl, {
|
|
78
107
|
method: "PUT",
|
|
@@ -83,6 +112,14 @@ class UserSettingsStorage {
|
|
|
83
112
|
throw await ResponseError.fromResponse(response);
|
|
84
113
|
}
|
|
85
114
|
const { value } = await response.json();
|
|
115
|
+
this.userSettingsLoader.prime(
|
|
116
|
+
{ bucket: this.namespace, key },
|
|
117
|
+
{
|
|
118
|
+
key,
|
|
119
|
+
presence: "present",
|
|
120
|
+
value
|
|
121
|
+
}
|
|
122
|
+
);
|
|
86
123
|
this.notifyChanges({ key, value, presence: "present" });
|
|
87
124
|
}
|
|
88
125
|
observe$(key) {
|
|
@@ -93,7 +130,9 @@ class UserSettingsStorage {
|
|
|
93
130
|
let signalSubscription;
|
|
94
131
|
this.subscribers.add(subscriber);
|
|
95
132
|
const updateSnapshot = () => {
|
|
96
|
-
Promise.resolve().then(
|
|
133
|
+
Promise.resolve().then(
|
|
134
|
+
() => this.userSettingsLoader.load({ bucket: this.namespace, key })
|
|
135
|
+
).then((snapshot) => subscriber.next(snapshot)).catch((error) => this.errorApi.post(error));
|
|
97
136
|
};
|
|
98
137
|
if (this.signalApi) {
|
|
99
138
|
signalSubscription = this.signalApi.subscribe(
|
|
@@ -120,29 +159,53 @@ class UserSettingsStorage {
|
|
|
120
159
|
snapshot(key) {
|
|
121
160
|
return { key, presence: "unknown" };
|
|
122
161
|
}
|
|
123
|
-
async
|
|
162
|
+
async getMulti(bucketAndKeyList) {
|
|
163
|
+
if (bucketAndKeyList.length === 0) return [];
|
|
124
164
|
if (!await this.isSignedIn()) {
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const response = await this.fetchApi.fetch(fetchUrl);
|
|
129
|
-
if (response.status === 404) {
|
|
130
|
-
return { key, presence: "absent" };
|
|
131
|
-
}
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
throw await ResponseError.fromResponse(response);
|
|
165
|
+
return bucketAndKeyList.map(
|
|
166
|
+
(bucketAndKey) => this.fallback.snapshot(bucketAndKey.key)
|
|
167
|
+
);
|
|
134
168
|
}
|
|
135
169
|
try {
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
return val;
|
|
170
|
+
const baseUrl = await this.discoveryApi.getBaseUrl("user-settings");
|
|
171
|
+
const response = await this.fetchApi.fetch(`${baseUrl}/multiget`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: JSON_HEADERS,
|
|
174
|
+
body: JSON.stringify({ items: bucketAndKeyList })
|
|
142
175
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
176
|
+
if (response.status === 404) {
|
|
177
|
+
return bucketAndKeyList.map((bucketAndKey) => ({
|
|
178
|
+
key: bucketAndKey.key,
|
|
179
|
+
presence: "absent"
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
throw await ResponseError.fromResponse(response);
|
|
184
|
+
}
|
|
185
|
+
const { items: values } = await response.json();
|
|
186
|
+
return bucketAndKeyList.map(
|
|
187
|
+
({ key }, i) => {
|
|
188
|
+
if (!values[i]) {
|
|
189
|
+
return { key, presence: "absent" };
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
key,
|
|
193
|
+
presence: "present",
|
|
194
|
+
value: JSON.parse(JSON.stringify(values[i].value), (_key, val) => {
|
|
195
|
+
if (typeof val === "object" && val !== null) {
|
|
196
|
+
Object.freeze(val);
|
|
197
|
+
}
|
|
198
|
+
return val;
|
|
199
|
+
})
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
} catch (e) {
|
|
204
|
+
this.errorApi.post(new Error(`Failed to fetch user settings, ${e}`));
|
|
205
|
+
return bucketAndKeyList.map((bucketAndKey) => ({
|
|
206
|
+
key: bucketAndKey.key,
|
|
207
|
+
presence: "absent"
|
|
208
|
+
}));
|
|
146
209
|
}
|
|
147
210
|
}
|
|
148
211
|
async getFetchUrl(key) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserSettingsStorage.esm.js","sources":["../../../src/apis/StorageApi/UserSettingsStorage.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebStorage } from '@backstage/core-app-api';\nimport {\n DiscoveryApi,\n ErrorApi,\n FetchApi,\n IdentityApi,\n StorageApi,\n StorageValueSnapshot,\n} from '@backstage/core-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport { JsonValue, Observable } from '@backstage/types';\nimport { SignalApi, SignalSubscriber } from '@backstage/plugin-signals-react';\nimport ObservableImpl from 'zen-observable';\nimport { UserSettingsSignal } from '@backstage/plugin-user-settings-common';\n\nconst JSON_HEADERS = {\n 'Content-Type': 'application/json; charset=utf-8',\n Accept: 'application/json',\n};\n\nconst buckets = new Map<string, UserSettingsStorage>();\n\n/**\n * An implementation of the storage API, that uses the user-settings backend to\n * persist the data in the DB.\n *\n * @public\n */\nexport class UserSettingsStorage implements StorageApi {\n private subscribers = new Set<\n ZenObservable.SubscriptionObserver<StorageValueSnapshot<JsonValue>>\n >();\n\n private readonly observables = new Map<\n string,\n Observable<StorageValueSnapshot<JsonValue>>\n >();\n\n private readonly namespace: string;\n private readonly fetchApi: FetchApi;\n private readonly discoveryApi: DiscoveryApi;\n private readonly errorApi: ErrorApi;\n private readonly identityApi: IdentityApi;\n private readonly fallback: WebStorage;\n private readonly signalApi?: SignalApi;\n\n private constructor(\n namespace: string,\n fetchApi: FetchApi,\n discoveryApi: DiscoveryApi,\n errorApi: ErrorApi,\n identityApi: IdentityApi,\n fallback: WebStorage,\n signalApi?: SignalApi,\n ) {\n this.namespace = namespace;\n this.fetchApi = fetchApi;\n this.discoveryApi = discoveryApi;\n this.errorApi = errorApi;\n this.identityApi = identityApi;\n this.fallback = fallback;\n this.signalApi = signalApi;\n }\n\n static create(options: {\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n errorApi: ErrorApi;\n identityApi: IdentityApi;\n signalApi?: SignalApi;\n namespace?: string;\n }): UserSettingsStorage {\n return new UserSettingsStorage(\n options.namespace ?? 'default',\n options.fetchApi,\n options.discoveryApi,\n options.errorApi,\n options.identityApi,\n WebStorage.create({\n namespace: options.namespace,\n errorApi: options.errorApi,\n }),\n options.signalApi,\n );\n }\n\n forBucket(name: string): StorageApi {\n // use dot instead of slash separator to have nicer URLs\n const bucketPath = `${this.namespace}.${name}`;\n\n if (!buckets.has(bucketPath)) {\n buckets.set(\n bucketPath,\n new UserSettingsStorage(\n bucketPath,\n this.fetchApi,\n this.discoveryApi,\n this.errorApi,\n this.identityApi,\n this.fallback,\n ),\n );\n }\n\n return buckets.get(bucketPath)!;\n }\n\n async remove(key: string): Promise<void> {\n const fetchUrl = await this.getFetchUrl(key);\n\n const response = await this.fetchApi.fetch(fetchUrl, {\n method: 'DELETE',\n });\n\n if (!response.ok && response.status !== 404) {\n throw await ResponseError.fromResponse(response);\n }\n\n this.notifyChanges({ key, presence: 'absent' });\n }\n\n async set<T extends JsonValue>(key: string, data: T): Promise<void> {\n if (!(await this.isSignedIn())) {\n await this.fallback.set(key, data);\n this.notifyChanges({ key, presence: 'present', value: data });\n return;\n }\n\n const fetchUrl = await this.getFetchUrl(key);\n\n const response = await this.fetchApi.fetch(fetchUrl, {\n method: 'PUT',\n headers: JSON_HEADERS,\n body: JSON.stringify({ value: data }),\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const { value } = await response.json();\n\n this.notifyChanges({ key, value, presence: 'present' });\n }\n\n observe$<T extends JsonValue>(\n key: string,\n ): Observable<StorageValueSnapshot<T>> {\n if (!this.observables.has(key)) {\n this.observables.set(\n key,\n new ObservableImpl<StorageValueSnapshot<JsonValue>>(subscriber => {\n let signalSubscription: SignalSubscriber | undefined;\n this.subscribers.add(subscriber);\n\n const updateSnapshot = () => {\n Promise.resolve()\n .then(() => this.get(key))\n .then(snapshot => subscriber.next(snapshot))\n .catch(error => this.errorApi.post(error));\n };\n\n if (this.signalApi) {\n signalSubscription = this.signalApi.subscribe(\n `user-settings`,\n (msg: UserSettingsSignal) => {\n if (msg.key === key) {\n updateSnapshot();\n }\n },\n );\n }\n\n updateSnapshot();\n\n return () => {\n if (signalSubscription) {\n signalSubscription.unsubscribe();\n }\n this.subscribers.delete(subscriber);\n };\n }).filter(({ key: messageKey }) => messageKey === key),\n );\n }\n\n return this.observables.get(key) as Observable<StorageValueSnapshot<T>>;\n }\n\n snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T> {\n return { key, presence: 'unknown' };\n }\n\n private async get<T extends JsonValue>(\n key: string,\n ): Promise<StorageValueSnapshot<T>> {\n if (!(await this.isSignedIn())) {\n // This explicitly uses WebStorage, which we know is synchronous and doesn't return presence: unknown\n return this.fallback.snapshot(key);\n }\n\n const fetchUrl = await this.getFetchUrl(key);\n const response = await this.fetchApi.fetch(fetchUrl);\n\n if (response.status === 404) {\n return { key, presence: 'absent' };\n }\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n try {\n const { value: rawValue } = await response.json();\n const value = JSON.parse(JSON.stringify(rawValue), (_key, val) => {\n if (typeof val === 'object' && val !== null) {\n Object.freeze(val);\n }\n return val;\n });\n\n return { key, presence: 'present', value };\n } catch {\n // If the value is not valid JSON, we return an unknown presence. This should never happen\n return { key, presence: 'absent' };\n }\n }\n\n private async getFetchUrl(key: string) {\n const baseUrl = await this.discoveryApi.getBaseUrl('user-settings');\n const encodedNamespace = encodeURIComponent(this.namespace);\n const encodedKey = encodeURIComponent(key);\n return `${baseUrl}/buckets/${encodedNamespace}/keys/${encodedKey}`;\n }\n\n private async notifyChanges<T extends JsonValue>(\n snapshot: StorageValueSnapshot<T>,\n ) {\n for (const subscription of this.subscribers) {\n try {\n subscription.next(snapshot);\n } catch {\n // ignore\n }\n }\n }\n\n private async isSignedIn(): Promise<boolean> {\n try {\n const credentials = await this.identityApi.getCredentials();\n return credentials?.token ? true : false;\n } catch {\n return false;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UserSettingsStorage.esm.js","sources":["../../../src/apis/StorageApi/UserSettingsStorage.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { WebStorage } from '@backstage/core-app-api';\nimport {\n DiscoveryApi,\n ErrorApi,\n FetchApi,\n IdentityApi,\n StorageApi,\n StorageValueSnapshot,\n} from '@backstage/core-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport { JsonValue, Observable } from '@backstage/types';\nimport { SignalApi, SignalSubscriber } from '@backstage/plugin-signals-react';\nimport ObservableImpl from 'zen-observable';\nimport {\n MultiGetResponse,\n UserSettingsSignal,\n} from '@backstage/plugin-user-settings-common';\nimport DataLoader from 'dataloader';\nimport { CacheMap } from './CacheMap';\n\nconst JSON_HEADERS = {\n 'Content-Type': 'application/json; charset=utf-8',\n Accept: 'application/json',\n};\n\nconst buckets = new Map<string, UserSettingsStorage>();\n\nconst DATALOADER_CACHE_TTL_MS = 2 * 1000; // 2 seconds cache\nconst DATALOADER_WINDOW_MS = 10; // 10 ms\n\ntype DataLoaderType = DataLoader<\n { bucket: string; key: string },\n StorageValueSnapshot<JsonValue>,\n string\n>;\n\n/**\n * An implementation of the storage API, that uses the user-settings backend to\n * persist the data in the DB.\n *\n * @public\n */\nexport class UserSettingsStorage implements StorageApi {\n private subscribers = new Set<\n ZenObservable.SubscriptionObserver<StorageValueSnapshot<JsonValue>>\n >();\n\n private readonly observables = new Map<\n string,\n Observable<StorageValueSnapshot<JsonValue>>\n >();\n\n private readonly namespace: string;\n private readonly fetchApi: FetchApi;\n private readonly discoveryApi: DiscoveryApi;\n private readonly errorApi: ErrorApi;\n private readonly identityApi: IdentityApi;\n private readonly fallback: WebStorage;\n private readonly signalApi?: SignalApi;\n private readonly userSettingsLoader: DataLoaderType;\n\n private constructor(\n namespace: string,\n fetchApi: FetchApi,\n discoveryApi: DiscoveryApi,\n errorApi: ErrorApi,\n identityApi: IdentityApi,\n fallback: WebStorage,\n signalApi?: SignalApi,\n userSettingsLoader?: DataLoaderType,\n ) {\n this.namespace = namespace;\n this.fetchApi = fetchApi;\n this.discoveryApi = discoveryApi;\n this.errorApi = errorApi;\n this.identityApi = identityApi;\n this.fallback = fallback;\n this.signalApi = signalApi;\n\n this.userSettingsLoader =\n userSettingsLoader ??\n new DataLoader(\n async bucketAndKeyList => this.getMulti(bucketAndKeyList),\n {\n name: 'UserSettingsStorage.userSettingsLoader',\n cacheMap: new CacheMap<\n string,\n Promise<StorageValueSnapshot<JsonValue>>\n >(DATALOADER_CACHE_TTL_MS),\n cacheKeyFn: bucketAndKey => this.stringifyDataLoaderKey(bucketAndKey),\n maxBatchSize: 100,\n batchScheduleFn: cb => setTimeout(cb, DATALOADER_WINDOW_MS),\n },\n );\n }\n\n private stringifyDataLoaderKey({\n bucket,\n key,\n }: {\n bucket: string;\n key: string;\n }) {\n return `${encodeURIComponent(bucket)}/${encodeURIComponent(key)}`;\n }\n private clearCacheKey(key: string) {\n this.userSettingsLoader.clear({ bucket: this.namespace, key });\n }\n\n static create(options: {\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n errorApi: ErrorApi;\n identityApi: IdentityApi;\n signalApi?: SignalApi;\n namespace?: string;\n }): UserSettingsStorage {\n return new UserSettingsStorage(\n options.namespace ?? 'default',\n options.fetchApi,\n options.discoveryApi,\n options.errorApi,\n options.identityApi,\n WebStorage.create({\n namespace: options.namespace,\n errorApi: options.errorApi,\n }),\n options.signalApi,\n );\n }\n\n forBucket(name: string): StorageApi {\n // use dot instead of slash separator to have nicer URLs\n const bucketPath = `${this.namespace}.${name}`;\n\n if (!buckets.has(bucketPath)) {\n buckets.set(\n bucketPath,\n new UserSettingsStorage(\n bucketPath,\n this.fetchApi,\n this.discoveryApi,\n this.errorApi,\n this.identityApi,\n this.fallback,\n this.signalApi,\n this.userSettingsLoader,\n ),\n );\n }\n\n return buckets.get(bucketPath)!;\n }\n\n async remove(key: string): Promise<void> {\n const fetchUrl = await this.getFetchUrl(key);\n\n const response = await this.fetchApi.fetch(fetchUrl, {\n method: 'DELETE',\n });\n\n if (!response.ok && response.status !== 404) {\n throw await ResponseError.fromResponse(response);\n }\n\n this.clearCacheKey(key);\n\n this.notifyChanges({ key, presence: 'absent' });\n }\n\n async set<T extends JsonValue>(key: string, data: T): Promise<void> {\n if (!(await this.isSignedIn())) {\n await this.fallback.set(key, data);\n this.notifyChanges({ key, presence: 'present', value: data });\n this.clearCacheKey(key);\n return;\n }\n\n this.clearCacheKey(key);\n\n const fetchUrl = await this.getFetchUrl(key);\n\n const response = await this.fetchApi.fetch(fetchUrl, {\n method: 'PUT',\n headers: JSON_HEADERS,\n body: JSON.stringify({ value: data }),\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const { value } = await response.json();\n\n this.userSettingsLoader.prime(\n { bucket: this.namespace, key },\n {\n key,\n presence: 'present',\n value,\n },\n );\n\n this.notifyChanges({ key, value, presence: 'present' });\n }\n\n observe$<T extends JsonValue>(\n key: string,\n ): Observable<StorageValueSnapshot<T>> {\n if (!this.observables.has(key)) {\n this.observables.set(\n key,\n new ObservableImpl<StorageValueSnapshot<JsonValue>>(subscriber => {\n let signalSubscription: SignalSubscriber | undefined;\n this.subscribers.add(subscriber);\n\n const updateSnapshot = () => {\n Promise.resolve()\n .then(() =>\n this.userSettingsLoader.load({ bucket: this.namespace, key }),\n )\n .then(snapshot => subscriber.next(snapshot))\n .catch(error => this.errorApi.post(error));\n };\n\n if (this.signalApi) {\n signalSubscription = this.signalApi.subscribe(\n `user-settings`,\n (msg: UserSettingsSignal) => {\n if (msg.key === key) {\n updateSnapshot();\n }\n },\n );\n }\n\n updateSnapshot();\n\n return () => {\n if (signalSubscription) {\n signalSubscription.unsubscribe();\n }\n this.subscribers.delete(subscriber);\n };\n }).filter(({ key: messageKey }) => messageKey === key),\n );\n }\n\n return this.observables.get(key) as Observable<StorageValueSnapshot<T>>;\n }\n\n snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T> {\n return { key, presence: 'unknown' };\n }\n\n private async getMulti(\n bucketAndKeyList: readonly { bucket: string; key: string }[],\n ): Promise<StorageValueSnapshot<JsonValue>[]> {\n if (bucketAndKeyList.length === 0) return [];\n\n if (!(await this.isSignedIn())) {\n // This explicitly uses WebStorage, which we know is synchronous and doesn't return presence: unknown\n return bucketAndKeyList.map(bucketAndKey =>\n this.fallback.snapshot(bucketAndKey.key),\n );\n }\n\n try {\n const baseUrl = await this.discoveryApi.getBaseUrl('user-settings');\n const response = await this.fetchApi.fetch(`${baseUrl}/multiget`, {\n method: 'POST',\n headers: JSON_HEADERS,\n body: JSON.stringify({ items: bucketAndKeyList }),\n });\n\n if (response.status === 404) {\n return bucketAndKeyList.map(bucketAndKey => ({\n key: bucketAndKey.key,\n presence: 'absent',\n }));\n }\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n const { items: values } = (await response.json()) as MultiGetResponse;\n\n return bucketAndKeyList.map(\n ({ key }, i): StorageValueSnapshot<JsonValue> => {\n if (!values[i]) {\n return { key, presence: 'absent' };\n }\n return {\n key,\n presence: 'present',\n value: JSON.parse(JSON.stringify(values[i].value), (_key, val) => {\n if (typeof val === 'object' && val !== null) {\n Object.freeze(val);\n }\n return val;\n }),\n };\n },\n );\n } catch (e) {\n this.errorApi.post(new Error(`Failed to fetch user settings, ${e}`));\n return bucketAndKeyList.map(bucketAndKey => ({\n key: bucketAndKey.key,\n presence: 'absent',\n }));\n }\n }\n\n private async getFetchUrl(key: string) {\n const baseUrl = await this.discoveryApi.getBaseUrl('user-settings');\n const encodedNamespace = encodeURIComponent(this.namespace);\n const encodedKey = encodeURIComponent(key);\n return `${baseUrl}/buckets/${encodedNamespace}/keys/${encodedKey}`;\n }\n\n private async notifyChanges<T extends JsonValue>(\n snapshot: StorageValueSnapshot<T>,\n ) {\n for (const subscription of this.subscribers) {\n try {\n subscription.next(snapshot);\n } catch {\n // ignore\n }\n }\n }\n\n private async isSignedIn(): Promise<boolean> {\n try {\n const credentials = await this.identityApi.getCredentials();\n return credentials?.token ? true : false;\n } catch {\n return false;\n }\n }\n}\n"],"names":[],"mappings":";;;;;;AAoCA,MAAM,YAAA,GAAe;AAAA,EACnB,cAAA,EAAgB,iCAAA;AAAA,EAChB,MAAA,EAAQ;AACV,CAAA;AAEA,MAAM,OAAA,uBAAc,GAAA,EAAiC;AAErD,MAAM,0BAA0B,CAAA,GAAI,GAAA;AACpC,MAAM,oBAAA,GAAuB,EAAA;AActB,MAAM,mBAAA,CAA0C;AAAA,EAC7C,WAAA,uBAAkB,GAAA,EAExB;AAAA,EAEe,WAAA,uBAAkB,GAAA,EAGjC;AAAA,EAEe,SAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,QAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,kBAAA;AAAA,EAET,WAAA,CACN,WACA,QAAA,EACA,YAAA,EACA,UACA,WAAA,EACA,QAAA,EACA,WACA,kBAAA,EACA;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AACjB,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,YAAA,GAAe,YAAA;AACpB,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,WAAA,GAAc,WAAA;AACnB,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAEjB,IAAA,IAAA,CAAK,kBAAA,GACH,sBACA,IAAI,UAAA;AAAA,MACF,OAAM,gBAAA,KAAoB,IAAA,CAAK,QAAA,CAAS,gBAAgB,CAAA;AAAA,MACxD;AAAA,QACE,IAAA,EAAM,wCAAA;AAAA,QACN,QAAA,EAAU,IAAI,QAAA,CAGZ,uBAAuB,CAAA;AAAA,QACzB,UAAA,EAAY,CAAA,YAAA,KAAgB,IAAA,CAAK,sBAAA,CAAuB,YAAY,CAAA;AAAA,QACpE,YAAA,EAAc,GAAA;AAAA,QACd,eAAA,EAAiB,CAAA,EAAA,KAAM,UAAA,CAAW,EAAA,EAAI,oBAAoB;AAAA;AAC5D,KACF;AAAA,EACJ;AAAA,EAEQ,sBAAA,CAAuB;AAAA,IAC7B,MAAA;AAAA,IACA;AAAA,GACF,EAGG;AACD,IAAA,OAAO,GAAG,kBAAA,CAAmB,MAAM,CAAC,CAAA,CAAA,EAAI,kBAAA,CAAmB,GAAG,CAAC,CAAA,CAAA;AAAA,EACjE;AAAA,EACQ,cAAc,GAAA,EAAa;AACjC,IAAA,IAAA,CAAK,mBAAmB,KAAA,CAAM,EAAE,QAAQ,IAAA,CAAK,SAAA,EAAW,KAAK,CAAA;AAAA,EAC/D;AAAA,EAEA,OAAO,OAAO,OAAA,EAOU;AACtB,IAAA,OAAO,IAAI,mBAAA;AAAA,MACT,QAAQ,SAAA,IAAa,SAAA;AAAA,MACrB,OAAA,CAAQ,QAAA;AAAA,MACR,OAAA,CAAQ,YAAA;AAAA,MACR,OAAA,CAAQ,QAAA;AAAA,MACR,OAAA,CAAQ,WAAA;AAAA,MACR,WAAW,MAAA,CAAO;AAAA,QAChB,WAAW,OAAA,CAAQ,SAAA;AAAA,QACnB,UAAU,OAAA,CAAQ;AAAA,OACnB,CAAA;AAAA,MACD,OAAA,CAAQ;AAAA,KACV;AAAA,EACF;AAAA,EAEA,UAAU,IAAA,EAA0B;AAElC,IAAA,MAAM,UAAA,GAAa,CAAA,EAAG,IAAA,CAAK,SAAS,IAAI,IAAI,CAAA,CAAA;AAE5C,IAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,UAAU,CAAA,EAAG;AAC5B,MAAA,OAAA,CAAQ,GAAA;AAAA,QACN,UAAA;AAAA,QACA,IAAI,mBAAA;AAAA,UACF,UAAA;AAAA,UACA,IAAA,CAAK,QAAA;AAAA,UACL,IAAA,CAAK,YAAA;AAAA,UACL,IAAA,CAAK,QAAA;AAAA,UACL,IAAA,CAAK,WAAA;AAAA,UACL,IAAA,CAAK,QAAA;AAAA,UACL,IAAA,CAAK,SAAA;AAAA,UACL,IAAA,CAAK;AAAA;AACP,OACF;AAAA,IACF;AAEA,IAAA,OAAO,OAAA,CAAQ,IAAI,UAAU,CAAA;AAAA,EAC/B;AAAA,EAEA,MAAM,OAAO,GAAA,EAA4B;AACvC,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,WAAA,CAAY,GAAG,CAAA;AAE3C,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,CAAS,MAAM,QAAA,EAAU;AAAA,MACnD,MAAA,EAAQ;AAAA,KACT,CAAA;AAED,IAAA,IAAI,CAAC,QAAA,CAAS,EAAA,IAAM,QAAA,CAAS,WAAW,GAAA,EAAK;AAC3C,MAAA,MAAM,MAAM,aAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA,IACjD;AAEA,IAAA,IAAA,CAAK,cAAc,GAAG,CAAA;AAEtB,IAAA,IAAA,CAAK,aAAA,CAAc,EAAE,GAAA,EAAK,QAAA,EAAU,UAAU,CAAA;AAAA,EAChD;AAAA,EAEA,MAAM,GAAA,CAAyB,GAAA,EAAa,IAAA,EAAwB;AAClE,IAAA,IAAI,CAAE,MAAM,IAAA,CAAK,UAAA,EAAW,EAAI;AAC9B,MAAA,MAAM,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AACjC,MAAA,IAAA,CAAK,cAAc,EAAE,GAAA,EAAK,UAAU,SAAA,EAAW,KAAA,EAAO,MAAM,CAAA;AAC5D,MAAA,IAAA,CAAK,cAAc,GAAG,CAAA;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,cAAc,GAAG,CAAA;AAEtB,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,WAAA,CAAY,GAAG,CAAA;AAE3C,IAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,QAAA,CAAS,MAAM,QAAA,EAAU;AAAA,MACnD,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS,YAAA;AAAA,MACT,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,MAAM;AAAA,KACrC,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,MAAM,aAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA,IACjD;AAEA,IAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAM,SAAS,IAAA,EAAK;AAEtC,IAAA,IAAA,CAAK,kBAAA,CAAmB,KAAA;AAAA,MACtB,EAAE,MAAA,EAAQ,IAAA,CAAK,SAAA,EAAW,GAAA,EAAI;AAAA,MAC9B;AAAA,QACE,GAAA;AAAA,QACA,QAAA,EAAU,SAAA;AAAA,QACV;AAAA;AACF,KACF;AAEA,IAAA,IAAA,CAAK,cAAc,EAAE,GAAA,EAAK,KAAA,EAAO,QAAA,EAAU,WAAW,CAAA;AAAA,EACxD;AAAA,EAEA,SACE,GAAA,EACqC;AACrC,IAAA,IAAI,CAAC,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA,EAAG;AAC9B,MAAA,IAAA,CAAK,WAAA,CAAY,GAAA;AAAA,QACf,GAAA;AAAA,QACA,IAAI,eAAgD,CAAA,UAAA,KAAc;AAChE,UAAA,IAAI,kBAAA;AACJ,UAAA,IAAA,CAAK,WAAA,CAAY,IAAI,UAAU,CAAA;AAE/B,UAAA,MAAM,iBAAiB,MAAM;AAC3B,YAAA,OAAA,CAAQ,SAAQ,CACb,IAAA;AAAA,cAAK,MACJ,KAAK,kBAAA,CAAmB,IAAA,CAAK,EAAE,MAAA,EAAQ,IAAA,CAAK,SAAA,EAAW,GAAA,EAAK;AAAA,aAC9D,CACC,IAAA,CAAK,CAAA,QAAA,KAAY,UAAA,CAAW,KAAK,QAAQ,CAAC,CAAA,CAC1C,KAAA,CAAM,CAAA,KAAA,KAAS,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,KAAK,CAAC,CAAA;AAAA,UAC7C,CAAA;AAEA,UAAA,IAAI,KAAK,SAAA,EAAW;AAClB,YAAA,kBAAA,GAAqB,KAAK,SAAA,CAAU,SAAA;AAAA,cAClC,CAAA,aAAA,CAAA;AAAA,cACA,CAAC,GAAA,KAA4B;AAC3B,gBAAA,IAAI,GAAA,CAAI,QAAQ,GAAA,EAAK;AACnB,kBAAA,cAAA,EAAe;AAAA,gBACjB;AAAA,cACF;AAAA,aACF;AAAA,UACF;AAEA,UAAA,cAAA,EAAe;AAEf,UAAA,OAAO,MAAM;AACX,YAAA,IAAI,kBAAA,EAAoB;AACtB,cAAA,kBAAA,CAAmB,WAAA,EAAY;AAAA,YACjC;AACA,YAAA,IAAA,CAAK,WAAA,CAAY,OAAO,UAAU,CAAA;AAAA,UACpC,CAAA;AAAA,QACF,CAAC,EAAE,MAAA,CAAO,CAAC,EAAE,GAAA,EAAK,UAAA,EAAW,KAAM,UAAA,KAAe,GAAG;AAAA,OACvD;AAAA,IACF;AAEA,IAAA,OAAO,IAAA,CAAK,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA;AAAA,EACjC;AAAA,EAEA,SAA8B,GAAA,EAAsC;AAClE,IAAA,OAAO,EAAE,GAAA,EAAK,QAAA,EAAU,SAAA,EAAU;AAAA,EACpC;AAAA,EAEA,MAAc,SACZ,gBAAA,EAC4C;AAC5C,IAAA,IAAI,gBAAA,CAAiB,MAAA,KAAW,CAAA,EAAG,OAAO,EAAC;AAE3C,IAAA,IAAI,CAAE,MAAM,IAAA,CAAK,UAAA,EAAW,EAAI;AAE9B,MAAA,OAAO,gBAAA,CAAiB,GAAA;AAAA,QAAI,CAAA,YAAA,KAC1B,IAAA,CAAK,QAAA,CAAS,QAAA,CAAS,aAAa,GAAG;AAAA,OACzC;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,YAAA,CAAa,WAAW,eAAe,CAAA;AAClE,MAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,SAAA,CAAA,EAAa;AAAA,QAChE,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,YAAA;AAAA,QACT,MAAM,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,kBAAkB;AAAA,OACjD,CAAA;AAED,MAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AAC3B,QAAA,OAAO,gBAAA,CAAiB,IAAI,CAAA,YAAA,MAAiB;AAAA,UAC3C,KAAK,YAAA,CAAa,GAAA;AAAA,UAClB,QAAA,EAAU;AAAA,SACZ,CAAE,CAAA;AAAA,MACJ;AAEA,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,MAAM,aAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA,MACjD;AAEA,MAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAO,GAAK,MAAM,SAAS,IAAA,EAAK;AAE/C,MAAA,OAAO,gBAAA,CAAiB,GAAA;AAAA,QACtB,CAAC,EAAE,GAAA,EAAI,EAAG,CAAA,KAAuC;AAC/C,UAAA,IAAI,CAAC,MAAA,CAAO,CAAC,CAAA,EAAG;AACd,YAAA,OAAO,EAAE,GAAA,EAAK,QAAA,EAAU,QAAA,EAAS;AAAA,UACnC;AACA,UAAA,OAAO;AAAA,YACL,GAAA;AAAA,YACA,QAAA,EAAU,SAAA;AAAA,YACV,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,CAAC,CAAA,CAAE,KAAK,CAAA,EAAG,CAAC,IAAA,EAAM,GAAA,KAAQ;AAChE,cAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,IAAA,EAAM;AAC3C,gBAAA,MAAA,CAAO,OAAO,GAAG,CAAA;AAAA,cACnB;AACA,cAAA,OAAO,GAAA;AAAA,YACT,CAAC;AAAA,WACH;AAAA,QACF;AAAA,OACF;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,IAAA,CAAK,SAAS,IAAA,CAAK,IAAI,MAAM,CAAA,+BAAA,EAAkC,CAAC,EAAE,CAAC,CAAA;AACnE,MAAA,OAAO,gBAAA,CAAiB,IAAI,CAAA,YAAA,MAAiB;AAAA,QAC3C,KAAK,YAAA,CAAa,GAAA;AAAA,QAClB,QAAA,EAAU;AAAA,OACZ,CAAE,CAAA;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAc,YAAY,GAAA,EAAa;AACrC,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,YAAA,CAAa,WAAW,eAAe,CAAA;AAClE,IAAA,MAAM,gBAAA,GAAmB,kBAAA,CAAmB,IAAA,CAAK,SAAS,CAAA;AAC1D,IAAA,MAAM,UAAA,GAAa,mBAAmB,GAAG,CAAA;AACzC,IAAA,OAAO,CAAA,EAAG,OAAO,CAAA,SAAA,EAAY,gBAAgB,SAAS,UAAU,CAAA,CAAA;AAAA,EAClE;AAAA,EAEA,MAAc,cACZ,QAAA,EACA;AACA,IAAA,KAAA,MAAW,YAAA,IAAgB,KAAK,WAAA,EAAa;AAC3C,MAAA,IAAI;AACF,QAAA,YAAA,CAAa,KAAK,QAAQ,CAAA;AAAA,MAC5B,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,UAAA,GAA+B;AAC3C,IAAA,IAAI;AACF,MAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,WAAA,CAAY,cAAA,EAAe;AAC1D,MAAA,OAAO,WAAA,EAAa,QAAQ,IAAA,GAAO,KAAA;AAAA,IACrC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACF;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,10 @@ declare class UserSettingsStorage implements StorageApi {
|
|
|
23
23
|
private readonly identityApi;
|
|
24
24
|
private readonly fallback;
|
|
25
25
|
private readonly signalApi?;
|
|
26
|
+
private readonly userSettingsLoader;
|
|
26
27
|
private constructor();
|
|
28
|
+
private stringifyDataLoaderKey;
|
|
29
|
+
private clearCacheKey;
|
|
27
30
|
static create(options: {
|
|
28
31
|
fetchApi: FetchApi;
|
|
29
32
|
discoveryApi: DiscoveryApi;
|
|
@@ -37,7 +40,7 @@ declare class UserSettingsStorage implements StorageApi {
|
|
|
37
40
|
set<T extends JsonValue>(key: string, data: T): Promise<void>;
|
|
38
41
|
observe$<T extends JsonValue>(key: string): Observable<StorageValueSnapshot<T>>;
|
|
39
42
|
snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T>;
|
|
40
|
-
private
|
|
43
|
+
private getMulti;
|
|
41
44
|
private getFetchUrl;
|
|
42
45
|
private notifyChanges;
|
|
43
46
|
private isSignedIn;
|
package/dist/package.json.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "@backstage/plugin-user-settings";
|
|
2
|
-
var version = "0.
|
|
2
|
+
var version = "0.9.0-next.2";
|
|
3
3
|
var description = "A Backstage plugin that provides a settings page";
|
|
4
4
|
var backstage = {
|
|
5
5
|
role: "frontend-plugin",
|
|
@@ -68,6 +68,7 @@ var dependencies = {
|
|
|
68
68
|
"@material-ui/core": "^4.12.2",
|
|
69
69
|
"@material-ui/icons": "^4.9.1",
|
|
70
70
|
"@material-ui/lab": "4.0.0-alpha.61",
|
|
71
|
+
dataloader: "^2.0.0",
|
|
71
72
|
"react-use": "^17.2.4",
|
|
72
73
|
"zen-observable": "^0.10.0"
|
|
73
74
|
};
|
|
@@ -84,13 +85,13 @@ var devDependencies = {
|
|
|
84
85
|
msw: "^1.0.0",
|
|
85
86
|
react: "^18.0.2",
|
|
86
87
|
"react-dom": "^18.0.2",
|
|
87
|
-
"react-router-dom": "^6.
|
|
88
|
+
"react-router-dom": "^6.30.2"
|
|
88
89
|
};
|
|
89
90
|
var peerDependencies = {
|
|
90
91
|
"@types/react": "^17.0.0 || ^18.0.0",
|
|
91
92
|
react: "^17.0.0 || ^18.0.0",
|
|
92
93
|
"react-dom": "^17.0.0 || ^18.0.0",
|
|
93
|
-
"react-router-dom": "^6.
|
|
94
|
+
"react-router-dom": "^6.30.2"
|
|
94
95
|
};
|
|
95
96
|
var peerDependenciesMeta = {
|
|
96
97
|
"@types/react": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.json.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"package.json.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-user-settings",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-next.2",
|
|
4
4
|
"description": "A Backstage plugin that provides a settings page",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "frontend-plugin",
|
|
@@ -68,27 +68,28 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@backstage/catalog-model": "1.7.6",
|
|
71
|
-
"@backstage/core-app-api": "1.19.5-next.
|
|
72
|
-
"@backstage/core-components": "0.18.7-next.
|
|
73
|
-
"@backstage/core-plugin-api": "1.12.3-next.
|
|
71
|
+
"@backstage/core-app-api": "1.19.5-next.1",
|
|
72
|
+
"@backstage/core-components": "0.18.7-next.2",
|
|
73
|
+
"@backstage/core-plugin-api": "1.12.3-next.1",
|
|
74
74
|
"@backstage/errors": "1.2.7",
|
|
75
|
-
"@backstage/frontend-plugin-api": "0.14.0-next.
|
|
76
|
-
"@backstage/plugin-catalog-react": "
|
|
77
|
-
"@backstage/plugin-signals-react": "0.0.19-next.
|
|
78
|
-
"@backstage/plugin-user-settings-common": "0.0.
|
|
79
|
-
"@backstage/theme": "0.7.2-next.
|
|
75
|
+
"@backstage/frontend-plugin-api": "0.14.0-next.2",
|
|
76
|
+
"@backstage/plugin-catalog-react": "2.0.0-next.2",
|
|
77
|
+
"@backstage/plugin-signals-react": "0.0.19-next.1",
|
|
78
|
+
"@backstage/plugin-user-settings-common": "0.1.0-next.0",
|
|
79
|
+
"@backstage/theme": "0.7.2-next.1",
|
|
80
80
|
"@backstage/types": "1.2.2",
|
|
81
81
|
"@material-ui/core": "^4.12.2",
|
|
82
82
|
"@material-ui/icons": "^4.9.1",
|
|
83
83
|
"@material-ui/lab": "4.0.0-alpha.61",
|
|
84
|
+
"dataloader": "^2.0.0",
|
|
84
85
|
"react-use": "^17.2.4",
|
|
85
86
|
"zen-observable": "^0.10.0"
|
|
86
87
|
},
|
|
87
88
|
"devDependencies": {
|
|
88
|
-
"@backstage/cli": "0.35.4-next.
|
|
89
|
-
"@backstage/dev-utils": "1.1.20-next.
|
|
90
|
-
"@backstage/plugin-catalog": "1.33.0-next.
|
|
91
|
-
"@backstage/test-utils": "1.7.15-next.
|
|
89
|
+
"@backstage/cli": "0.35.4-next.2",
|
|
90
|
+
"@backstage/dev-utils": "1.1.20-next.2",
|
|
91
|
+
"@backstage/plugin-catalog": "1.33.0-next.2",
|
|
92
|
+
"@backstage/test-utils": "1.7.15-next.2",
|
|
92
93
|
"@testing-library/dom": "^10.0.0",
|
|
93
94
|
"@testing-library/jest-dom": "^6.0.0",
|
|
94
95
|
"@testing-library/react": "^16.0.0",
|
|
@@ -97,13 +98,13 @@
|
|
|
97
98
|
"msw": "^1.0.0",
|
|
98
99
|
"react": "^18.0.2",
|
|
99
100
|
"react-dom": "^18.0.2",
|
|
100
|
-
"react-router-dom": "^6.
|
|
101
|
+
"react-router-dom": "^6.30.2"
|
|
101
102
|
},
|
|
102
103
|
"peerDependencies": {
|
|
103
104
|
"@types/react": "^17.0.0 || ^18.0.0",
|
|
104
105
|
"react": "^17.0.0 || ^18.0.0",
|
|
105
106
|
"react-dom": "^17.0.0 || ^18.0.0",
|
|
106
|
-
"react-router-dom": "^6.
|
|
107
|
+
"react-router-dom": "^6.30.2"
|
|
107
108
|
},
|
|
108
109
|
"peerDependenciesMeta": {
|
|
109
110
|
"@types/react": {
|